【移動開發】Checkout開源庫原始碼解析

Cooke發表於2018-11-23

Checkout開源庫的原始碼解析

1.功能介紹

1.1Checkout是什麼

Checkout是Android In-App Billing API(v3 +)的一個封裝庫。In-App Billing 是一項 Google Play提供的內購服務,可讓我們在自己的應用內出售數字內容。我們可以使用該服務來出售眾多內容,包括可下載內容(例如媒體檔案或照片)和虛擬內容(例如遊戲關卡或魔藥、高階服務和功能,等等)Checkout的主要目標是儘可能簡單直接地整合應用內產品:開發人員不應該花太多時間來實現乏味的應用內結算API,而應該關注更重要的事情 - 他們的應用。 Checkout的github地址是:https://github.com/serso/android-checkout

1.2Checkout解決的問題

  • Activity被銷燬時如何取消所有的billing請求?
  • 如何在後臺查詢購買資訊?
  • 如何驗證購買?
  • 如何使用continuationToken來載入使用者已購買的商品項以及商品項的資訊[介面限制每次請求只會返回20個商品項]
  • 如何使用最少示例程式碼增加儲值功能?

1.3結算流程

【移動開發】Checkout開源庫原始碼解析

2.總體設計

2.1總體設計圖

【移動開發】Checkout開源庫原始碼解析

2.2核心類的概念

Billing: Checkout的核心類,實現了Android's Billing API。主要負責:

  • Billing Service的連線建立和斷開
  • 執行結算請求
  • 快取請求結果
  • 建立Checkout物件

Request: 表示Billing結算請求的實體類,具體實現類有BillingSupportedRequest,GetPurchaseHistoryRequest,GetPurchasesRequest,ChangePurchaseRequest,ConsumePurchaseRequest,GetSkuDetailsRequest,PurchaseRequest,分別代表具體的請求操作。

OnConnectedServiceRunnable: Request的包裝類,實現了RequestRunnable介面,核心方法是run()

Checkout: Billing類的幫助類,維護了Billing例項,用於主執行緒中,生命週期需與activity/fragment/service繫結,對應的子類有FragmentCheckout,ActivityCheckout和CustomUiCheckout等。

PendingRequests: 該類表示待處理的請求,維護了一個RequestRunnable的集合,所有的請求順序執行。

Configuration: 表示Billing結算的配置介面,需要實現Configuration介面自定義配置。

Cache: 表示快取的介面,具體實現類為MapCache。

ServiceConnector: 連線服務的介面,預設實現類為DefaultServiceConnector,負責Google play app的繫結和解綁。

Purchase: 表示購買資訊的類,成員變數與getBuyIntent()返回的INAPP_DATA_SIGNATURE資料的 JSON 欄位對應,也就是說Purchase都是根據這個JSON欄位的內容定義的。

Purchases: 表示購買資訊列表的類。維護了一個Purchase集合。

PurchaseFlow: 表示從使用者請求購買之時起直到購買完成為止的一個購買流程的類

PurchaseVerifier: 驗證購買介面,實現類為BasePurchaseVerifier,該類為抽象類,可繼承它實現自己的驗證類。驗證過程通常在後臺伺服器進行。

Inventory: 用於載入產品,SKU和購買相關資訊的類,其生命週期與Checkout的相關。子類有FallingBackInventory,CheckoutInventory和RobotmediaInventory。

3.request流程圖

【移動開發】Checkout開源庫原始碼解析

4.詳細設計

4.1UML類關係圖

【移動開發】Checkout開源庫原始碼解析

4.2核心類解析

4.2.1 Checkout.java

Checkout是一個工具類,主要是對Billing結算流程的一個封裝和對Inventory的處理。根據Context環境的不同,構建一個Checkout類的非抽象子類(FragmentCheckoutActivityCheckoutCustomUiCheckout)物件,啟動結算流程。 注意:Checkout要與activity/fragment/service等生命週期相繫結,在onDestroy()中呼叫mCheckout.stop(),取消待執行的請求,解綁service.

1.主要成員變數

  • Billing mBilling主類例項

  • Billing.Requests mRequests 代表各種結算方法的物件。

2.構造物件

根據以下幾個靜態方法構造出子類例項,對應ui/activity/fragment/service,並將Billing作為引數傳進來。

public static UiCheckout forUi(@Nonnull IntentStarter intentStarter, @Nonnull Object tag, @Nonnull Billing billing);
public static UiCheckout forFragment(@Nonnull Fragment fragment, @Nonnull Billing billing);
public static ActivityCheckout forActivity(@Nonnull Activity activity, @Nonnull Billing billing);
public static Checkout forService(@Nonnull Service service, @Nonnull Billing billing);
複製程式碼

2. 主要方法

作為Checkout庫的呼叫入口,建立出 Checkout 以後,呼叫 start 方法

public void start() {
    start(null);
}

public void start(@Nullable final Listener listener) {
    Check.isMainThread();

    synchronized (mLock) {
        Check.isFalse(mState == State.STARTED, "Already started");
        Check.isNull(mRequests, "Already started");
        mState = State.STARTED;
        mBilling.onCheckoutStarted();
        mRequests = mBilling.getRequests(mTag);
    }
    whenReady(listener == null ? new EmptyListener() {} : listener);
}
複製程式碼

start有兩過載方法,無參方法呼叫帶有listener的方法,由第二個方法可見,主要是通過mBilling獲取mRequests,然後呼叫whenReady()方法。

public void whenReady(@Nonnull final Listener listener) {
    Check.isMainThread();

    synchronized (mLock) {
        Check.isNotNull(mRequests);
        final Billing.Requests requests = mRequests;

        @Nonnull
        final Set<String> loadingProducts = new HashSet<>(ProductTypes.ALL);
        for (final String product : ProductTypes.ALL) {
            requests.isBillingSupported(product, new RequestListener<Object>() {

                private void onBillingSupported(boolean supported) {
                    listener.onReady(requests, product, supported);
                    loadingProducts.remove(product);
                    if (loadingProducts.isEmpty()) {
                        listener.onReady(requests);
                    }
                }

                @Override
                public void onSuccess(@Nonnull Object result) {
                    onBillingSupported(true);
                }

                @Override
                public void onError(int response, @Nonnull Exception e) {
                    onBillingSupported(false);
                }
            });
        }
    }
}
複製程式碼

whenReady()方法的目的是檢查是否支援Billing API,也就是最終會呼叫service.isBillingSupported()方法,然後返回回撥處理結果。

當離開頁面時,需要呼叫stop()方法釋放資源

public void stop() {
    Check.isMainThread();

    synchronized (mLock) {
        if (mState != State.INITIAL) {
            mState = State.STOPPED;
        }
        if (mRequests != null) {
            mRequests.cancelAll();
            mRequests = null;
        }
        if (mState == State.STOPPED) {
            mBilling.onCheckoutStopped();
        }
    }
}
複製程式碼

當呼叫stop()時,將Request佇列中的請求取消,而mBilling.onCheckoutStopped();主要做的事是斷開與Google Play服務的連線。

3.使用流程

【移動開發】Checkout開源庫原始碼解析

在分析Billing類之前,我們先分析Billing中幾個成員變數對應的類。

4.2.2 Request.java

表示Billing請求的實體類,該類為抽象類,具體實現類有BillingSupportedRequestGetSkuDetailsRequestConsumePurchaseRequest等,子類需要實現抽象方法

abstract void start(@Nonnull IInAppBillingService service, @Nonnull String packageName)
        throws RemoteException, RequestException;
abstract String getCacheKey();
複製程式碼

子類的start()呼叫service相關的Billing API方法。

主要成員變數

  • int mApiVersion In-app Billing的api版本

  • int mId 作為請求獨一無二的id

  • RequestType mType 請求的型別

  • Object mTag 標籤

  • RequestListener<R> mListener 請求的回撥介面

4.2.3 OnConnectedServiceRunnable

該類實現了RequestRunnable介面,主要是對Request的行為進行包裝,增加快取檢查和異常處理

1.成員變數

  • Request mRequest 被包裝的請求

2.核心方法

    @Override
    public boolean run() {
        final Request localRequest = getRequest();
        if (localRequest == null) {
            // request was cancelled => finish here
            return true;
        }

        if (checkCache(localRequest)) return true;

        // request is alive, let's check the service state
        final State localState;
        final IInAppBillingService localService;
        synchronized (mLock) {
            localState = mState;
            localService = mService;
        }
        if (localState == State.CONNECTED) {
            Check.isNotNull(localService);
            // service is connected, let's start request
            try {
                localRequest.start(localService, mContext.getPackageName());
            } catch (RemoteException | RuntimeException | RequestException e) {
                localRequest.onError(e);
            }
        } else {
            // service is not connected, let's check why
            if (localState != State.FAILED) {
                // service was disconnected
                connect();
                return false;
            } else {
                // service was not connected in the first place => can't do anything, aborting the request
                localRequest.onError(ResponseCodes.SERVICE_NOT_CONNECTED);
            }
        }

        return true;
    }
複製程式碼

該方法的邏輯也很清楚,先檢查是否有快取,如果有快取,直接返回(注意:checkCache()會將快取返回給request),否則檢查狀態,如果處於已連線狀態,執行request的start(),否則嘗試建立起連線。

4.2.4 PendingRequests

該類表示待處理的請求,並實現了Runnable介面,其維護了一個RequestRunnable列表mList,所有請求需新增至mList才能被處理。核心方法為run(),通過迴圈取出RequestRunnable,並執行RequestRunnablerun()方法。

4.2.5 Requests

該類實現了BillingRequests介面,Requests作為Billing的內部類,持有Billing例項的引用,並呼叫了其例項方法。BillingRequests定義一系列關於Billing api相關的方法

4.2.6 Configuration

Billing API的配置介面,定義瞭如下方法 String getPublicKey();獲取公鑰,用於購買過程中的簽名。

Cache getCache();獲取快取物件

PurchaseVerifier getPurchaseVerifier();返回PurchaseVerifier

Inventory getFallbackInventory(@Nonnull Checkout checkout, @Nonnull Executor onLoadExecutor);返回後備庫存,用於恢復購買

boolean isAutoConnect();是否自動連線

4.2.7 StaticConfiguration

該類可對其他Configuration進行包裝,得到其mPublicKeymPurchaseVerifier的引用。StaticConfiguration實現了Configuration的方法。一般情況下,我們需要實現自己的Configuration

1.成員變數

  • Configuration mOriginal 原始的Configuration

  • String mPublicKey;公鑰字串

  • PurchaseVerifier mPurchaseVerifier 驗證購買類物件

4.2.8 DefaultConfiguration

實現Configuration部分方法的類,該類通過newCache()獲取快取物件,通過newPurchaseVerifier()獲取購買驗證物件,isAutoConnect()直接返回true。而getFallbackInventory()則返回null,其子類需要實現getPublicKey()

4.2.9 Cache

快取介面,代表了一個可以獲取請求結果,儲存請求結果的快取。

1.主要方法

Entry get(Key key); 通過 key 獲取請求的快取實體

void put(Key key, Entry entry); 存入一個請求的快取實體

void init();初始化

void remove(Key key); 移除指定的快取實體

void removeAll(int type); 清除某一型別的快取實體

void clear(); 清空快取

2.代表鍵實體的內部類Key

成員變數

  • int type型別

  • String key鍵值字串

2.代表快取實體的內部類Entry

成員變數

  • Object data 快取的物件

  • long expiresAt快取到期時間

4.2.10 MapCache

Cache介面的實現類,通過維護一個Map<Key, Entry> mMap物件,實現了Cache的快取功能。

4.2.11 ConcurrentCache

Cache介面的實現類,該類對其他Cache實現類進行包裝,通過synchronized同步鎖達到執行緒安全的效果

4.2.12 SafeCache

該類對Cache介面的實現類,該類對其他Cache實現類進行包裝,捕獲異常。

4.2.13 DefaultServiceConnector

該類實現了ServiceConnector介面,實現了connect()disconnect(),用於處理服務建立與斷開。DefaultServiceConnector持有Billing物件的引用。

1.成員變數

  • ServiceConnection mConnection ServiceConnection例項,當建立連線後,會呼叫Billing的setService()

      private final ServiceConnection mConnection = new ServiceConnection() {
          @Override
          public void onServiceDisconnected(ComponentName name) {
              setService(null, false);
          }
    
          @Override
          public void onServiceConnected(ComponentName name,
                                         IBinder service) {
              setService(IInAppBillingService.Stub.asInterface(service), true);
          }
      };
    複製程式碼

2.實現方法

    @Override
    public boolean connect() {
        try {
            final Intent intent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
            intent.setPackage("com.android.vending");
            return mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        } catch (IllegalArgumentException e) {
            // some devices throw IllegalArgumentException (Service Intent must be explicit)
            // even though we set package name explicitly. Let's not crash the app and catch
            // such exceptions here, the billing on such devices will not work.
            return false;
        } catch (NullPointerException e) {
            // Meizu M3s phones might throw an NPE in Context#bindService (Attempt to read from field 'int com.android.server.am.ProcessRecord.uid' on a null object reference).
            // As in-app purchases don't work if connection to the billing service can't be
            // established let's not crash and allow users to continue using the app
            return false;
        }
    }

    @Override
    public void disconnect() {
        mContext.unbindService(mConnection);
    }
複製程式碼

connect()負責繫結服務,disconnect()解綁服務。

4.2.14 Billing.java

接下來重點分析Billing類。作為Checkout的核心類,Billing封裝了結算流程的主要邏輯。

1.構造物件

為避免與Google Play app重複連線,所以只能有一個Billing物件,所以我們採取在application中構建單例的形式。

@Nonnull
private final Billing mBilling = new Billing(this, new Conguration());
複製程式碼

2.主要成員變數

  • StaticConfiguration mConfiguration 配置類,主要是對publicKey,Cache等配置

  • ConcurrentCache mCache 快取類,代表了一個可以獲取請求結果,儲存請求結果的快取

  • PendingRequests mPendingRequests 表示待執行的請求佇列。

  • BillingRequests mRequests 定義了所有的billing結算方法的介面

  • IInAppBillingService mService billing服務例項物件

  • State mState 表示結算過程中的狀態

  • CancellableExecutor mMainThread表示主執行緒,用於處理服務連線建立和取消的過程。

  • Executor mBackground 表示子執行緒,用於處理結算流程。

  • ServiceConnector mConnector 服務連線類。

3.state狀態切換流程

state表示連線過程中的狀態的列舉類,具有INITIAL,CONNECTING,CONNECTED,DISCONNECTING,DISCONNECTED, FAILED6個狀態。state的轉換方式需要按照下圖:

【移動開發】Checkout開源庫原始碼解析

通過setState()方法改變State狀態,如果傳入的值為CONNECTED,則開始執行Request佇列

void setState(@Nonnull State newState) {
    synchronized (mLock) {
        if (mState == newState) {
            return;
        }
        Check.isTrue(sPreviousStates.get(newState).contains(mState), "State " + newState + " can't come right after " + mState + " state");
        mState = newState;
        switch (mState) {
            case DISCONNECTING:
                // as we can jump directly from DISCONNECTING to CONNECTED state let's remove
                // the listener here instead of in DISCONNECTED state. That also will protect
                // us from getting in the following trap: CONNECTED->DISCONNECTING->CONNECTING->FAILED
                mPlayStoreBroadcastReceiver.removeListener(mPlayStoreListener);
                break;
            case CONNECTED:
                // CONNECTED is the only state when we know for sure that Play Store is available.
                // Registering the listener here also means that it should be never registered
                // in the FAILED state
                mPlayStoreBroadcastReceiver.addListener(mPlayStoreListener);
                executePendingRequests();
                break;
            case FAILED:
                // the play store listener should not be registered in the receiver in case of
                // failure as FAILED state can't occur after CONNECTED
                Check.isTrue(!mPlayStoreBroadcastReceiver.contains(mPlayStoreListener), "Leaking the listener");
                mMainThread.execute(new Runnable() {
                    @Override
                    public void run() {
                        mPendingRequests.onConnectionFailed();
                    }
                });
                break;
        }
    }
}
複製程式碼

4.建立連線

public void connect() {
    synchronized (mLock) {
        if (mState == State.CONNECTED) {
            executePendingRequests();
            return;
        }
        if (mState == State.CONNECTING) {
            return;
        }
        if (mConfiguration.isAutoConnect() && mCheckoutCount <= 0) {
            warning("Auto connection feature is turned on. There is no need in calling Billing.connect() manually. See Billing.Configuration.isAutoConnect");
        }
        setState(State.CONNECTING);
        mMainThread.execute(new Runnable() {
            @Override
            public void run() {
                connectOnMainThread();
            }
        });
    }
}
複製程式碼

通過上面看出,connect()方法主要是設定stateCONNECTING,並通過mMainThread呼叫了connectOnMainThread()方法,該方法又呼叫了mConnectorconnect()方法,並返回mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)的結果。

需要注意的是,每次執行請求流程時,connect()都會被呼叫,確保服務是連線上的。

5.執行request

當建立起連線後,state被置為CONNECTED,並呼叫executePendingRequests()方法,該方法通過一個單執行緒的執行緒池,執行mPendingRequestsrun()方法,迴圈的取出request(實際上是RequestRunnable)並執行。

private void executePendingRequests() {
    mBackground.execute(mPendingRequests);
}
複製程式碼

當開啟某一型別的請求時,Billing類中的runWhenConnected()會被呼叫,這個方法會呼叫到connect(),並最終執行executePendingRequests()方法。 接著我們來重點看一下這個方法,這是個過載方法。

private int runWhenConnected(@Nonnull Request request, @Nullable Object tag) {
    return runWhenConnected(request, null, tag);
}

<R> int runWhenConnected(@Nonnull Request<R> request, @Nullable RequestListener<R> listener, @Nullable Object tag) {
    if (listener != null) {
        if (mCache.hasCache()) {
            listener = new CachingRequestListener<>(request, listener);
        }
        request.setListener(listener);
    }
    if (tag != null) {
        request.setTag(tag);
    }

    mPendingRequests.add(onConnectedService(request));
    connect();

    return request.getId();
}
複製程式碼

可以看出runWhenConnected()做的事情就是傳進一個request物件,並將其加到mPendingRequests中,然後在connect()中執行request任務。

6.request執行過程中的呼叫棧

我們來了解一下一個請求執行的過程,以獲取購買的商品為例

【移動開發】Checkout開源庫原始碼解析

7.斷開連線

public void disconnect() {
    synchronized (mLock) {
        if (mState == State.DISCONNECTED || mState == State.DISCONNECTING || mState == State.INITIAL) {
            return;
        }
        if (mState == State.FAILED) {
            // it would be strange to change the state from FAILED to DISCONNECTING/DISCONNECTED,
            // thus, just cancelling all pending the requested here and returning without updating
            // the state
            mPendingRequests.cancelAll();
            return;
        }
        if (mState == State.CONNECTED) {
            setState(State.DISCONNECTING);
            mMainThread.execute(new Runnable() {
                @Override
                public void run() {
                    disconnectOnMainThread();
                }
            });
        } else {
            // if we're still CONNECTING - skip DISCONNECTING state
            setState(State.DISCONNECTED);
        }
        // requests should be cancelled only when Billing#disconnect() is called explicitly as
        // it's only then we know for sure that no more work should be done
        mPendingRequests.cancelAll();
    }
}
複製程式碼

針對不同狀態做不同處理。當mState為CONNECTED時,通過mMainThread呼叫disconnectOnMainThread()。來看下這個方法。

private void disconnectOnMainThread() {
    Check.isMainThread();
    mConnector.disconnect();
}
複製程式碼

邏輯很簡單,通過mConnector斷開service連線。

4.2.15 Purchase

表示購買資訊的類

成員變數

  • String sku 表示商品項名稱

  • String orderId表示訂單識別符號

  • String packageName 應用包名

  • long time 購買的時間

  • String payload 一個開發人員指定的字串,該欄位在儲值的時候填入,在Google Play儲值完成後返回

  • String token

  • State state 購買的狀態,有PURCHASED,CANCELLED,REFUNDED,EXPIRED四個狀態

  • boolean autoRenewing是否自動更新訂閱。

  • String data 購買的原始資料

  • String signature 資料簽名

4.2.16 Purchases

表示購買資訊列表的類。維護了一個Purchase集合。

成員變數

  • String product 產品型別

  • List<Purchase> list購買過的商品列表

  • String continuationToken用於查詢更多產品的token

4.2.17 PurchaseFlow

表示從使用者請求購買之時起直到購買完成為止的一個購買流程的類,該類實現了CancellableRequestListener介面,重寫了onSuccess()回撥方法。

1.核心方法

@Override
public void onSuccess(@Nonnull PendingIntent purchaseIntent) {
    if (mListener == null) {
        // request was cancelled => stop here
        return;
    }
    try {
        mIntentStarter.startForResult(purchaseIntent.getIntentSender(), mRequestCode, new Intent());
    } catch (RuntimeException | IntentSender.SendIntentException e) {
        handleError(e);
    }
複製程式碼

當PurchaseRequest獲取到BuyIntent後,呼叫了RequestListener的onSuccess()並把purchaseIntent傳進來,啟動購買頁面。然後在activity的onActivityResult()執行購買結果流程,PurchaseFlow把這個流程封裝在本類中的onActivityResult()方法中

void onActivityResult(int requestCode, int resultCode, Intent intent) {
    try {
        Check.equals(mRequestCode, requestCode);
        if (intent == null) {
            // sometimes intent is null (it's not obvious when it happens but it happens from time to time)
            handleError(NULL_INTENT);
            return;
        }
        final int responseCode = intent.getIntExtra(EXTRA_RESPONSE, OK);
        if (resultCode != RESULT_OK || responseCode != OK) {
            handleError(responseCode);
            return;
        }
        final String data = intent.getStringExtra(EXTRA_PURCHASE_DATA);
        final String signature = intent.getStringExtra(EXTRA_PURCHASE_SIGNATURE);
        Check.isNotNull(data);
        Check.isNotNull(signature);

        final Purchase purchase = Purchase.fromJson(data, signature);
        mVerifier.verify(singletonList(purchase), new VerificationListener());
    } catch (RuntimeException | JSONException e) {
        handleError(e);
    }
}
複製程式碼

2.PurchaseFlow的流程

【移動開發】Checkout開源庫原始碼解析

4.2.18 Inventory

表示載入關於products,SKUs和purchases相關資訊的介面。

1.構造物件

這個類不能直接被例項化,需要通過呼叫Checkout的loadInventory()makeInventory()

@Nonnull
public Inventory loadInventory(@Nonnull Inventory.Request request, @Nonnull Inventory.Callback callback) {
    final Inventory inventory = makeInventory();
    inventory.load(request, callback);
    return inventory;
}

@Nonnull
public Inventory makeInventory() {
    Check.isMainThread();

    synchronized (mLock) {
        checkIsNotStopped();
    }

    final Inventory inventory;
    final Inventory fallbackInventory = mBilling.getConfiguration().getFallbackInventory(this, mOnLoadExecutor);
    if (fallbackInventory == null) {
        inventory = new CheckoutInventory(this);
    } else {
        inventory = new FallingBackInventory(this, fallbackInventory);
    }
    return inventory;
}
複製程式碼

可以看出loadInventory()又呼叫了makeInventory()Inventory的例項化是在makeInventory()中進行的。先獲取FallingBackInventory物件,如果不存在,則例項化CheckoutInventory物件。

2.主要方法

int load(@Nonnull Request request, @Nonnull Callback callback);//載入Products並且非同步傳遞到Callback中,這是核心方法。
void cancel();//取消所有載入任務
void cancel(int id);//根據id取消指定的任務。
boolean isLoading();//判斷是否至少有一個任務在載入中
複製程式碼

4.2.19 BaseInventory

BaseInventory實現了Inventory介面,作為基類。子類需要實現protected abstract Runnable createWorker(@Nonnull Task task);抽象方法。

1.主要成員變數

  • List<Task> mTasks 維護了一個Task列表,用於對任務的管理

  • Checkout mCheckout 持有Checkout引用。

2.核心方法

@Override
public int load(@Nonnull Request request, @Nonnull Callback callback) {
    synchronized (mLock) {
        final Task task = new Task(request, callback);
        mTasks.add(task);
        task.run();
        return task.mId;
    }
}
複製程式碼

可以看出load()根據request和callback例項化task物件,並新增到mTasks中,再執行task的run()

4.2.20 CheckoutInventory

BaseInventory的子類,用於載入購買流程的相關資訊,實現了BaseInventory的抽象方法

protected Runnable createWorker(@Nonnull Task task) {
    return new Worker(task);
}
複製程式碼

可見createWorker()方法返回了Worker物件,Worker是CheckoutInventory的內部類。

1.內部類Worker

Worker實現了Runnable介面和Checkout.Listener介面,作為CheckoutInventory的內部類,持有外部類引用,所以也就持有Checkout引用。run()方法呼叫了checkout的whenReady()方法.我們來看一下whenReady()中又呼叫了 Checkout.Listener回撥方法。我們看一下回撥方法的實現。

    @Override
    public void onReady(@Nonnull BillingRequests requests) {
    }

    @Override
    public void onReady(@Nonnull BillingRequests requests, @Nonnull String productId,
            boolean billingSupported) {
        final Product product = new Product(productId, billingSupported);
        synchronized (mLock) {
            countDown();
            mProducts.add(product);
            if (!mTask.isCancelled() && product.supported && mTask.getRequest().shouldLoadPurchases(productId)) {
                loadPurchases(requests, product);
            } else {
                countDown(1);
            }
            if (!mTask.isCancelled() && product.supported && mTask.getRequest().shouldLoadSkus(productId)) {
                loadSkus(requests, product);
            } else {
                countDown(1);
            }
        }
    }
複製程式碼

可以看出onReady()回撥方法判斷是否載入購買資訊或者載入SKU,分別呼叫了loadPurchases()loadSkus(),而兩個方法右分別呼叫了requests.getAllPurchases()requests.getSkus(),從而實現了獲取資訊的流程。

2.查詢資訊流程

我們通過時序圖來理清整個流程,這裡以獲取購買資訊為例

【移動開發】Checkout開源庫原始碼解析

4.2.21 FallingBackInventory

同樣的整合了BaseInventory,該類持有CheckoutInventory引用。表示如果其中一個產品不被支援,則庫存回退到後備庫存。

4.2.22 ProductTypes

Billing API中支援的Product型別,目前有IN_APP和SUBSCRIPTION兩種

4.2.23 Product

表示在Inventory中的一種Product,包含了purchase列表和SKUS列表(如果有的話),Product可根據ProductTypes分為IN_APP和SUBSCRIPTION。

1.成員變數

  • String id Product ID

  • boolean supportedproduct是否被支援

  • List<Purchase> mPurchases purchase列表

  • List<Sku> mSkus SKU列表

4.2.24 Products

表示Product的集合,維護了一個儲存Product的map。

4.2.25 MainThread

工具類,作用是確保Runnable在主執行緒執行

主要方法

@Override
public void execute(@Nonnull Runnable runnable) {
    if (MainThread.isMainThread()) {
        runnable.run();
    } else {
        mHandler.post(runnable);
    }
}
複製程式碼

5.總結

優點

  • Checkout為整個應用內結算算流程的邏輯進行封裝,提供了簡便的方法給開發者呼叫。整合的時候只需要在構建Billing例項時做簡單的配置,在生命週期內呼叫方法即可。
  • 做了快取處理,避免重複的跨程式請求。
  • 通過一個佇列維護請求順序,便於管理Request

缺點

  • Billing庫中做了很多併發處理,在方法中新增同步鎖,這一定程度上影響了程式效能。

本文首發在公眾號:三七互娛技術中心。歡迎關注~~

【移動開發】Checkout開源庫原始碼解析

相關文章