在 Android 开发中,我们经常需要小心处理各种 SDK 的初始化和回调,尤其是在需要异步操作时。本文我们将聊聊 Kotlin 中处理这些回调的四种优化方式,主要以 Google 的几个 SDK 为例。
首先,回顾一下传统的回调机制。在 Java 中,回调函数是处理异步任务结果的常用方式,但这种方法有不少问题:
在 Kotlin 中,虽然有协程等强大的工具,但由于需要兼容大量的 Java 代码和历史项目,回调问题仍然存在。以 Google 提供的 SDK 为例,尽管有些已经提供 KTX 扩展包,但并没有在初始化上的问题上进行优化。
AtomicBoolean
简单记录状态最直接的方法是使用 AtomicBoolean
来记录 SDK 是否已经初始化成功。
private val sdkInitialized = AtomicBoolean(false)
MobileAds.initialize(context) { result: InitializationStatus ->
// 检查所有的适配器是否已准备就绪
sdkInitialized.set(result.adapterStatusMap.values.any {
it.initializationState == AdapterStatus.State.READY
})
}
问题在于:这种方式只能在调用 SDK 其他方法时,手动检查 sdkInitialized
,无法动态地等待初始化完成。
示例:
fun prepareNextRewardedAd() {
if (sdkInitialized.get()) {
// 正常初始化
} else {
// 错误提示
}
}
优点:
缺点:
CompletableDeferred
动态等待CompletableDeferred
可以让我们在协程中挂起,直到任务完成,非常适合等待 SDK 初始化的场景。
private val isSDKInitialized = CompletableDeferred<Unit>()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
// 初始化成功
} else {
// 初始化失败
}
// 无论成功与否,都标记为完成,避免协程一直挂起
isSDKInitialized.complete(Unit)
}
override fun onBillingServiceDisconnected() {
// 可以尝试重新连接
}
})
使用方式:
suspend fun queryMerchandise(...) = withContext(Dispatchers.IO) {
// 等待初始化完成
isSDKInitialized.await()
billingClient.queryProductDetails(...)
}
优点:
缺点:
Channel
处理生产者消费者模式当我们只需要处理单一的“生产者消费者”回调结果时,Channel
可能是个不错的选择。下面以加载 AdMob 激励广告为例。
private val rewardedAdChannel = Channel<RewardedAd?>(1)
...
RewardedAd.load(
activity,
adUnitId,
adRequest,
object : RewardedAdLoadCallback() {
override fun onAdLoaded(ad: RewardedAd) {
rewardedAdChannel.trySend(ad).isSuccess
}
override fun onAdFailedToLoad(adError: LoadAdError) {
rewardedAdChannel.trySend(null).isSuccess
}
}
)
使用方式:
suspend fun showSingleRewardedAd(activity: Activity) {
// 等待广告加载完成
val ad = rewardedAdChannel.receive()
if (ad != null) {
ad.show(activity) { rewardItem ->
// 处理奖励逻辑
}
} else {
// 处理加载失败的情况
}
}
优点:
缺点:
Channel
的生命周期,防止内存泄漏。Channel
可能会阻塞,需要设置合适的容量或处理超时。SharedFlow
处理多次重现的事件当我们需要处理多次初始化或状态更新时,SharedFlow
非常适合。
示例:假设我们有一个需要多次监听的网络状态变化
private val networkStatusFlow = MutableSharedFlow<NetworkStatus>(replay = 1)
fun startNetworkMonitoring() {
networkMonitor.setOnNetworkStatusChangedListener { status ->
networkStatusFlow.tryEmit(status)
}
}
使用方式:
fun observeNetworkStatus() {
lifecycleScope.launch {
networkStatusFlow.collect { status ->
when (status) {
NetworkStatus.Available -> // 网络可用
NetworkStatus.Unavailable -> // 网络不可用
}
}
}
}
优点:
缺点:
根据不同的需求,我们可以选择不同的方式来优化回调处理:
AtomicBoolean
,适用于简单的初始化状态记录。CompletableDeferred
,适用于需要在协程中等待初始化完成的情况。Channel
,适用于一次性结果的回调,如广告加载。SharedFlow
,适用于需要监听多次状态更新的场景。希望这篇文章能给大家带来一些处理 SDK 初始化回调的思路,未来碰到类似问题时可尝试选择最适合的方式,提高代码的可读性和维护性。