史上最详 Android 版 kotlin 协程入门进阶实战 (四),架构师必备
"请求结果"
}
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
的创建默认就使用了SupervisorJob
和 Dispatchers.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
操作怎么办。
官方早就为我们这些懒人想好了解决方案,这个时候我们只需要再集成一个 ktx 运行库就可以了。
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
这个时候我们就可以在activity
或者framgent
直接使用lifecycleScope
进行启动协程。我们看来看看 activity 中的lifecycleScope
实现
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
我们可以到lifecycleScope
它是通过lifecycle
得到一个coroutineScope
,是一个LifecycleCoroutineScope
对象。
public val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
while (true) {
val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
if (existing != null) {
return existing
}
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main.immediate
)
if (mInternalScopeRef.compareAndSet(null, newScope)) {
newScope.register()
return newScope
}
}
}
我们可以看到lifecycleScope
采用的和MainScope
一样的创建CoroutineScope
,同时它又通过结合lifecycle
来实现当lifecycle
状态处于DESTROYED
状态的时候自动关闭所有的协程。
public abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
internal abstract val lifecycle: Lifecycle
public fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenCreated(block)
}
public fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenStarted(block)
}
public fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenResumed(block)
}
}
internal class LifecycleCoroutineScopeImpl(
override val lifecycle: Lifecycle,
override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
init {
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
coroutineContext.cancel()
}
}
fun register() {
launch(Dispatchers.Main.immediate) {
if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
} else {
coroutineContext.cancel()
}
}
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
lifecycle.removeObserver(this)
coroutineContext.cancel()
}
}
}
同时我们也可以通过launchWhenCreated
、launchWhenStarted
、launchWhenResumed
来启动协程,等到lifecycle
处于对应状态时自动触发此处创建的协程。
比如我们可以这么操作:
class MainTestActivity : AppCompatActivity() {
init {
lifecycleScope.launchWhenResumed {
Log.d("init", "在类初始化位置启动协程")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
D/onResume: onResume
D/init: 在类初始化位置启动协程
按照我们正常情况加载顺序,是不是应该init
先执行输出?然而在实际情况中它是在等待Activity
进入onResume
状态以后才执行接着看launchWhenResumed
中调用的whenResumed
实现。
public suspend fun <T> Lifecycle.whenResumed(block: suspend CoroutineScope.() -> T): T {
return whenStateAtLeast(Lifecycle.State.RESUMED, block)
}
public suspend fun <T> Lifecycle.whenStateAtLeast(
minState: Lifecycle.State,
block: suspend CoroutineScope.() -> T
): T = withContext(Dispatchers.Main.immediate) {
val job = coroutineContext[Job] ?: error("when[State] methods should have a parent job")
val dispatcher = PausingDispatcher()
val controller =
LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job)
try {
withContext(dispatcher, block)
} finally {
controller.finish()
}
}
@MainThread
internal class LifecycleController(
private val lifecycle: Lifecycle,
private val minState: Lifecycle.State,
private val dispatchQueue: DispatchQueue,
parentJob: Job
) {
private val observer = LifecycleEventObserver { source, _ ->
if (source.lifecycle.currentState == Lifecycle.State.DESTROYED) {
handleDestroy(parentJob)
} else if (source.lifecycle.currentState < minState) {
dispatchQueue.pause()
} else {
dispatchQueue.resume()
}
}
init {
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
handleDestroy(parentJob)
} else {
lifecycle.addObserver(observer)
}
}
private inline fun handleDestroy(parentJob: Job) {
parentJob.cancel()
finish()
}
@MainThread
fun finish() {
lifecycle.removeObserver(observer)
dispatchQueue.finish()
}
}
我们可以看到,实际上是调用了whenStateAtLeast
,同时使用了withContext
进行了一个同步操作。然后在LifecycleController
中通过添加LifecycleObserver
来监听状态,通过lifecycle
当前状态来对比我们设定的触发状态,最终决定是否恢复执行。
现在我们对于Activity
中的lifecycleScope
的创建以及销毁流程有了一个大概的了解。同理Fragment
中的lifecycleScope
实现原理也是和Activity
是一样的,这里我们就不再重复讲解。我们做个简单的实验:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launch {
delay(2000)
Toast.makeText(this@MainActivity,"haha",Toast.LENGTH_SHORT).show()
}
}
}
这个时候是不是比之前的使用方式简单多了,我们既不用关心创建过程,也不用关心销毁的过程。
这个时候我们就需要提到CoroutineExceptionHandler
协程异常处理。通过之前的章节我们知道,启动一个协程以后,如果未在协程上下文中添加CoroutineExceptionHandler
情况下,一旦产生了未捕获的异常,那么我们的程序将会崩溃退出。
class MainActivity : AppCompatActivity() {
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
Log.d("exceptionHandler", "throwable")
}
fun load() {
lifecycleScope.launch(exceptionHandler) {
//省略...
}
lifecycleScope.launch(exceptionHandler) {
//省略...
}
lifecycleScope.launch(exceptionHandler) {
//省略...
}
}
}
当出现这种情况的时候,像笔者这种有严重偷懒情结的人就开始抓狂了。为什么要写这么多遍 lifecycleScope.launch
,同时每一次启动都要手动添加CoroutineExceptionHandler
。难道就不能再简便一点吗?
当然可以,首先我们自定义一个异常处理,,我们在实现上只做一个简单的异常日志输出:
/**
@param errCode 错误码
@param errMsg 简要错误信息
@param report 是否需要上报
*/
class GlobalCoroutineExceptionHandler(private val errCode: Int, private val errMsg: String = "", private val report: Boolean = false) : CoroutineExceptionHandler {
override val key: CoroutineContext.Key<*>
get() = CoroutineExceptionHandler
override fun handleException(context: CoroutineContext, exception: Throwable) {
val msg = exception.stackTraceToString()
Log.e("{msg}")
}
}
然后我们在通过 kotlin 的扩展函数来简化我们的使用,去掉重复写lifecycleScope.launch
和exceptionHandler
的过程,我们就定义三个常用方法。
inline fun AppCompatActivity.requestMain(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
crossinline block: suspend CoroutineScope.() -> Unit) {
lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
inline fun AppCompatActivity.requestIO(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
crossinline block: suspend CoroutineScope.() -> Unit): Job {
return lifecycleScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
inline fun AppCompatActivity.delayMain(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
delayTime: Long, crossinline block: suspend CoroutineScope.() -> Unit) {
lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
withContext(Dispatchers.IO) {
delay(delayTime)
}
block.invoke(this)
}
}
这个时候我们就可以愉快的在Activity
中使用了
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView
(R.layout.activity_main)
requestMain {
delay(2000)
Toast.makeText(this@MainActivity,"haha",Toast.LENGTH_SHORT).show()
}
requestIO {
loadNetData()
}
delayMain(100){
Toast.makeText(this@MainActivity,"haha",Toast.LENGTH_SHORT).show()
}
}
private suspend fun loadNetData(){
//网络加载
}
}
同样的我们再扩展一套基于Fragment
的方法
inline fun Fragment.requestMain(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
crossinline block: suspend CoroutineScope.() -> Unit) {
lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
inline fun Fragment.requestIO(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
crossinline block: suspend CoroutineScope.() -> Unit) {
lifecycleScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
inline fun Fragment.delayMain(
errCode: Int = -1, errMsg: String = "", report: Boolean = false, delayTime: Long,
crossinline block: suspend CoroutineScope.() -> Unit) {
lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
withContext(Dispatchers.IO) {
delay(delayTime)
}
block.invoke(this)
}
}
然后也可以愉快的在Fragment
中使用了
class HomeFragment:Fragment() {
init {
lifecycleScope.launchWhenCreated {
Toast.makeText(context,"Fragment 创建了", Toast.LENGTH_SHORT).show()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
requestMain {
//...
}
requestIO {
//...
}
delayMain(100){
//...
}
}
}
这里需要提一下,可能有的人不太明白,为什么要把Activity
和Fragment
都分开写,他们都是使用的lifecycleScope
,我们直接通过lifecycleScope
扩展就不可以了吗。假如我们这么扩展:
inline fun LifecycleCoroutineScope.requestMain(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
crossinline block: suspend CoroutineScope.() -> Unit) {
launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
复制代码
我们以Dailog
为例,来启动一个协程:
val dialog = Dialog(this)
dialog.show()
(dialog.context as LifecycleOwner).lifecycleScope.requestMain {
withContext(Dispatchers.IO){
//网络加载
}
// 刷新 UI
}
dialog.cancel()
那么可能会出现一个什么问题?是的,内存泄露的问题以及错误的引用问题。虽然我的dialog
被销毁了,但是我们lifecycleScope
并不处于DESTROYED
状态,所以我们的协程依然会执行,这个时候我们就会出现内存泄露和崩溃问题。
通过上面的学习,我们已经基本掌握了协程在Activity
和Fragment
中的使用方式。接下来我们讲解在Viewmodel
中使用协程。
如果我们想和在Activity
和Fragment
中一样的简便、快速的在ViewModel
使用协程。那么我们就需要集成下面这个官方的ViewModel
扩展库。
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
与Activity
和Fragment
不同的是,在ViewModel
我们使用的不是lifecycleScope
,而是使用viewModelScope
,使用viewModelScope
,使用viewModelScope
。重要的事情说三遍。
这里一定要注意噢,之前就有好几个人问我为什么在viewmodel
里面用不了协程,我开始纳闷半天咋就用不了呢。最后一问结果是_在ViewModel
使用lifecycleScope
_,这样做是不对滴。
public val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(
JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
)
}
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
viewModelScope
相比较lifecycleScope
实现会稍微简单一点。都是使用的SupervisorJob() + Dispatchers.Main
上下文,同时最终的取消操作也类似lifecycleScope
,只不过viewModelScope
取消是在ViewModel
的销毁的时候取消。
评论