由于文章涉及到的只是点比较多、内容可能过长,可以根据自己的能力水平和熟悉程度分阶段跳着看。如有讲述的不正确的地方劳烦各位私信给笔者,万分感谢
由于时间原因,笔者白天工作只有晚上空闲时间才能写作,所以更新频率应该在一周一篇,当然我也会尽量的利用时间,争取能够提前发布。为了方便阅读将本文章拆分个多个章节,根据自己需要选择对应的章节,现在也只是目前笔者心里的一个大概目录,最终以更新为准:
Kotlin
协程基础及原理系列
- 史上最详Android版kotlin协程入门进阶实战(一) -> kotlin协程的基础用法
- 史上最详Android版kotlin协程入门进阶实战(二) -> kotlin协程的关键知识点初步讲解
- 史上最详Android版kotlin协程入门进阶实战(三) -> kotlin协程的异常处理
- 史上最详Android版kotlin协程入门进阶实战(四) -> 使用kotlin协程开发Android的应用
- 史上最详Android版kotlin协程入门进阶实战(五) -> kotlin协程的网络请求封装
Flow
系列
扩展系列
kotlin协程在Android中的基础应用
通过前面的三个章节,现在我们已经了解了kotlin
协程的基本使用和相关基础知识点。如:
- 协程的基础使用方式和基本原理。
CoroutineContext
:协程上下文中包含的Element
以及下上文的作用,传递。CoroutineDispatcher
:协程调度器的使用CoroutineStart
:协程启动模式在不同模式下的区别CoroutineScope
:协程作用域的分类,以及不同作用域下的异常处理。- 挂起函数以及
suspend
关键字的作用,以及Continuation
的挂起恢复流程。 CoroutineExceptionHandler
:协程异常处理,结合supervisorScope
和SupervisorJob
的使用。
这一章节中,我们将主要讲解kotlin协程在Android中的基础使用。我们先引入相关扩展库组件库:
1 | kotlin复制代码 implementation "androidx.activity:activity-ktx:1.2.2" |
Android使用kotlin协程
我们在之前的章节中使用协程的方式都是通过runBlocking
或者使用GlobalScope
的launch
、async
方式启动,当然也可以通过创建一个新的CoroutineScope
,然后通过launch
或者async
方式启动一个新的协程。我们在讲解协程异常处理
的篇章中就提到,通过SupervisorJob
和CoroutineExceptionHandler
实现了一个和supervisorScope
相同的作用域。
1 | kotlin复制代码private fun testException(){ |
在第一节中我们提到runBlocking
它会将常规的阻塞代码连接到一起,主要用于main函数和测试中。而GlobalScope
又是一个全局顶级协程,我们在之前的案例中基本都使用这种方式。但是这个协程是在整个应用程序生命周期内运行的,如果我们用GlobalScope
启动协程,我们启动一个将会变得极其繁琐,而且需要对于各种引用的处理以及管控异常取消操作。
我们可以先忽略CoroutineExceptionHandler
协程异常处理。因为不管是任何方式启动协程,如果不在程上下文中添加CoroutineExceptionHandler
,当产生未捕获的异常时都会导致应用崩溃。
那么下面代码会出现什么问题?
1 | kotlin复制代码private fun start() { |
- 因为我们的
GlobalScope
默认使用的是Dispatchers.Default
,这会导致我们在非主线程上刷新UI。 - 子协程产生异常会产生相互干扰。子协程异常取消会导致父协程取消,同时其他子协程也将会被取消。
- 如果我们这个时候
activity
或者framgent
退出,因为协程是在GlobalScope
中运行,所以即使activity
或者framgent
退出,这个协程还是在运行,这个时候会产生各种泄露问题。同时此协程当执行到刷新操作时,因为我们的界面已经销毁,这个时候执行UI刷新将会产生崩溃。
如果我们要解决上面的问题。我们得这么做:
1 | kotlin复制代码var job:Job? = null |
我们先需要通过launch
启动时加入Dispatchers.Main
来保证我们是在主线程刷新UI,同时还需要再GlobalScope.launch
的协程上下文中加入SupervisorJob
来避免子协程的异常取消会导致整个协程树被终结。
最后我们还得把每次
通过GlobalScope
启动的Job
保存下来,在activity
或者framgent
退出时调用job.cancel
取消整个协程树。这么来一遍感觉还行,但是我们不是写一次啊,每次写的时候会不会感觉超麻烦,甚至怀疑人生。
所以官方在kotlin协程中提供了一个默认在主线程运行的协程:MainScope
,我们可以通过它来启动协。
1 | kotlin复制代码public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main) |
我们可以看到MainScope
的创建默认就使用了SupervisorJob
和 Dispatchers.Main
。说明我们可以通过MainScope
来处理UI组件刷新。同时由于MainScope
采用的是SupervisorJob
,所以我们各个子协程中的异常导致的取消操作并不会导致MainScope
的取消。这就很好的简化了我们通过GlobalScope
去启动一个协程的过程。
1 | kotlin复制代码private val mainScope = MainScope() |
通过使用MainScope
我们是不是省略了很多操作。同时我们也不需要保存每一个通过MainScope
启动的Job
了,直接在最后销毁的时候调用mainScope.cancel()
就能取消所有通过mainScope
启动的协程。
这里多提一点:可能这里有的人会想,我使用GlobalScope
也不保存启动的Job
,直接GlobalScope.cancel
不行吗?如果是这样的话,那么恭喜你喜提超级崩溃BUG一个。这里就不扩展了。可以自己动手去试试,毕竟实践出真理。
那可能还有人想,我连创建MainScope
都懒得写,而且脑子经常不好使,容易忘记调用mainScope
进行cancel
操作怎么办。
官方早就为我们这些懒人想好了解决方案,这个时候我们只需要再集成一个ktx运行库就可以了。
在Activity与Framgent中使用协程
1 | kotlin复制代码 implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1" |
这个时候我们就可以在activity
或者framgent
直接使用lifecycleScope
进行启动协程。我们看来看看activity中的lifecycleScope
实现
1 | kotlin复制代码public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope |
我们可以到lifecycleScope
它是通过lifecycle
得到一个coroutineScope
,是一个LifecycleCoroutineScope
对象。
1 | kotlin复制代码public val Lifecycle.coroutineScope: LifecycleCoroutineScope |
我们可以看到lifecycleScope
采用的和MainScope
一样的创建CoroutineScope
,同时它又通过结合lifecycle
来实现当lifecycle
状态处于DESTROYED
状态的时候自动关闭所有的协程。
1 | kotlin复制代码public abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope { |
同时我们也可以通过launchWhenCreated
、launchWhenStarted
、launchWhenResumed
来启动协程,等到lifecycle
处于对应状态时自动触发此处创建的协程。
比如我们可以这么操作:
1 | kotlin复制代码class MainTestActivity : AppCompatActivity() { |
1 | kotlin复制代码D/onResume: onResume |
按照我们正常情况加载顺序,是不是应该init
先执行输出?然而在实际情况中它是在等待Activity
进入onResume
状态以后才执行接着看launchWhenResumed
中调用的whenResumed
实现。
1 | kotlin复制代码public suspend fun <T> Lifecycle.whenResumed(block: suspend CoroutineScope.() -> T): T { |
我们可以看到,实际上是调用了whenStateAtLeast
,同时使用了withContext
进行了一个同步操作。然后在LifecycleController
中通过添加LifecycleObserver
来监听状态,通过lifecycle
当前状态来对比我们设定的触发状态,最终决定是否恢复执行。
现在我们对于Activity
中的lifecycleScope
的创建以及销毁流程有了一个大概的了解。同理Fragment
中的lifecycleScope
实现原理也是和Activity
是一样的,这里我们就不再重复讲解。我们做个简单的实验:
1 | kotlin复制代码class MainActivity : AppCompatActivity() { |
这个时候是不是比之前的使用方式简单多了,我们既不用关心创建过程,也不用关心销毁的过程。
这个时候我们就需要提到CoroutineExceptionHandler
协程异常处理。通过之前的章节我们知道,启动一个协程以后,如果未在协程上下文中添加CoroutineExceptionHandler
情况下,一旦产生了未捕获的异常,那么我们的程序将会崩溃退出。
1 | kotlin复制代码class MainActivity : AppCompatActivity() { |
当出现这种情况的时候,像笔者这种有严重偷懒情结的人就开始抓狂了。为什么要写这么多遍 lifecycleScope.launch
,同时每一次启动都要手动添加CoroutineExceptionHandler
。难道就不能再简便一点吗?
当然可以,首先我们自定义一个异常处理,,我们在实现上只做一个简单的异常日志输出:
1 | kotlin复制代码/** |
然后我们在通过kotlin的扩展函数来简化我们的使用,去掉重复写lifecycleScope.launch
和exceptionHandler
的过程,我们就定义三个常用方法。
1 | kotlin复制代码inline fun AppCompatActivity.requestMain( |
这个时候我们就可以愉快的在Activity
中使用了
1 | kotlin复制代码class MainActivity : AppCompatActivity() { |
同样的我们再扩展一套基于Fragment
的方法
1 | kotlin复制代码inline fun Fragment.requestMain( |
然后也可以愉快的在Fragment
中使用了
1 | kotlin复制代码class HomeFragment:Fragment() { |
这里需要提一下,可能有的人不太明白,为什么要把Activity
和Fragment
都分开写,他们都是使用的lifecycleScope
,我们直接通过lifecycleScope
扩展就不可以了吗。假如我们这么扩展:
1 | kotlin复制代码inline fun LifecycleCoroutineScope.requestMain( |
我们以Dailog
为例,来启动一个协程:
1 | kotlin复制代码val dialog = Dialog(this) |
那么可能会出现一个什么问题?是的,内存泄露的问题以及错误的引用问题。虽然我的dialog
被销毁了,但是我们lifecycleScope
并不处于DESTROYED
状态,所以我们的协程依然会执行,这个时候我们就会出现内存泄露和崩溃问题。
通过上面的学习,我们已经基本掌握了协程在Activity
和Fragment
中的使用方式。接下来我们讲解在Viewmodel
中使用协程。
ViewModel中使用协程
如果我们想和在Activity
和Fragment
中一样的简便、快速的在ViewModel
使用协程。那么我们就需要集成下面这个官方的ViewModel
扩展库。
1 | kotlin复制代码implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" |
与Activity
和Fragment
不同的是,在ViewModel
我们使用的不是lifecycleScope
,而是使用viewModelScope
,使用viewModelScope
,使用viewModelScope
。重要的事情说三遍。
这里一定要注意噢,之前就有好几个人问我为什么在viewmodel
里面用不了协程,我开始纳闷半天咋就用不了呢。最后一问结果是在ViewModel
使用lifecycleScope
,这样做是不对滴。
1 | kotlin复制代码public val ViewModel.viewModelScope: CoroutineScope |
viewModelScope
相比较lifecycleScope
实现会稍微简单一点。都是使用的SupervisorJob() + Dispatchers.Main
上下文,同时最终的取消操作也类似lifecycleScope
,只不过viewModelScope
取消是在ViewModel
的销毁的时候取消。
1 | java复制代码final void clear() { |
同样的通过上面的总结,我们也为ViewModel
扩展一套常用的方法
1 | kotlin复制代码inline fun ViewModel.requestMain( |
然后我们就可以愉快的在ViewModel
进行使用协程了。
1 | kotlin复制代码class MainViewModel:ViewModel() { |
好了,常规使用协程的方式我都已经学会。但是我们在一些环境下如法使用使用lifecycleScope
和viewModelScope
的时候我们又该怎么办。比如:在Service
、Dialog
、PopWindow
以及一些其他的环境中又该如何使用。
其他环境下使用协程
在这些环境中我们可以采用通用的方式进行处理,其实还是根据协程作用域的差异分为两类:
协同作用域
:这一类我们就模仿MainScope
自定义一个CoroutineScope
。主从(监督)作用域
:这一类我们直接使用MainScope
,然后在此基础上做一些扩展即可。
如果对这两个概念还不理解的,麻烦移步到第二章节里面仔细阅读一遍,这里就不再解释。
我们接下来模仿MainScope
创建一个CoroutineScope
,它是在主线程下执行,并且它的Job
不是SupervisorJob
。
1 | kotlin复制代码@Suppress("FunctionName") |
然后我再基于NormalScope
和MainScope
进行使用。我们就以Service
为例来实现。
1 | kotlin复制代码abstract class BaseService :Service(){ |
我们创建一个抽象类BaseService
类,然后再定义一些基础使用方法后,我就可以快速的使用了
1 | kotlin复制代码class MainService : BaseService() { |
同理在Dialog
、PopWindow
以及一些其他的环境中可以依照此方法,定义符合我们自己需求的CoroutineScope
。一定要记得不要跨域使用,以及及时的关闭协程。
又到了文章末尾,在此章节中我们已经了解了协程结合Activity
、Fragment
、Lifecycle
、Viewmodel
的基础使用,以及如何简单的自定义一个协程,如果还有不清楚的地方,可在下方留言。
需要源码的看这里:demo源码
预告
下一篇我们将结合Android Jetpack
组件进行使用,如:DataBinding
,LiveData
,Room
等。
原创不易。如果您喜欢这篇文章,您可以动动小手点赞收藏。
Android技术交流群,有兴趣的可以私聊加入
关联文章
Kotlin
协程基础及原理系列
- 史上最详Android版kotlin协程入门进阶实战(一) -> kotlin协程的基础用法
- 史上最详Android版kotlin协程入门进阶实战(二) -> kotlin协程的关键知识点初步讲解
- 史上最详Android版kotlin协程入门进阶实战(三) -> kotlin协程的异常处理
- 史上最详Android版kotlin协程入门进阶实战(四) -> 使用kotlin协程开发Android的应用
- 史上最详Android版kotlin协程入门进阶实战(五) -> kotlin协程的网络请求封装
Flow
系列
扩展系列
本文转载自: 掘金