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

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-checkoutjava

1.2Checkout解決的問題

  • Activity被銷燬時如何取消全部的billing請求?
  • 如何在後臺查詢購買信息?
  • 如何驗證購買?
  • 如何使用continuationToken來加載用戶已購買的商品項以及商品項的信息[接口限制每次請求只會返回20個商品項]
  • 如何使用最少示例代碼增長儲值功能?

1.3結算流程

2.整體設計

2.1整體設計圖

2.2核心類的概念

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

  • Billing Service的鏈接創建和斷開
  • 執行結算請求
  • 緩存請求結果
  • 建立Checkout對象

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

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

Checkout: Billing類的幫助類,維護了Billing實例,用於主線程中,生命週期需與activity/fragment/service綁定,對應的子類有FragmentCheckout,ActivityCheckout和CustomUiCheckout等。api

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

Configuration: 表示Billing結算的配置接口,須要實現Configuration接口自定義配置。安全

Cache: 表示緩存的接口,具體實現類爲MapCache。服務器

ServiceConnector: 鏈接服務的接口,默認實現類爲DefaultServiceConnector,負責Google play app的綁定和解綁。併發

Purchase: 表示購買信息的類,成員變量與getBuyIntent()返回的INAPP_DATA_SIGNATURE數據的 JSON 字段對應,也就是說Purchase都是根據這個JSON字段的內容定義的。app

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

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

PurchaseVerifier: 驗證購買接口,實現類爲BasePurchaseVerifier,該類爲抽象類,可繼承它實現本身的驗證類。驗證過程一般在後臺服務器進行。

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

3.request流程圖

4.詳細設計

4.1UML類關係圖

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.使用流程

在分析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的轉換方式須要按照下圖:

經過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執行過程當中的調用棧

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

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的流程

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.查詢信息流程

咱們經過時序圖來理清整個流程,這裏以獲取購買信息爲例

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庫中作了不少併發處理,在方法中添加同步鎖,這必定程度上影響了程序性能。

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

相關文章
相關標籤/搜索