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 loadingProducts = new HashSet<>(ProductTypes.ALL);
for (final String product : ProductTypes.ALL) {
requests.isBillingSupported(product, new RequestListener
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
请求的回调接口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
对象,实现了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);
}
int runWhenConnected(@Nonnull Request request, @Nullable RequestListener 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
购买过的商品列表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 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 List
SKU列表mSkus
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库中做了很多并发处理,在方法中添加同步锁,这一定程度上影响了程序性能。