Brodastcast 与 Coroutine

1,259 阅读3分钟

这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战

Brodastcast 与 Coroutine

前言

Brodastcast 是Android 开发中非常常用的组件,作为四大组件之一,有着非常广泛的用途。一般来讲,业务代码存在的时间都比较长,有大量使用 Brodastcast的地方,在引入新的技术栈时怎么与现有的技术不冲突,是我们开发中需要考虑的问题。

RxJava

RxJava 是一个可以让我们非常方便的实现异步操作的库,基于响应式编程思想,经过几年的推广,其使用也非常广泛。在使用时怎么将广播事件简易的转换成Rx的事件流呢?

在使用RxJava 时,我们可以将 Brodastcast 进行简单的封装与RxJava 方便的结合起来,融入RxJava的生态,比如

class BroadcastReceiverObservable @JvmOverloads constructor(
        val action: String,
        val application: Application
) : Observable<Intent>() {
    override fun subscribeActual(observer: Observer<in Intent>) {
        val receiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
               observer.onNext(intent)
            }
        }
        val filter = IntentFilter()
        filter.addAction(action)
        LocalBroadcastManager.getInstance(application).registerReceiver(receiver, filter)
        observer.onSubscribe(BroadcastReceiverDisposable(application, receiver))
    }
}

class BroadcastReceiverDisposable(
    private val context: Context,
    private val broadcastReceiver: BroadcastReceiver
) : Disposable {
    private val disposed = AtomicBoolean(false)
    override fun isDisposed(): Boolean = disposed.get()
    override fun dispose() {
        if (!disposed.getAndSet(true)) {
            LocalBroadcastManager.getInstance(context).unregisterReceiver(broadcastReceiver)
        }
    }
}

我们实现了基于广播的Observable 及其对应的 Disposable,使用起来也比较方便。但是因为RxJava 的生命周期需要我们自己维护,因此我们一般需要在特定的时机调用Dispose#dispose来避免资源和内存的泄露问题,一般在OnDestroy 中手动取消订阅即可

val dispose = BroadcastReceiverObservable(ACTION,MyApp.get()).subscribe(...)
dispose.dispose()

Coroutine

近几年,Koltin 经过大力推广,已经渐渐成为Android 开发的主要语言,其协程的编程理念也渐渐让人熟悉,虽然Android 开发在一开始就缺少对协程理解的心智基础,导致Koltin Coroutine 上手难度较高,学习曲线非常陡峭,但是其异步模型非常适合客户端的业务场景,对于结构性并发,异步错误处理都有良好的支持,在实现复杂的交互场景时是非常有利的。

Coroutine天然是有生命周期的(使用GlobalScope的生命周期是全局),在使用了Koltin Coroutine,怎么将BrodastcastCoroutine结合,融入我们的业务代码呢。当然这里也有很多种实现方式:

如果仅仅需要监听一次广播,比如等待某个条件通知,我们可以一直挂起,这里可以使用简单的封装方式,使用挂起函数监听一次广播:

suspend fun waitSomeCondition(action: String): Intent = suspendCancellableCoroutine<Intent> { cont ->
 val receiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent) {
            cont.resume(intent)
            LocalBroadcastManager.getInstance(context).unregisterReceiver(this)
        }
    }
    LocalBroadcastManager.getInstance(context).registerReceiver(receiver, IntentFilter(action))
    //协程取消或正常结束需要取消注册广播
    coroutineContext[Job]?.invokeOnCompletion {
 LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver)
    }
 }

lifeScope.launch{
    val passed = waitSomeCondition(ACTION).getBooleanExtra(ARG,false)
}

有些场景,我们可能和Rxjav 中一样需要将广播转换为事件流,这时候我们可以将广播转换成Flow,这里可以使用Channel,当然更推荐的是官方推荐的callbackFlow

比如使用callbackFlow 将广播转换为flow 事件流,注意这里的Flow 是冷流,意味着每一次订阅都会有一个新的被观察者创建,如果想把Flow 转换为热流,可以了解其stateIn 或shareIn 操作符将其转换为MultiShareFlow。

val actionFlow = callbackFlow<Intent> {
 val receiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent) {
            offer(intent)
            //当然使用callbackFlow 封装单次广播也是可以的,只需要在接收到广播后调用close就好
            //close()
        }
    } 
    LocalBroadcastManager.getInstance(context).registerReceiver(receiver, IntentFilter(action))
    //取消注册
    invokeOnClose {
 LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver)
    }
 }
lifeScope.launch{
    actionFlow.collect { intent ->
    //....
}
}

总结

需要注意的是我们注册的广播是在挂起函数里,与RxJava同步注册不同,只有当我们的这个挂起函数执行了,我们的广播才会注册,然后才能能正常接收事件,这点在使用上需要特别注意,以防漏掉广播事件。当然优点也是有的,我们不需要手动取消广播的注册,协程的生命周期结束,广播就会自动取消注册。

Happy ending.