最近,Android宣布彻底废弃AsyncTask
,推荐Coroutine
作为首选的异步编程方案。
如果说AsyncTask被Coroutine替代毫无悬念,那RxJava
与Coroutine如何取舍呢?
J神很早就就给出自己的看法了。当时Flow还未出现,现在J神恐怕连Observable
、Subject
也早就不用了。 RxJava在Android中被废弃的日子恐怕也不远了。
那么,一个既有的RxJava的MVVM项目如何简单地改造为Coroutine呢?
以一个RxJava项目为例,Model层基于Single进行数据请求
class Api {
fun fetchPersons(): Single<List<Person>> = ...
}
class ViewModel(
private val api: Api = Api(),
private val scheduler: Scheduler = Schedulers.io()
) {
private val mutablePersons = MutableLiveData<List<Person>>()
val persons: LiveData<List<Person>> = mutablePersons
fun onCreate() {
api.fetchPersons()
.subscribeOn(scheduler)
.subscribe(mutablePersons::postValue)
}
}
kotlinx-coroutines-rx2 是个好东西,可以帮助我们在Rx2与Coroutine之间任意切换。
除了Rx2以外,Rx3等其他Reactive库也有支持https://github.com/Kotlin/kotlinx.coroutines/tree/master/reactive
使用SingleSource.await
,可以将上面例子中Model层的Single转换为suspend函数
class ViewModel(
private val api: Api = Api(),
private val coroutineContext: CoroutineContext = CommonPool
) {
private val mutablePersons = MutableLiveData<List<Person>>()
val persons: LiveData<List<Person>> = mutablePersons
fun onCreate() {
launch(coroutineContext) {
//将Rx请求转换挂起函数
val persons = api.fetchPersons().await()
mutablePersons.postValue(persons)
}
}
}
虽然例子中几乎毫不费力地将Rx转换为了Coroutine,但若要用在实际项目中,可能还要稍微加工一下:
class PersonsViewModel(
private val api: Api = Api(),
coroutineContext: CoroutineContext = CommonPool
) : ViewModel() {
private val compositeJob = Job() //1
private val mutablePersons = MutableLiveData<List<Person>>()
val persons: LiveData<List<Person>> = mutablePersons
init {
launch(coroutineContext, parent = compositeJob) {
try { //2
val persons = api.fetchPersons().await()
mutablePersons.postValue(persons)
} catch (e: Exception) {
...
}
}
}
override fun onCleared() {
compositeJob.cancel()
}
onCleared
时统一cancel
,避免泄露。当然这个工作现在可以通过ViewModel
的KTX扩展来做launch
中通过try...catch...
替代Rx的onError捕获异常,关于Coroutine的异常捕获的更多内容可参考Kotlin Coroutine 最佳实践使用Scheduler.asCoroutineDispatcher可以将RxJava的Schedulers
转为Dispatcher
,让重构前后的线程池不变,保证一致性。
object AppCoroutineDispatchers(
val io: CoroutineContext = Schedulers.io().asCoroutineDispatcher(),
val computation: CoroutineContext = Schedulers.computation().asCoroutineDispatcher(),
val ui: CoroutineContext = UI
)
//use
launch(AppCoroutineDispatchers.io) { ... }
虽然Android更推荐使用Coroutine,但是kotlinx-coroutines-rx2 还是很友好地提供了Coroutine转Rx2的API,所以顺道看一下如何进行反向操作:
class Api {
suspend fun fetchPersons(): List<Person> = ...
}
class ViewModel(
private val api: Api = Api(),
private val scheduler: Scheduler = Schedulers.io(),
val coroutineContext: CoroutineContext = CommonPool
) {
private val mutablePersons = MutableLiveData<List<Person>>()
val persons: LiveData<List<Person>> = mutablePersons
fun onCreate() {
rxSingle(coroutineContext) { api.fetchPersons() }
.subscribeOn(scheduler)
.subscribe(mutablePersons::postValue)
}
}