Android协程(Coroutines)系列-深入理解CoroutineScope作用域

5,178 阅读6分钟

小知识,大挑战!本文正在参与“   程序员必备小知识   ”创作活动

本文同时参与 「掘力星计划」   ,赢取创作大礼包,挑战创作激励金

📚 如果您是 Android 平台上协程的初学者,请查阅上一篇文章: Android协程(Coroutines)系列-入门

🍈 CoroutineScope

CoroutineScope是什么?如果你觉得陌生,那么GlobalScopelifecycleScopeviewModelScope相信就很熟悉了吧。它们都实现了CoroutineScope接口。

public interface CoroutineScope {
    /**
     * The context of this scope.
     * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
     * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
     *
     * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
     */
    public val coroutineContext: CoroutineContext
}

CoroutineScope中只包含一个待实现的变量CoroutineContext(上篇文章已经介绍)

通过它的结构,我们可以认为它是提供CoroutineContext的容器,保证CoroutineContext能在整个协程运行中传递下去,约束CoroutineContext的作用边界。

例如,在Android中使用协程来请求数据,当接口请求未完成时Activity就已经退出了,这时如果不停止正在运行的协程将会造成不可预期的后果。所以在Activity中我们都推荐使用lifecycleScope来启动协程,lifecycleScope可以让协程具有与Activity一样的生命周期意识。

🛩️GlobalScope

/**
 * A global [CoroutineScope] not bound to any job.
 *
 * Global scope is used to launch top-level coroutines which are operating on the whole application lifetime
 * and are not cancelled prematurely.
 * Another use of the global scope is operators running in [Dispatchers.Unconfined], which don't have any job associated with them.
 *
 * Application code usually should use an application-defined [CoroutineScope]. Using
 * [async][CoroutineScope.async] or [launch][CoroutineScope.launch]
 * on the instance of [GlobalScope] is highly discouraged.
 *
 * Usage of this interface may look like this:
 *
 * ```
 * fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) {
 *     for (number in this) {
 *         send(Math.sqrt(number))
 *     }
 * }
 * ```
 */
public object GlobalScope : CoroutineScope {
    /**
     * Returns [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}
  • GlobalScope(object关键词修饰,其实就是个单例)不受job任何边界限制。
  • GlobalScope用于启动顶级协程,在整个应用程序生命周期内运行且不会过早取消。
  • GlobalScope的另一种用法是在Dispatchers.Unconfined中运行的操作符,它与job无任何关联。
  • 应用程序代码通常应使用应用程序定义的CoroutineScope。
  • 不建议GlobalScope在应用中使用

🛩️lifecycleScope

🍄手动实现lifecycleScope同样功能

class MainActivity : AppCompatActivity() {
    /** 1. 创建一个 MainScope
     *
     * (也可以通过实现CoroutineScope接口方式实现 )
     * class MainActivity : AppCompatActivity(), CoroutineScope{
     *      lateinit var job: Job
     *      override val coroutineContext: CoroutineContext
     *          get() = Dispatchers.Main + job

     *      override fun onCreate(savedInstanceState: Bundle?) {
     *          super.onCreate(savedInstanceState)
     *          job = Job()
     *      }
     *      override fun onDestroy() {
     *          super.onDestroy()
     *          job.cancel() // Cancel job on activity destroy. After destroy all children jobs will be cancelled automatically
     *         }
     *      }
     */
    private val scope = CoroutineScope(Dispatchers.Main + Job())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        loadData()
    }
    
    private fun loadData() {
        //2.在IO线程开始(Dispatchers.IO切换到IO线程)
        scope.launch(Dispatchers.IO) {
            //3. IO 线程里拉取数据
            val result = fetchData()
            //4.主线程更新数据
            withContext(Dispatchers.Main) {
                //5.更新UI
                findViewById<TextView>(R.id.tvShowContent).text = result
            }
        }
    }

    //关键词 suspend
    private suspend fun fetchData(): String {
        delay(5000)
        return "数据内容"
    }

    override fun onDestroy() {
        super.onDestroy()
        //协程取消
        //CoroutineScope.cancel的扩展方法,实际调用的是job.cancel()
        scope.cancel()
    }
}

上面也能够保证协程在Activity销毁时终止运行。

🍄lifecycleScope使用

使用lifecycleScope之前,需要引入依赖

implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'//lifecycleScope

使用

lifecycleScope.launch(Dispatchers.IO) {

    //....
}

下面是lifecycleScope源码:

LifecycleOwner的扩展函数

/**
 * [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
 *
 * This scope will be cancelled when the [Lifecycle] is destroyed.
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
 */
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

Lifecycle的扩展函数

/**
 * [CoroutineScope] tied to this [Lifecycle].
 *
 * This scope will be cancelled when the [Lifecycle] is destroyed.
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
 */
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
            }
        }
    }

它创建了一个LifecycleCoroutineScopeImpl实例,它实现了CoroutineScope接口,同时传入SupervisorJob() + Dispatchers.Main作为它的CoroutineContext

看一下 newScope.register() 方法

internal class LifecycleCoroutineScopeImpl(
    override val lifecycle: Lifecycle,
    override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
    init {
        // in case we are initialized on a non-main thread, make a best effort check before
        // we return the scope. This is not sync but if developer is launching on a non-main
        // dispatcher, they cannot be 100% sure anyways.
        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()
        }
    }
}

register方法中通过经典的launch来创建一个协程,而launch使用到的CoroutineContext就是CoroutineSope中的CoroutineContext。然后在协程中结合JetpackLifecycle特性来监听Activiyt的生命周期。

如果对Lifecycle的使用与特性还不是很了解的,请阅读作者的 Jetpack全家桶(二)之Lifecycle使用及源码.

当Activity销毁的时候,这里就使用到了CoroutineContextCoroutineContext自身是没有cancel方法的,这个cancel方法是CoroutineContext的扩展方法。

/**
 * Cancels [Job] of this context with an optional cancellation cause.
 * See [Job.cancel] for details.
 */
public fun CoroutineContext.cancel(cause: CancellationException? = null) {
    this[Job]?.cancel(cause)
}

所以真正的逻辑是从CoroutineContex集合中取出KeyJob的实例,这个对应的就是上面创建LifecycleCoroutineScopeImpl实例时传入的SupervisorJob,它是CoroutineContext的其中一个子类。

🛩️viewModelScope

在ViewModel中使用的协程。 它是ViewModel的扩展属性。

自动取消,不会造成内存泄漏,如果是CoroutineScope,就需要在onCleared()方法中手动取消了,否则可能会造成内存泄漏。

配合ViewModel,能减少样板代码,提高效率。

🍄手动实现viewModelScope同样功能

class MyViewModel : ViewModel() {
 
    /**
     * 这是此 ViewModel 运行的所有协程所用的任务。
     * 终止这个任务将会终止此 ViewModel 开始的所有协程。
     */
    private val viewModelJob = SupervisorJob()
    
    /**
     * 这是 MainViewModel 启动的所有协程的主作用域。
     * 因为我们传入了 viewModelJob,你可以通过调用viewModelJob.cancel() 
     * 来取消所有 uiScope 启动的协程。
     */
    private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
    
    /**
     * 当 ViewModel 清空时取消所有协程
     */
    override fun onCleared() {
        super.onCleared()
        viewModelJob.cancel()
    }
    
    /**
     * 没法在主线程完成的繁重操作
     */
    fun launchDataLoad() {
        uiScope.launch {
            sortList()
            // 更新 UI
        }
    }
    
    suspend fun sortList() = withContext(Dispatchers.Default) {
        // 繁重任务
    }
}

🍄viewModelScope使用

使用viewModelScope之前,需要引入依赖

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'//viewModelScope

使用

viewModelScope.launch(Dispatchers.IO) {

    //....
}

源码分析

val ViewModel.viewModelScope: CoroutineScope
        get() {
            val scope: CoroutineScope? = this.getTag(JOB_KEY)
            if (scope != null) {
                return scope
            }
            // 默认使用Dispatchers.Main,方便Activity和Fragment更新UI
            return setTagIfAbsent(JOB_KEY,
                CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
        }

// 为了ViewModel能够取消协程,需要实现Closeable接口
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context


    override fun close() {
        coroutineContext.cancel()
    }
}

在get()中,通过setTagIfAbsent创建了协程,并且指定主线程。CloseableCoroutineScope实现Closeable接口,并重写唯一方法close(),并在方法中取消了协程。

下面看setTagIfAbsent方法

abstract class ViewModel
    // ViewModel通过HashMap存储CoroutineScope对象
    private final Map<String, Object> mBagOfTags = new HashMap<>();

    // ViewModel被销毁时内部执行clear()方法,
    @MainThread
    final void clear() {
        mCleared = true;
        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    // 取消viewModelScope作用域的协程
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }

// 取消CoroutineScope
    private static void closeWithRuntimeException(Object obj) {
        if (obj instanceof Closeable) {
            try {
                ((Closeable) obj).close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }


    // 如果JOB_KEY已存在且对应的协程作用域不为空则返回对象,否则创建新的协程作用域,并设置JOB_KEY
    @SuppressWarnings("unchecked")
    <T> T setTagIfAbsent(String key, T newValue) {
        T previous;
        synchronized (mBagOfTags) {
            previous = (T) mBagOfTags.get(key);
            if (previous == null) {
                mBagOfTags.put(key, newValue);
            }
        }
        T result = previous == null ? newValue : previous;
        if (mCleared) {
            closeWithRuntimeException(result);
        }
        return result;
    }

    // 通过JOB_KEY从HashMap中返回相应的协程作用域
    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    <T> T getTag(String key) {
        synchronized (mBagOfTags) {
            return (T) mBagOfTags.get(key);
        }
    }

}

在setTagIfAbsent中,以HashMap的形式把协程对象保存起来了,并配有getTag方法。并且调用了closeWithRuntimeException(),这个方法中调用了Closeable接口的close()方法,而close()方法就是用来取消协程的。

而closeWithRuntimeException方法是谁调用的呢,主要是ViewModel中的clear()方法。

👀那这个ViewModel中的clear()方法又是谁调用的呢?

查看源码,只有一处调用,就是在ViewModelStore中,ViewModelStore的介绍请阅读作者之前写的Jetpack全家桶(第3篇)之ViewModel源码篇

总结

  • GlobalScope是生命周期是process级别的,即使Activity或Fragment已经被销毁,协程仍然在执行。
  • lifecycleScope只能在Activity、Fragment中使用,会绑定Activity和Fragment的生命周期
  • viewModelScope只能在ViewModel中使用,绑定ViewModel的生命周期