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

1.2Checkout解决的问题

  • Activity被销毁时如何取消所有的billing请求?
  • 如何在后台查询购买信息?
  • 如何验证购买?
  • 如何使用continuationToken来加载用户已购买的商品项以及商品项的信息[接口限制每次请求只会返回20个商品项]
  • 如何使用最少示例代码增加储值功能?

1.3结算流程

iab_v3_purchase_flow.png

2.总体设计

2.1总体设计图

Checkout总体设计图.png

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

未命名文件.png

4.详细设计

4.1UML类关系图

Checkout UML图.png

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

在分析Billing类之前,我们先分析Billing中几个成员变量对应的类。

4.2.2 Request.java

表示Billing请求的实体类,该类为抽象类,具体实现类有BillingSupportedRequestGetSkuDetailsRequest
ConsumePurchaseRequest等,子类需要实现抽象方法

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的转换方式需要按照下图:

State切换流程.png

通过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执行过程中的调用栈

我们来了解一下一个请求执行的过程,以获取购买的商品为例

请求购买流程图.png

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

Purchase购买流程.png

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

我们通过时序图来理清整个流程,这里以获取购买信息为例

UML时序图.png

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库中做了很多并发处理,在方法中添加同步锁,这一定程度上影响了程序性能。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容