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结算流程
2.总体设计
2.1总体设计图
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流程图
4.详细设计
4.1UML类关系图
4.2核心类解析
4.2.1 Checkout.java
Checkout是一个工具类,主要是对Billing结算流程的一个封装和对Inventory
的处理。根据Context
环境的不同,构建一个Checkout类的非抽象子类(FragmentCheckout
、ActivityCheckout
、CustomUiCheckout
)对象,启动结算流程。
注意: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请求的实体类,该类为抽象类,具体实现类有BillingSupportedRequest
,GetSkuDetailsRequest
,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
作为请求独一无二的idRequestType 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
,并执行RequestRunnable
的run()
方法。
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
进行包装,得到其mPublicKey
和mPurchaseVerifier
的引用。StaticConfiguration
实现了Configuration
的方法。一般情况下,我们需要实现自己的Configuration
1.成员变量
Configuration mOriginal
原始的ConfigurationString 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
,
FAILED
6个状态。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()
方法主要是设置state
为CONNECTING
,并通过mMainThread
调用了connectOnMainThread()
方法,该方法又调用了mConnector
的connect()
方法,并返回mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
的结果。
需要注意的是,每次执行请求流程时,connect()都会被调用,确保服务是连接上的。
5.执行request
当建立起连接后,state被置为CONNECTED,并调用executePendingRequests()
方法,该方法通过一个单线程的线程池,执行mPendingRequests
的run()
方法,循环的取出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 IDboolean supported
product是否被支持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库中做了很多并发处理,在方法中添加同步锁,这一定程度上影响了程序性能。