使用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)
方法。
看到这里我们知道了:是在ViewModel
的clear()
方法中取消协程的。
而且,clear
方法里面居然还有我们熟悉的onCleared()
方法调用。而onCleared()
我们知道是干什么的,当这个ViewModel
不再使用时会回调这个方法,一般我们需要在此方法中做一些收尾工作,如取消观察者订阅、关闭资源之类的。
接下来就来找一下clear()
方法是在哪里调用的。
搜索一下发现是在ViewModelStore
类clear()
方法中调用的。
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
的容器。在ViewModelStore
的clear()
方法中调用了该ViewModelStore
中所有ViewModel
的clear()
方法。那么ViewModelStore
的clear()
是在哪里调用的?接着追踪,发现是在ComponentActivity
的构造方法中和FragmentManagerViewModel
中都会调用,对应就是Activity
和Fragment
中,也就是我们Android
应用的页面容器中。接下来只分析Activity
,FragmentManagerViewModel
其实也差不多。
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
的时候调用了ViewModelStore
的clear()
。有同学看到这里会有疑问,ViewModel
是如何存放到ViewModelStore
中的呢?还记得项目中是如何初始化ViewModel
的吗?
ViewModelProvider(this).get(TestViewModel::class.java)
最终看到ViewModelProvider
中的get()
方法中有这么一句话store.put(key, it)
。
好了,终于把viewModelScope
理顺了。总结下调用流程,就是当页面的生命周期走到DESTROY
的时候调用了ViewModelStore
的clear()
,接着调用ViewModel
的clear()
,再然后调用ViewModel
的closeWithRuntimeException()
,最后走到CloseableCoroutineScope
的close()
中关闭了协程。
3.lifecycleScope
viewModelScope
是在ViewModel
中使用,那么lifecycleScope
很显然就是在Activity
和Fragment
中使用的。viewModelScope
的作用是在ViewModel
更方便的使用协程并且自动取消协程,lifecycleScope
的作用就是在Activity
或Fragment
中更方便的使用协程并且自动取消协程。
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
lifecycleScope
是LifecycleOwner
的扩展属性,返回值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
用来观察生命周期变化,两个变量startWorkEvent
、cancelWorkEvent
分别表示协程在何时开始和取消,所以这两个判断语句就是实现在指定State
下执行,在非指定State
下取消执行的核心逻辑。为什么能重复执行呢?因为suspendCancellableCoroutine
挂起了,一直就没往下执行过,只有当event == Lifecycle.Event.ON_DESTROY
时才会恢复挂起,也就是协程继续往下执行,接着执行到finally
中,取消了协程、取消了生命周期的监听。
好了,不得不说,随着时代的进步,开发的工作越来越简化了,大多数时候只要关注业务,灵活使用这些组件,能节省很多工作,赶紧用起来吧。