Kotlin已经正式成为Android的官方开发语言,越来越多的Andorid框架/三方库开始用Kotlin实现。我也尝试通过Kotlin的协程来实现Android常见的MVP/MVVP架构,希望能将Kotlin更好地融入日常开发中。
使用协程,可以方便地实现各种同步或者异步逻辑。
通过suspend函数,我们可以在不阻塞线程的情况下,实现线程间的同步
suspend fun hoge(): Int {
// this: CoroutineScope
return 10
}
当需要实现异步任务时,可以使用async启动一个子协程 (Kotlin协程介绍(四)创建协程 async/await)
fun hoge(): Deferred = GlobalScope.async {
// this: CoroutineScope
return@async 10
}
返回值Deferred
在实现MVP/MVVM框架时,我们会根据需求,选择协程的同步或者异步的实现方案。
在Model层,数据请求多事异步任务,所以通过async进行数据请求,返回Deferred的结果
class Model : ModelScope {
fun getUsers(): Deferred> = async {
//...
}
}
传统的MVP中,View调用Presenter的方法进行数据请求,Presenter获取请求结果后回调View。Presenter和View之间的相互调用都是在UI线程完成,此时通过suspend函数可以轻松实现上述逻辑,suspend函数被View调用时的CoroutineContext是UI,所以model层任务完成后会自动在UI线程回复协程的执行:
class Presenter {
suspend fun onGetButtonClick() {
val users = model.getUsers().await()
val userViewModels = convert(users).await()
view.users = users
}
private fun convert(users: List): Deferred> = async {
...
}
}
在View层,我们通过launch启动协程,此处不建议通过runBlocking启动协程,因为这样会阻塞UI线程效率比较低。调用Presenter的suspend方法(也可以同过IPresent的接口调用),所以需要提供launch所需的CoroutineScope,我们让Activity实现CoroutineScope,使用UI线程的Dispatcher创建CoroutineContext:
interface UsersView {
var users: List
}
class UsersActivity : AppCompatActivity(), UsersView,CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatcher.Main
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//...
getButton.setOnClickListener {
launch { presenter.onGetButtonClick() }
}
}
}
layout部分可以用Anko实现,代码更加简洁
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
verticalLayout {
button {
onClick { presenter.onGetButtonClick() }
}.lparams(width = matchParent)
}
}
借助协程,View层不必再实现来自Presenter的回调,代码看起来相当简洁。但千万不要忘了协程是挂起运行的,当Model层切换的异步线程执行数据请求时,UI线程是不会阻塞的,如果这是Activity执行了onDestroy,很容易造成内存泄漏问题,所以我们需要在onDestroy中取消所有执行中的协程,我们可以为CoroutineScope设置一个根Job,然后在onDestroy的时候通过根Job取消所有子协程的执行
class UsersActivity : AppCompatActivity(), UsersView,CoroutineScope {
private val viewJob = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = Dispatcher.Main + viewModleJob
override fun onDestroy() {
super.onDestroy()
viewJob.cancel
}
}
MVVM和MVP的Model层没有太大区别,同样使用async/await进行数据请求。代码略
MVVM框架基于Databinding实现,所以ViewModel的方法可能会从XML中调用。此时不能寄希望像MVP一样在View层构建CoroutineScope,所以我们在ViewModel层实现CoroutineScope
class ViewModel : CoroutineScope {
private val viewModelJob = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = Dispatcher.Main + viewModleJob
fun onGetButtonClick(): Job = launch{
...
}
override fun onCleared (
super.onCleared()
viewModelJob.cancel()
)
}
注意此处onGetButtonClickJob使用launch不适用runBlocking,一是为性能考虑,二是launch返回的Job.join可以方便实现同步逻辑,这对于编写单元测试时十分有利。另外,在ViewModel的onCleared中同样要进行后处理以避免泄露。
通过DataBinding在xml中调用ViewModel的方法
在lambda内调用onGetButtonClick即可,此时无需理会返回的Job
本文给了一个基于协程实现MVP/MVVM的基本思路,要真正用在项目上可能要进一步打磨。但通过实例已经不难发现,协程相对于RxJava等传统方式有不少优点:
在拥抱Kotlin的大背景下,建议大家多尝试使用Kotlin+协程,享受上述好处的同时,提高项目的技术先进性。