史上最详Android版kotlin协程入门进阶实战(四)

kotlin协程在Android中的基础应用

通过前面的三个章节,现在我们已经了解了kotlin协程的基本使用和相关基础知识点。如:

  1. 协程的基础使用方式和基本原理。
  2. CoroutineContext:协程上下文中包含的Element以及下上文的作用,传递。
  3. CoroutineDispatcher:协程调度器的使用
  4. CoroutineStart:协程启动模式在不同模式下的区别
  5. CoroutineScope:协程作用域的分类,以及不同作用域下的异常处理。
  6. 挂起函数以及suspend关键字的作用,以及Continuation的挂起恢复流程。
  7. CoroutineExceptionHandler:协程异常处理,结合supervisorScopeSupervisorJob的使用。

这一章节中,我们将主要讲解kotlin协程在Android中的基础使用。我们先引入相关扩展库组件库:

    implementation "androidx.activity:activity-ktx:1.2.2"
    implementation "androidx.fragment:fragment-ktx:1.3.3"

Android使用kotlin协程

我们在之前的章节中使用协程的方式都是通过runBlocking或者使用GlobalScopelaunchasync方式启动,当然也可以通过创建一个新的CoroutineScope,然后通过launch或者async方式启动一个新的协程。我们在讲解协程异常处理的篇章中就提到,通过SupervisorJobCoroutineExceptionHandler实现了一个和supervisorScope相同的作用域。

private fun testException(){
    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        Log.d("exceptionHandler", "${coroutineContext[CoroutineName].toString()} 处理异常 :$throwable")
    }
    val supervisorScope = CoroutineScope(SupervisorJob() + exceptionHandler)
    with(supervisorScope) {
        launch{
        }
        //省略...
    }
}

在第一节中我们提到runBlocking它会将常规的阻塞代码连接到一起,主要用于main函数和测试中。而GlobalScope又是一个全局顶级协程,我们在之前的案例中基本都使用这种方式。但是这个协程是在整个应用程序生命周期内运行的,如果我们用GlobalScope启动协程,我们启动一个将会变得极其繁琐,而且需要对于各种引用的处理以及管控异常取消操作。

我们可以先忽略CoroutineExceptionHandler协程异常处理。因为不管是任何方式启动协程,如果不在程上下文中添加CoroutineExceptionHandler,当产生未捕获的异常时都会导致应用崩溃。

那么下面代码会出现什么问题?

private fun start() {
    GlobalScope.launch{
        launch {
            //网络请求1...
            throw  NullPointerException("空指针")
        }
        val result = withContext(Dispatchers.IO) {
            //网络请求2...
            requestData()
            "请求结果"
        }
         btn.text = result
        launch {
            //网络请求3...
        }
    }
}
  • 因为我们的GlobalScope默认使用的是Dispatchers.Default,这会导致我们在非主线程上刷新UI。

  • 子协程产生异常会产生相互干扰。子协程异常取消会导致父协程取消,同时其他子协程也将会被取消。

  • 如果我们这个时候activity或者framgent退出,因为协程是在GlobalScope中运行,所以即使activity或者framgent退出,这个协程还是在运行,这个时候会产生各种泄露问题。同时此协程当执行到刷新操作时,因为我们的界面已经销毁,这个时候执行UI刷新将会产生崩溃。

如果我们要解决上面的问题。我们得这么做:

var job:Job? = null
private fun start() {
    job = GlobalScope.launch(Dispatchers.Main + SupervisorJob()) {
        launch {
            throw  NullPointerException("空指针")
        }
        val result = withContext(Dispatchers.IO) {
            //网络请求...
            "请求结果"
        }
        launch {
            //网络请求3...
        }
        btn.text = result
    }
}

override fun onDestroy() {
    super.onDestroy()
    job?.cancel()
}

我们先需要通过launch启动时加入Dispatchers.Main来保证我们是在主线程刷新UI,同时还需要再GlobalScope.launch的协程上下文中加入SupervisorJob来避免子协程的异常取消会导致整个协程树被终结。 最后我们还得把每次通过GlobalScope启动的Job保存下来,在activity或者framgent退出时调用job.cancel取消整个协程树。这么来一遍感觉还行,但是我们不是写一次啊,每次写的时候会不会感觉超麻烦,甚至怀疑人生。

所以官方在kotlin协程中提供了一个默认在主线程运行的协程:MainScope,我们可以通过它来启动协。

public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

我们可以看到MainScope的创建默认就使用了SupervisorJobDispatchers.Main。说明我们可以通过MainScope来处理UI组件刷新。同时由于MainScope采用的是SupervisorJob,所以我们各个子协程中的异常导致的取消操作并不会导致MainScope的取消。这就很好的简化了我们通过GlobalScope去启动一个协程的过程。

private val mainScope = MainScope()
private fun start() {
    mainScope.launch {
        launch {
            throw  NullPointerException("空指针")
        }
        val result = withContext(Dispatchers.IO) {
            //网络请求...
            "请求结果"
        }
        launch {
            //网络请求3...
        }
        btn.text = result
    }
} 
override fun onDestroy() {
    super.onDestroy()
    mainScope.cancel()
}

通过使用MainScope我们是不是省略了很多操作。同时我们也不需要保存每一个通过MainScope启动的Job了,直接在最后销毁的时候调用mainScope.cancel()就能取消所有通过mainScope启动的协程。

这里多提一点:可能这里有的人会想,我使用GlobalScope也不保存启动的Job,直接GlobalScope.cancel不行吗?如果是这样的话,那么恭喜你喜提超级崩溃BUG一个。这里就不扩展了。可以自己动手去试试,毕竟实践出真理。

那可能还有人想,我连创建MainScope都懒得写,而且脑子经常不好使,容易忘记调用mainScope进行cancel操作怎么办。

image.png

官方早就为我们这些懒人想好了解决方案,这个时候我们只需要再集成一个ktx运行库就可以了。

在Activity与Framgent中使用协程

    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"

这个时候我们就可以在activity或者framgent直接使用lifecycleScope进行启动

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值