这是我参与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
,怎么将Brodastcast
与Coroutine
结合,融入我们的业务代码呢。当然这里也有很多种实现方式:
如果仅仅需要监听一次广播,比如等待某个条件通知,我们可以一直挂起,这里可以使用简单的封装方式,使用挂起函数监听一次广播:
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.