【移动开发】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结算流程
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;@Nonnullfinal 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);}}@Overridepublic void onSuccess(@Nonnull Object result) {onBillingSupported(true);}@Overridepublic 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.核心方法
@Overridepublic boolean run() {final Request localRequest = getRequest();if (localRequest == null) {// request was cancelled => finish herereturn true;}if (checkCache(localRequest)) return true;// request is alive, let's check the service statefinal State localState;final IInAppBillingService localService;synchronized (mLock) {localState = mState;localService = mService;}if (localState == State.CONNECTED) {Check.isNotNull(localService);// service is connected, let's start requesttry {localRequest.start(localService, mContext.getPackageName());} catch (RemoteException | RuntimeException | RequestException e) {localRequest.onError(e);}} else {// service is not connected, let's check whyif (localState != State.FAILED) {// service was disconnectedconnect();return false;} else {// service was not connected in the first place => can't do anything, aborting the requestlocalRequest.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() {@Overridepublic void onServiceDisconnected(ComponentName name) {setService(null, false);}@Overridepublic void onServiceConnected(ComponentName name,IBinder service) {setService(IInAppBillingService.Stub.asInterface(service), true);}}; 复制代码
2.实现方法
@Overridepublic 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 appreturn false;}}@Overridepublic 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->FAILEDmPlayStoreBroadcastReceiver.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 statemPlayStoreBroadcastReceiver.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 CONNECTEDCheck.isTrue(!mPlayStoreBroadcastReceiver.contains(mPlayStoreListener), "Leaking the listener");mMainThread.execute(new Runnable() {@Overridepublic 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() {@Overridepublic 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 statemPendingRequests.cancelAll();return;}if (mState == State.CONNECTED) {setState(State.DISCONNECTING);mMainThread.execute(new Runnable() {@Overridepublic void run() {disconnectOnMainThread();}});} else {// if we're still CONNECTING - skip DISCONNECTING statesetState(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 donemPendingRequests.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 herereturn;}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
回调方法。我们看一下回调方法的实现。
@Overridepublic void onReady(@Nonnull BillingRequests requests) {}@Overridepublic 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库中做了很多并发处理,在方法中添加同步锁,这一定程度上影响了程序性能。
本文首发在公众号:三七互娱技术中心。欢迎关注~~
转载于:https://juejin.im/post/5a4f1be76fb9a01ca9153eb3
【移动开发】Checkout开源库源码解析相关推荐
- linux epoll 开发指南-【ffrpc源码解析】
linux epoll 开发指南-[ffrpc源码解析] 摘要 关于epoll的问题很早就像写文章讲讲自己的看法,但是由于ffrpc一直没有完工,所以也就拖下来了.Epoll主要在服务器编程中使用,本 ...
- SEAL开源库源码02
SEAL开源库源码02 本篇的最终目的是要分析 seal/modulus.h 文章目录 SEAL开源库源码02 seal/version.h seal/util/hestdparms.h 128-bi ...
- SEAL开源库源码10
SEAL开源库源码10 文章目录 SEAL开源库源码10 seal/evaluator.h Evaluator 类 构造函数,利用 context 来构造 negate_inplace 和 negat ...
- SEAL开源库源码12
SEAL开源库源码12 文章目录 SEAL开源库源码12 5_ckks_basics.cpp example_ckks_basics 函数 6_rotation.cpp example_rotatio ...
- SEAL开源库源码01
SEAL开源库源码01 文章目录 SEAL开源库源码01 seal/util/defines.h 通用的判断函数 将x输出为字符串 字符串的拼接 检查double类型是否是64比特 检查int是否为3 ...
- PyCrypto密码学库源码解析(二)RSA参数生成
Python Crypto库源码解析(二) RSA参数生成 * 版权声明 * 引用请注明出处,转载请联系: h0.1c@foxmail.com 本文主要讲解pycrypto库中RSA参数生成的实现方法 ...
- HEAAN开源库源码(一)
HEAAN开源库源码 文章目录 HEAAN开源库源码 HEAAN.h HEAAN.cpp TimeUtils.h TimeUtils.cpp TestScheme.h TestScheme.cpp S ...
- hox 状态管理库源码解析
文章目录 hox是什么 hox实现状态共享的方式 基本使用 全局状态共享 局部状态共享 源码解析 index.js 入口文件 coantainer.tsx 管理每个hook 全局状态共享的实现 Hox ...
- Android 常用开源框架源码解析 系列 (四)Glide
一.定义 Glide 一个被google所推荐的图片加载库,作者是bumptech.对Android SDk 最低要求是 API 10 与之功能类似的是Square公司的picasso 二.基本 ...
最新文章
- python使用matplotlib可视化线图(line plot)、并自定义线条的粗细(线条的宽度、 line width in Matplotlib)
- 复习es6-let和const
- OpenStack镜像制作笔记 --以windows8.1-amd64为例
- JSF Struts Spring Hibernate 整合
- ubuntu14.04的键盘失灵解决方案
- 打印更无缝:微软改善Win11中通用打印体验
- Android平台RTMP多实例推送的几种情况探讨
- 无处不在的xss攻击
- css索引_CSS中的Z索引:它是什么以及它做什么
- Python项目实战:带领你爬取sexy的福利图片
- python内置函数__init__及__str__的区别
- gfdgdfgdfg
- cocos2d-x打飞机实例总结
- android源码定制之初探--定制android关机界面
- C1认证学习二十六(基础选择器)
- 音频服务器未能正常启动,Windows10系统音频服务未启动问题彻底解决方法
- JPEG 标准推荐的亮度、色度DC、AC Huffman 编码表
- 小学生也能看懂的海伦公式推导
- 不正方形(四个点构成一个凸四边形)
- 智能优化算法——哈里鹰算法(Matlab实现)
热门文章
- 图扑软件数字孪生海上风电 | 向海图强,奋楫争先
- 达芬奇发明计算机读后感200,三年级读后感:读《达芬奇》有感200字
- 我们学校的计算机房英语作文,精选我的学校英语作文6篇
- OSChina 周六乱弹 ——所以会魔法的人都是孤独的
- 发那科机器人请关闭电源_FANUC机器人常见错误恢复步骤,你真的都会吗?
- error怎么开机 fan_电脑开机提示cpu fan error怎么解决
- 异常被 ”吃“ 掉导致事务无法回滚
- 不运动也能增肌???打一针冬眠黑熊的血清就行
- php 5.3.29 nts,为什么PHP 5.3不在支持ISAPI,为什么还要分NTS和TS版本?
- 去除数组中指定元素的方法