用了lifecycle-runtime-ktx这些API,写出更优雅的代码

  • highlight: vs theme: cyanosis
  • View.findViewTreeLifecycleOwner()
  • LifecycleOwner.withCreated/Started/Resumed()
  • 1.判断是否在主线程调度执行
  • 2.主线程且大于等于STARTED直接进行执行
  • 3.非主线程或小于STARTED挂起协程
  • 总结

highlight: vs theme: cyanosis

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

本篇文章主要是介绍lifecycle-runtime-ktx的两个大家用的比较少的API:findViewTreeLifecycleOwnerwithCreated/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是在哪里赋值的:

看下AppCompatActivitysetContentView()方法:

@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并决定被挂起的协程的恢复时机。

  1. 监听界面状态肯定需要添加观察者将协程与界面生命周期绑定,所以这步就是添加观察者

  2. 观察者主要干了两件事情:

  • 收到界面销毁ON_DESTROY,抛出异常LifecycleDestroyedException,并恢复挂起协程的执行;

  • 达到指定的Started状态,直接执行使用runCatching(捕捉异常)包裹的协程代码块,拿到结果后调用resumeWith恢复被挂起的协程;

  1. 协程被取消则取消观察者注册

这个就是为了兜底,一般都是建议使用Activity的lifecycleScopeviewModelviewModelScope作为协程作用域,因为这两个是和对应组件的生命周期绑定的,这样当组件销毁/清楚,该作用域下的子协程就会被取消,我们通过invokeOnCancellation{}监听到,可以执行一些资源的释放工作,比如这里的取消观察者的注册。

总结

lifecycle-runtime-ktx库中其他的扩展方法大家都比较熟悉,这里就不再额外进行介绍了,感兴趣的可以参考我下面写的几篇文章(包含源码分析):

Jetpack实践指南:lifecycle与协程的"猫腻"事(一)

Jetpack实践指南:lifecycle与协程的"猫腻"事(二)

原文:https://juejin.cn/post/7128402686898077710
版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《TeHub社区用户服务协议》和《TeHub社区知识产权保护指引》。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
喜欢(0)
chris
啥也不会的站长。

评论(0)

添加评论