关于Kotlin中的viewModelScope和lifecycleScope的使用和原理讲解

使用Kotlin的协程可以很方便的写异步代码,再配合Google提供的ktx扩展组件,那就更方便了。

1.添加依赖

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.4"

2.viewModelScope

2.1 旧的用法——不使用viewModelScope

先来回顾一下不使用viewModelScope,在ViewModel中使用协程的方式。自己管理CoroutineScope,在不需要的时候(一般是在onCleared())进行取消。否则,可能造成资源浪费、内存泄露等问题。

class TestViewModel : ViewModel() {

    //使用job能够方便的取消协程
    private val job = SupervisorJob()
    //指定协程在哪个线程上运行,并且结合job能方便的取消scope
    private val scope = CoroutineScope(Dispatchers.Main + job)

    fun fetchData() {
        scope.launch {
            val result = realCall()
            //更新UI
            Log.i("TAG", "这里省略")
        }
    }

    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }

    private suspend fun realCall() = withContext(Dispatchers.IO) {
        //真正的网络请求
        delay(1000)
        //模拟返回结果
        "json..."
    }

}

上面代码其实没啥毛病,主要是可能很容易忘记取消。

2.2 新的用法——使用viewModelScope

由于我们很容易忘记取消协程,所以Google贴心的提供了viewModelScope,来看看怎么用的。

class TestViewModel2 : ViewModel() {

    fun fetchData() {
        viewModelScope.launch {
            val result = realCall()
            //更新UI
            Log.i("TAG", "这里省略")
        }
    }

    private suspend fun realCall() = withContext(Dispatchers.IO) {
        //真正的网络请求
        delay(1000)
        //模拟返回结果
        "json..."
    }

}

对比一下代码简洁了很多,协程的初始化和取消都自动的实现了。

接下来深入源码看下是如何实现的。

2.3 viewModelScope的底层实现

知其然知其所以然,这样才能在遇到问题的时候知道有的放矢,才能有更好的解决办法。

private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"

/**
 * [CoroutineScope] tied to this [ViewModel].
 * This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
 */
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是什么,它其实是ViewModel的一个扩展属性。每次使用viewModelScope的时候首先从缓存中取,如果没有才去新建一个CloseableCoroutineScope,它实现了Closeable接口,并在close()方法中进行了取消。

接下来需要知道缓存是如何存和取的。

//ViewModel.java

private final Map<String, Object> mBagOfTags = new HashMap<>();

<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) {
        // It is possible that we'll call close() multiple times on the same object, but
        // Closeable interface requires close method to be idempotent:
        // "if the stream is already closed then invoking this method has no effect." (c)
        closeWithRuntimeException(result);
    }
    return result;
}

/**
    * Returns the tag associated with this viewmodel and the specified key.
    */
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
<T> T getTag(String key) {
    if (mBagOfTags == null) {
        return null;
    }
    synchronized (mBagOfTags) {
        return (T) mBagOfTags.get(key);
    }
}

实际上是存储在一个mBagOfTags变量中,那该变量又是什么时候用取的呢?

ViewModel.java

protected void onCleared() {
}

@MainThread
final void clear() {
    mCleared = true;
    // Since clear() is final, this method is still called on mock objects
    // and in those cases, mBagOfTags is null. It'll always be empty though
    // because setTagIfAbsent and getTag are not final so we can skip
    // clearing it
    if (mBagOfTags != null) {
        synchronized (mBagOfTags) {
            for (Object value : mBagOfTags.values()) {
                // see comment for the similar call in setTagIfAbsent
                closeWithRuntimeException(value);
            }
        }
    }
    // We need the same null check here
    if (mCloseables != null) {
        synchronized (mCloseables) {
            for (Closeable closeable : mCloseables) {
                closeWithRuntimeException(closeable);
            }
        }
    }
    onCleared();
}

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

继续在ViewModel中搜索,发现在clear()方法中取出了mBagOfTags,并调用了closeWithRuntimeException(xx)方法。

看到这里我们知道了:是在ViewModelclear()方法中取消协程的。

而且,clear方法里面居然还有我们熟悉的onCleared()方法调用。而onCleared()我们知道是干什么的,当这个ViewModel不再使用时会回调这个方法,一般我们需要在此方法中做一些收尾工作,如取消观察者订阅、关闭资源之类的。

接下来就来找一下clear()方法是在哪里调用的。

搜索一下发现是在ViewModelStoreclear()方法中调用的。

open class ViewModelStore {

    private val map = mutableMapOf<String, ViewModel>()

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    fun put(key: String, viewModel: ViewModel) {
        val oldViewModel = map.put(key, viewModel)
        oldViewModel?.onCleared()
    }

    /**
     * Returns the `ViewModel` mapped to the given `key` or null if none exists.
     */
    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    operator fun get(key: String): ViewModel? {
        return map[key]
    }

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    fun keys(): Set<String> {
        return HashSet(map.keys)
    }

    /**
     * Clears internal storage and notifies `ViewModel`s that they are no longer used.
     */
    fun clear() {
        for (vm in map.values) {
            vm.clear()
        }
        map.clear()
    }
}

ViewModelStore看名字就知道,它是存放ViewModel的容器。在ViewModelStoreclear()方法中调用了该ViewModelStore中所有ViewModelclear()方法。那么ViewModelStoreclear()是在哪里调用的?接着追踪,发现是在ComponentActivity的构造方法中和FragmentManagerViewModel中都会调用,对应就是ActivityFragment中,也就是我们Android应用的页面容器中。接下来只分析ActivityFragmentManagerViewModel其实也差不多。

public ComponentActivity() {
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                // Clear out the available context
                mContextAwareHelper.clearAvailableContext();
                // And clear the ViewModelStore
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
                mReportFullyDrawnExecutor.activityDestroyed();
            }
        }
    });
}

Activity生命周期走到DESTROY的时候调用了ViewModelStoreclear()。有同学看到这里会有疑问,ViewModel是如何存放到ViewModelStore中的呢?还记得项目中是如何初始化ViewModel的吗?

ViewModelProvider(this).get(TestViewModel::class.java)

最终看到ViewModelProvider中的get()方法中有这么一句话store.put(key, it)

好了,终于把viewModelScope理顺了。总结下调用流程,就是当页面的生命周期走到DESTROY的时候调用了ViewModelStoreclear(),接着调用ViewModelclear(),再然后调用ViewModelcloseWithRuntimeException(),最后走到CloseableCoroutineScopeclose()中关闭了协程。

3.lifecycleScope

viewModelScope是在ViewModel中使用,那么lifecycleScope很显然就是在ActivityFragment中使用的。viewModelScope的作用是在ViewModel更方便的使用协程并且自动取消协程,lifecycleScope的作用就是在ActivityFragment中更方便的使用协程并且自动取消协程。

3.1 简单的使用

class TestActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleScope.launch {
            repeat(1000) {
                tv.text = "$it"
            }
        }
    }
}

当页面onDestroy了,上面的协程就会被取消。

3.2 lifecycleScope的底层原理

public interface LifecycleOwner {
    /**
     * Returns the Lifecycle of the provider.
     *
     * @return The lifecycle of the provider.
     */
    public val lifecycle: Lifecycle
}

/**
 * [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].
 */
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

lifecycleScopeLifecycleOwner的扩展属性,返回值coroutineScope又是Lifecycle的扩展属性

public val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            val existing = internalScopeRef.get() as LifecycleCoroutineScopeImpl?
            if (existing != null) {
                return existing
            }
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            if (internalScopeRef.compareAndSet(null, newScope)) {
                newScope.register()
                return newScope
            }
        }
    }

先从internalScopeRef取,取不到就创建一个LifecycleCoroutineScopeImpl放进去并调用register()。这里的internalScopeRef: AtomicReference<Any> = AtomicReference<Any>(),保证原子性操作,当然是为了线程安全了。

再来看看LifecycleCoroutineScopeImpl

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()
        }
    }
}

有2个重要的函数:register()onStateChanged()register()函数是在初始化LifecycleCoroutineScopeImpl的时候调用的,先在register()函数中添加一个观察者用于观察生命周期的变化,然后在onStateChanged()函数中判断生命周期到DESTROYED时就移除观察者并且取消协程。

现在我们流程理清楚了,lifecycleScope使用时会构建一个协程,同时会观察组件的生命周期,在适当的时机(DESTROYED)取消协程。

4.repeatOnLifecycle

4.1 旧的用法

lifecycleScope还提供了几个比较实用的方法

lifecycleScope.launchWhenCreated {  }
lifecycleScope.launchWhenResumed { }
lifecycleScope.launchWhenStarted { }

但是这几个方法都被标记为DEPRECATION了,以launchWhenCreated为例,看下注释。

@Deprecated(
    message = "launchWhenCreated is deprecated as it can lead to wasted resources " +
        "in some cases. Replace with suspending repeatOnLifecycle to run the block whenever " +
        "the Lifecycle state is at least Lifecycle.State.CREATED."
)
@Suppress("DEPRECATION")
public fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch {
    lifecycle.whenCreated(block)
}

废弃的原因是说在某些情况下会导致资源浪费,那么就有疑问了,在什么情况下会导致资源浪费呢?因为launchWhenCreated是在生命周期走到CREATED的时候调用,如果正在执行里面的block的时候生命周期走到了DESTROYED,协程是不会取消的,这就是注释解释的会导致资源浪费的原因了。

4.2 新的用法

取而代之的是repeatOnLifecycle,用法如下。

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.CREATED) {
        Log.i("TAG", "create...")
    }
}

repeatOnLifecycle(Lifecycle.State.CREATED)的执行流程是:在生命周期走到CREATED状态的时候执行block如果离开CREATED状态,协程会被取消

4.3 底层原理

主要分析repeatOnLifecycle是如何做到在指定状态重复执行,并在不是指定状态时取消协程。

public suspend fun Lifecycle.repeatOnLifecycle(
    state: Lifecycle.State,
    block: suspend CoroutineScope.() -> Unit
) {
    require(state !== Lifecycle.State.INITIALIZED) {
        "repeatOnLifecycle cannot start work with the INITIALIZED lifecycle state."
    }

    if (currentState === Lifecycle.State.DESTROYED) {
        return
    }

    // This scope is required to preserve context before we move to Dispatchers.Main
    coroutineScope {
        withContext(Dispatchers.Main.immediate) {
            // Check the current state of the lifecycle as the previous check is not guaranteed
            // to be done on the main thread.
            if (currentState === Lifecycle.State.DESTROYED) return@withContext

            // Instance of the running repeating coroutine
            var launchedJob: Job? = null

            // Registered observer
            var observer: LifecycleEventObserver? = null
            try {
                // Suspend the coroutine until the lifecycle is destroyed or
                // the coroutine is cancelled
                suspendCancellableCoroutine<Unit> { cont ->
                    // Lifecycle observers that executes `block` when the lifecycle reaches certain state, and
                    // cancels when it falls below that state.
                    val startWorkEvent = Lifecycle.Event.upTo(state)
                    val cancelWorkEvent = Lifecycle.Event.downFrom(state)
                    val mutex = Mutex()
                    observer = LifecycleEventObserver { _, event ->
                        if (event == startWorkEvent) {
                            // Launch the repeating work preserving the calling context
                            launchedJob = this@coroutineScope.launch {
                                // Mutex makes invocations run serially,
                                // coroutineScope ensures all child coroutines finish
                                mutex.withLock {
                                    coroutineScope {
                                        block()
                                    }
                                }
                            }
                            return@LifecycleEventObserver
                        }
                        if (event == cancelWorkEvent) {
                            launchedJob?.cancel()
                            launchedJob = null
                        }
                        if (event == Lifecycle.Event.ON_DESTROY) {
                            cont.resume(Unit)
                        }
                    }
                    this@repeatOnLifecycle.addObserver(observer as LifecycleEventObserver)
                }
            } finally {
                launchedJob?.cancel()
                observer?.let {
                    this@repeatOnLifecycle.removeObserver(it)
                }
            }
        }
    }
}

代码有点长,主要关注suspendCancellableCoroutine,启动了一个可取消的协程,当代码执行到这里的时候协程挂起,跟所有对生命周期敏感的组件一样,内部也是注册了LifecycleEventObserver用来观察生命周期变化,两个变量startWorkEventcancelWorkEvent分别表示协程在何时开始和取消,所以这两个判断语句就是实现在指定State下执行,在非指定State下取消执行的核心逻辑。为什么能重复执行呢?因为suspendCancellableCoroutine挂起了,一直就没往下执行过,只有当event == Lifecycle.Event.ON_DESTROY时才会恢复挂起,也就是协程继续往下执行,接着执行到finally中,取消了协程、取消了生命周期的监听。

好了,不得不说,随着时代的进步,开发的工作越来越简化了,大多数时候只要关注业务,灵活使用这些组件,能节省很多工作,赶紧用起来吧。