小知识,大挑战!本文正在参与“ 程序员必备小知识 ”创作活动
本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金
📚 如果您是 Android 平台上协程的初学者,请查阅上一篇文章: Android协程(Coroutines)系列-入门
🍈 CoroutineScope
CoroutineScope是什么?如果你觉得陌生,那么GlobalScope、lifecycleScope与viewModelScope相信就很熟悉了吧。它们都实现了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。然后在协程中结合Jetpack的Lifecycle特性来监听Activiyt的生命周期。
如果对
Lifecycle的使用与特性还不是很了解的,请阅读作者的 Jetpack全家桶(二)之Lifecycle使用及源码.
当Activity销毁的时候,这里就使用到了CoroutineContext,CoroutineContext自身是没有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集合中取出Key为Job的实例,这个对应的就是上面创建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的生命周期