Kotlin协程实现Android MVP/MVVM

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<Int> = GlobalScope.async {
    // this: CoroutineScope
    return@async 10
}

返回值Deferred<Int>也是一个Job,调用await或者join可以将异步再转为同步,等待任务的完成。

在实现MVP/MVVM框架时,我们会根据需求,选择协程的同步或者异步的实现方案。

实现MVP

Model

在Model层,数据请求多事异步任务,所以通过async进行数据请求,返回Deferred的结果

class Model : ModelScope {
    fun getUsers(): Deferred<List<User>> = async {
        //...
    }
}

Presenter

传统的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<User>): Deferred<List<UserModel>> = async {
        ...
    }
}

View

在View层,我们通过launch启动协程,此处不建议通过runBlocking启动协程,因为这样会阻塞UI线程效率比较低。调用Presenter的suspend方法(也可以同过IPresent的接口调用),所以需要提供launch所需的CoroutineScope,我们让Activity实现CoroutineScope,使用UI线程的Dispatcher创建CoroutineContext:

interface UsersView {
    var users: List<UserViewModel>
}

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

Model

MVVM和MVP的Model层没有太大区别,同样使用async/await进行数据请求。代码略

ViewModel

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中同样要进行后处理以避免泄露。

View

通过DataBinding在xml中调用ViewModel的方法

<Button android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="@{() -> viewModel.onGetButtonClick()}" />

在lambda内调用onGetButtonClick即可,此时无需理会返回的Job

总结

本文给了一个基于协程实现MVP/MVVM的基本思路,要真正用在项目上可能要进一步打磨。但通过实例已经不难发现,协程相对于RxJava等传统方式有不少优点:

  • 代码更简洁,可读性更高。
  • 经测试,协程相对RxJava在运行时会创建更少的中间对象,性能开销更小;
  • RxJava的特有优势(丰富操作符适合处理stream式数据)也已经有了替代方案(flow)
  • 未来Jetpack对Kotlin协程的支持会越来越多,例如ViewModel等支持协程

在拥抱Kotlin的大背景下,建议大家多尝试使用Kotlin+协程,享受上述好处的同时,提高项目的技术先进性。

发布了11 篇原创文章 · 获赞 1 · 访问量 269

猜你喜欢

转载自blog.csdn.net/vitaviva/article/details/104102519