使用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中,取消了协程、取消了生命周期的监听。
好了,不得不说,随着时代的进步,开发的工作越来越简化了,大多数时候只要关注业务,灵活使用这些组件,能节省很多工作,赶紧用起来吧。