携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
本篇文章主要是介绍
lifecycle-runtime-ktx
的两个大家用的比较少的API:findViewTreeLifecycleOwner
和withCreated/Started/Resumed()
系列。
View.findViewTreeLifecycleOwner()
这个是ifecycle-runtime-ktx
官方库提供的一个扩展方法简化获取LifecycleOwner
的逻辑:
public fun View.findViewTreeLifecycleOwner(): LifecycleOwner? = ViewTreeLifecycleOwner.get(this)
复制代码
最终调用:
public static LifecycleOwner get(@NonNull View view) {
LifecycleOwner found = (LifecycleOwner) view.getTag(R.id.view_tree_lifecycle_owner);
if (found != null) return found;
ViewParent parent = view.getParent();
while (found == null && parent instanceof View) {
final View parentView = (View) parent;
found = (LifecycleOwner) parentView.getTag(R.id.view_tree_lifecycle_owner);
parent = parentView.getParent();
}
return found;
}
复制代码
可以看到最终是遍历view树,从View的tag
中通过R.id.view_tree_lifecycle_owner
获取的:
@UnsupportedAppUsage
//键值为非装箱的基本数据类型int
private SparseArray<Object> mKeyedTags;
public Object getTag(int key) {
if (mKeyedTags != null) return mKeyedTags.get(key);
return null;
}
复制代码
我们看下这个tag
是在哪里赋值的:
看下AppCompatActivity
的setContentView()
方法:
@Override
public void setContentView(@LayoutRes int layoutResID) {
initViewTreeOwners();
getDelegate().setContentView(layoutResID);
}
复制代码
走进initViewTreeOwners()
方法看下:
private void initViewTreeOwners() {
ViewTreeLifecycleOwner.set(getWindow().getDecorView(), this);
...
}
//ViewTreeLifecycleOwner.java
public static void set(@NonNull View view, @Nullable LifecycleOwner lifecycleOwner) {
view.setTag(R.id.view_tree_lifecycle_owner, lifecycleOwner);
}
复制代码
可以看到,就是在这里进行赋值的,其中这个参数view就是DecorView
。
LifecycleOwner.withCreated/Started/Resumed()
这里我们以LifecycleOwner.withStarted()
举例,这个方法是带有返回值
的且保证在主线程执行
,在未达到指定执行生命周期且返回结果执行完毕之前,运行的协程会进行挂起:
public suspend inline fun <R> LifecycleOwner.withStarted(
crossinline block: () -> R
): R = lifecycle.withStateAtLeastUnchecked(
state = Lifecycle.State.STARTED,
block = block
)
复制代码
最终走到lifecycle.withStateAtLeastUnchecked()
方法:
@PublishedApi
internal suspend inline fun <R> Lifecycle.withStateAtLeastUnchecked(
state: Lifecycle.State,
crossinline block: () -> R
): R {
//1.指定主线程调度器,判断是否需要分发
val lifecycleDispatcher = Dispatchers.Main.immediate
val dispatchNeeded = lifecycleDispatcher.isDispatchNeeded(coroutineContext)
//2.直接执行,无需分发
if (!dispatchNeeded) {
if (currentState == Lifecycle.State.DESTROYED) throw LifecycleDestroyedException()
if (currentState >= state) return block()
}
//3.挂起执行
return suspendWithStateAtLeastUnchecked(state, dispatchNeeded, lifecycleDispatcher) {
block()
}
}
复制代码
接下来我们来一步步进行分析:
1.判断是否在主线程调度执行
Dispatchers.Main.immediate
代表主线程调度器,通过isDispatchNeeded
判断当前的执行环境是否处于主线程,如果是将执行执行协程代码块,否则需要调度器分发到主线程再进行执行。
2.主线程且大于等于STARTED
直接进行执行
-
主线程环境下,首先判断当前界面已经处于
销毁
状态,是直接抛出LifecycleDestroyedException
异常,结束; -
如果界面状态大于等于
STARTED
状态,才直接执行协程代码块,如果界面状态小于STARTED
,就需要调度
3.非主线程或小于STARTED
挂起协程
下面走进suspendWithStateAtLeastUnchecked()
函数:
@PublishedApi
internal suspend fun <R> Lifecycle.suspendWithStateAtLeastUnchecked(
state: Lifecycle.State,
dispatchNeeded: Boolean,
lifecycleDispatcher: CoroutineDispatcher,
block: () -> R
): R = suspendCancellableCoroutine { co ->
val observer = object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.upTo(state)) {
removeObserver(this)
//2.达到执行`Started`状态恢复挂起的协程
co.resumeWith(runCatching(block))
} else if (event == Lifecycle.Event.ON_DESTROY) {
removeObserver(this)
//3.达到`DESTROYED`状态抛出异常
co.resumeWithException(LifecycleDestroyedException())
}
}
}
//1.添加观察者
if (dispatchNeeded) {
lifecycleDispatcher.dispatch(
EmptyCoroutineContext,
Runnable { addObserver(observer) }
)
} else addObserver(observer)
//3.移除观察者
co.invokeOnCancellation {
if (lifecycleDispatcher.isDispatchNeeded(EmptyCoroutineContext)) {
lifecycleDispatcher.dispatch(
EmptyCoroutineContext,
Runnable { removeObserver(observer) }
)
} else removeObserver(observer)
}
}
复制代码
首先说明下
suspendWithStateAtLeastUnchecked()
为什么使用@PublishedApi
注解修饰,由于内联函数中调用的方法只能是public
方法,而suspendWithStateAtLeastUnchecked()
是个internal
,所以需要增加该注解声明。
suspendCancellableCoroutine()
方法捕捉Continuation
并决定被挂起的协程的恢复时机。
-
监听界面状态肯定需要添加观察者将协程与界面生命周期绑定,所以这步就是
添加观察者
; -
观察者主要干了两件事情:
-
收到界面销毁
ON_DESTROY
,抛出异常LifecycleDestroyedException
,并恢复挂起协程的执行; -
达到指定的
Started
状态,直接执行使用runCatching
(捕捉异常)包裹的协程代码块,拿到结果后调用resumeWith
恢复被挂起的协程;
- 协程被取消则取消观察者注册
这个就是为了兜底,一般都是建议使用Activity的lifecycleScope
或viewModel
的viewModelScope
作为协程作用域,因为这两个是和对应组件的生命周期绑定的,这样当组件销毁/清楚,该作用域下的子协程就会被取消,我们通过invokeOnCancellation{}
监听到,可以执行一些资源的释放工作,比如这里的取消观察者的注册。
总结
lifecycle-runtime-ktx
库中其他的扩展方法大家都比较熟悉,这里就不再额外进行介绍了,感兴趣的可以参考我下面写的几篇文章(包含源码分析):