Android Jetpack系列之生命周期感知 androidx.lifecycle

639 阅读10分钟

Android Jetpack系列之处理生命周期 androidx.lifecycle

原文

使用生命周期感知型组件处理生命周期, 开发者模式的 “不保留活动” 之后很容易地重现了这个异常。

生命周期感知型组件可执行操作来响应另一个组件(如 Activity 和 Fragment)的生命周期状态的变化。 这些组件有助于您编写出更有条理且往往更精简的代码,此类代码更易于维护。

一种常见的模式是在 Activity 和 Fragment 的生命周期方法中实现依赖组件的操作。 但是,这种模式会导致代码条理性很差而且会扩散错误。 通过使用生命周期感知型组件,您可以将依赖组件的代码从生命周期方法移入组件本身中。

androidx.lifecycle 软件包提供了可用于构建生命周期感知型组件的类和接口 - 这些组件可以根据 Activity 或 Fragment 的当前生命周期状态自动调整其行为。

1.导入依赖

原文

    dependencies {
        val lifecycle_version = "2.5.0-alpha03"
        val arch_version = "2.1.0"

        // ViewModel
        implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
        // ViewModel utilities for Compose
        implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version")
        // LiveData
        implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
        // Lifecycles only (without ViewModel or LiveData)
        implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version")

        // Saved state module for ViewModel
        implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version")

        // Annotation processor
        kapt("androidx.lifecycle:lifecycle-compiler:$lifecycle_version")
        // alternately - if using Java8, use the following instead of lifecycle-compiler
        implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycle_version")

        // optional - helpers for implementing LifecycleOwner in a Service
        implementation("androidx.lifecycle:lifecycle-service:$lifecycle_version")

        // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
        implementation("androidx.lifecycle:lifecycle-process:$lifecycle_version")

        // optional - ReactiveStreams support for LiveData
        implementation("androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version")

        // optional - Test helpers for LiveData
        testImplementation("androidx.arch.core:core-testing:$arch_version")
    }
  

2.生命周期与相关回调接口与实现类

相关生命周期状态详见原文

LifecycleObserver,FullLifecycleObserver,DefaultLifecycleObserver

public interface LifecycleObserver {}
package androidx.lifecycle;

interface FullLifecycleObserver extends LifecycleObserver {

    void onCreate(LifecycleOwner owner);

    void onStart(LifecycleOwner owner);

    void onResume(LifecycleOwner owner);

    void onPause(LifecycleOwner owner);

    void onStop(LifecycleOwner owner);

    void onDestroy(LifecycleOwner owner);
}
package androidx.lifecycle;

import androidx.annotation.NonNull;

@SuppressWarnings("unused")
public interface DefaultLifecycleObserver extends FullLifecycleObserver {

    @Override
    default void onCreate(@NonNull LifecycleOwner owner) {
    }

    @Override
    default void onStart(@NonNull LifecycleOwner owner) {
    }

    @Override
    default void onResume(@NonNull LifecycleOwner owner) {
    }

    @Override
    default void onPause(@NonNull LifecycleOwner owner) {
    }

    @Override
    default void onStop(@NonNull LifecycleOwner owner) {
    }

    @Override
    default void onDestroy(@NonNull LifecycleOwner owner) {
    }
}

3.LifecycleOwner

查看LifecycleOwner的继承关系

选中LifecycleOwner类后,快捷键 Ctrl+H

ComponentActivity (androidx.activity) OnBackPressedDispatcherOwner (androidx.activity) ComponentActivity (androidx.core.app) ProcessLifecycleOwner (androidx.lifecycle) Fragment (androidx.fragment.app) SavedStateRegistryOwner (androidx.savedstate) LifecycleRegistryOwner (androidx.lifecycle) LifecycleService (androidx.lifecycle)

可以在activity,fragment,service,application

3.1整个application应用生命周期监听

//一般真正使用时,ProcessLifecycleOwner,DefaultLifecycleObserver对象,
//组合到一个新的类对象里封装,而不是直接写在application里
ProcessLifecycleOwner.get().lifecycle.addObserver(object: DefaultLifecycleObserver{
    override fun onCreate(owner: LifecycleOwner) {
        super.onCreate(owner)
        LogUtils.d("ProcessLifecycleOwner onCreate")
    }

    override fun onStart(owner: LifecycleOwner) {
        super.onStart(owner)
        LogUtils.d("ProcessLifecycleOwner onStart")
    }

    override fun onResume(owner: LifecycleOwner) {
        super.onResume(owner)
        LogUtils.d("ProcessLifecycleOwner onResume")
    }

    override fun onPause(owner: LifecycleOwner) {
        super.onPause(owner)
        LogUtils.d("ProcessLifecycleOwner onPause")
    }

    override fun onStop(owner: LifecycleOwner) {
        super.onStop(owner)
        LogUtils.d("ProcessLifecycleOwner onStop")
    }

    override fun onDestroy(owner: LifecycleOwner) {
        super.onDestroy(owner)
        LogUtils.d("ProcessLifecycleOwner onDestroy")
    }
})

3.2LifecycleService具有生命周期感知的Service

LifecycleService (androidx.lifecycle) SystemAlarmService (androidx.work.impl.background.systemalarm) SystemForegroundService (androidx.work.impl.foreground)

4.实现自定义 LifecycleOwner

持库 26.1.0 及更高版本中的 Fragment 和 Activity 已实现 LifecycleOwner 接口。

如果您有一个自定义类并希望使其成为 LifecycleOwner,您可以使用 LifecycleRegistry 类,但需要将事件转发到该类,如以下代码示例中所示:

class MyActivity : Activity(), LifecycleOwner {

    private lateinit var lifecycleRegistry: LifecycleRegistry

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

        lifecycleRegistry = LifecycleRegistry(this)
        lifecycleRegistry.markState(Lifecycle.State.CREATED)
    }

    public override fun onStart() {
        super.onStart()
        lifecycleRegistry.markState(Lifecycle.State.STARTED)
    }

    override fun getLifecycle(): Lifecycle {
        return lifecycleRegistry
    }
}

LifecycleService就是使用LifecycleRegistry实现了自定义的LifecycleOwner

代码封装在ServiceLifecycleDispatcher类里。

5.生命周期感知型组件的最佳做法

  • 使界面控制器(Activity 和 Fragment)尽可能保持精简。它们不应试图获取自己的数据,而应使用 ViewModel 执行此操作,并观察 LiveData 对象以将更改体现到视图中。
  • 设法编写数据驱动型界面,对于此类界面,界面控制器的责任是随着数据更改而更新视图,或者将用户操作通知给 ViewModel
  • 将数据逻辑放在 ViewModel 类中。ViewModel 应充当界面控制器与应用其余部分之间的连接器。不过要注意,ViewModel 不负责获取数据(例如,从网络获取)。但是,ViewModel 应调用相应的组件来获取数据,然后将结果提供给界面控制器。
  • 使用数据绑定在视图与界面控制器之间维持干净的接口。这样一来,您可以使视图更具声明性,并尽量减少需要在 Activity 和 Fragment 中编写的更新代码。如果您更愿意使用 Java 编程语言执行此操作,请使用诸如 Butter Knife 之类的库,以避免样板代码并实现更好的抽象化。
  • 如果界面很复杂,不妨考虑创建 presenter 类来处理界面的修改。这可能是一项艰巨的任务,但这样做可使界面组件更易于测试。
  • 避免在 ViewModel 中引用 ViewActivity 上下文。如果 ViewModel 存在的时间比 Activity 更长(在配置更改的情况下),Activity 将泄漏并且不会获得垃圾回收器的妥善处置。
  • 使用 Kotlin 协程管理长时间运行的任务和其他可以异步运行的操作。

6.生命周期感知型组件的用例

生命周期感知型组件可使您在各种情况下更轻松地管理生命周期。下面列举几个例子:

  • 在粗粒度和细粒度位置更新之间切换。使用生命周期感知型组件可在位置应用可见时启用细粒度位置更新,并在应用位于后台时切换到粗粒度更新。借助生命周期感知型组件 LiveData,应用可以在用户使用位置发生变化时自动更新界面。
  • 停止和开始视频缓冲。使用生命周期感知型组件可尽快开始视频缓冲,但会推迟播放,直到应用完全启动。此外,应用销毁后,您还可以使用生命周期感知型组件终止缓冲。
  • 开始和停止网络连接。借助生命周期感知型组件,可在应用位于前台时启用网络数据的实时更新(流式传输),并在应用进入后台时自动暂停。
  • 暂停和恢复动画可绘制资源。借助生命周期感知型组件,可在应用位于后台时暂停动画可绘制资源,并在应用位于前台后恢复可绘制资源。

7.处理 ON_STOP 事件

如果 Lifecycle 属于 AppCompatActivityFragment,那么调用 AppCompatActivityFragmentonSaveInstanceState() 时,Lifecycle 的状态会更改为 CREATED 并且会分派 ON_STOP 事件。

通过 onSaveInstanceState() 保存 FragmentAppCompatActivity 的状态后,其界面被视为不可变,直到调用 ON_START。如果在保存状态后尝试修改界面,很可能会导致应用的导航状态不一致,因此应用在保存状态后运行 FragmentTransaction 时,FragmentManager 会抛出异常。如需了解详情,请参阅 commit()

LiveData 本身可防止出现这种极端情况,方法是在其观察者的关联 Lifecycle 还没有至少处于 STARTED 状态时避免调用其观察者。在后台,它会在决定调用其观察者之前调用 isAtLeast()

遗憾的是,AppCompatActivityonStop() 方法会在 onSaveInstanceState() 之后调用,这样就会留下一个缺口,即不允许界面状态发生变化,但 Lifecycle 尚未移至 CREATED 状态。

为防止出现这个问题,beta2 及更低版本中的 Lifecycle 类会将状态标记为 CREATED 而不分派事件,这样一来,即使未分派事件(直到系统调用 onStop()),检查当前状态的代码也会获得实际值。

遗憾的是,此解决方案有两个主要问题:

  • 在 API 23 及更低级别,Android 系统实际上会保存 Activity 的状态,即使它的一部分被另一个 Activity 覆盖。换句话说,Android 系统会调用 onSaveInstanceState(),但不一定会调用 onStop()。这样可能会产生很长的时间间隔,在此时间间隔内,观察者仍认为生命周期处于活动状态,虽然无法修改其界面状态。
  • 任何要向 LiveData 类公开类似行为的类都必须实现由 Lifecycle 版本 beta 2 及更低版本提供的解决方案。

注意:为了简化此流程并让其与较低版本实现更好的兼容性,自 1.0.0-rc1 版本起,当调用 onSaveInstanceState() 时,会将 Lifecycle 对象标记为 CREATED 并分派 ON_STOP,而不等待调用 onStop() 方法。这不太可能影响您的代码,但您需要注意这一点,因为它与 API 26 及更低级别的 Activity 类中的调用顺序不符。

8.Lifecycle与协程

8.1导入依赖

对于 LifecycleScope,请使用 androidx.lifecycle:lifecycle-runtime-ktx:2.4.0 或更高版本。

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

8.2使用

为每个 Lifecycle 对象定义了 LifecycleScope。在此范围内启动的协程会在 Lifecycle 被销毁时取消。您可以通过 lifecycle.coroutineScope 或 lifecycleOwner.lifecycleScope 属性访问 Lifecycle 的 CoroutineScope。

以下示例演示了如何使用 lifecycleOwner.lifecycleScope 异步创建预计算文本:

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewLifecycleOwner.lifecycleScope.launch {
            val params = TextViewCompat.getTextMetricsParams(textView)
            val precomputedText = withContext(Dispatchers.Default) {
                PrecomputedTextCompat.create(longTextContent, params)
            }
            TextViewCompat.setPrecomputedText(textView, precomputedText)
        }
    }
}

8.3 可重启生命周期感知型协程

即使 lifecycleScope 提供了适当的方法以在 Lifecycle 处于 DESTROYED 状态时自动取消长时间运行的操作, 但在某些情况下,您可能需要在 Lifecycle 处于某个特定状态时开始执行代码块,并在其处于其他状态时取消。 例如,您可能希望在 Lifecycle 处于 STARTED 状态时收集数据流,并在其处于 STOPPED 状态时取消收集。 此方法仅在界面显示在屏幕上时才处理数据流发出操作,这样可节省资源并可能会避免发生应用崩溃问题。

对于这些情况,Lifecycle 和 LifecycleOwner 提供了挂起 repeatOnLifecycle API 来确切实现相应操作。 以下示例中的代码块会在每次关联的 Lifecycle 至少处于 STARTED 状态时运行, 并且会在 Lifecycle 处于 STOPPED 状态时取消运行:

class MyFragment : Fragment() {

    val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Create a new coroutine in the lifecycleScope
        viewLifecycleOwner.lifecycleScope.launch {
            // repeatOnLifecycle launches the block in a new coroutine every time the
            // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Trigger the flow and start listening for values.
                // This happens when lifecycle is STARTED and stops
                // collecting when the lifecycle is STOPPED
                viewModel.someDataFlow.collect {
                    // Process item
                }
            }
        }
    }
}

8.4 挂起生命周期感知型协程

即使 CoroutineScope 提供了适当的方法来自动取消长时间运行的操作,在某些情况下,您可能需要暂停执行代码块(除非 Lifecycle 处于特定状态)。例如,如需运行 FragmentTransaction,您必须等到 Lifecycle 至少为 STARTED。对于这些情况,Lifecycle 提供了其他方法:lifecycle.whenCreated、lifecycle.whenStarted 和 lifecycle.whenResumed。如果 Lifecycle 未至少处于所需的最低状态,则会挂起在这些块内运行的任何协程。

以下示例包含仅当关联的 Lifecycle 至少处于 STARTED 状态时才会运行的代码块:

class MyFragment: Fragment {
    init { // Notice that we can safely launch in the constructor of the Fragment.
        lifecycleScope.launch {
            whenStarted {
                // The block inside will run only when Lifecycle is at least STARTED.
                // It will start executing when fragment is started and
                // can call other suspend methods.

                //其中的块将只在Lifecycle至少是STARTED时运行。
                //当fragment被启动时,它将开始执行
                //可以调用其他挂起方法
                loadingView.visibility = View.VISIBLE
                val canAccess = withContext(Dispatchers.IO) {
                    checkUserAccess()
                }

                //当checkUserAccess返回时,下一行是自动的
                //如果生命周期不是*至少* STARTED,则挂起。
                //我们可以安全地运行片段事务,因为我们知道
                //除非生命周期至少是STARTED,否则代码不会运行。
                // When checkUserAccess returns, the next line is automatically
                // suspended if the Lifecycle is not *at least* STARTED.
                // We could safely run fragment transactions because we know the
                // code won't run unless the lifecycle is at least STARTED.
                loadingView.visibility = View.GONE
                if (canAccess == false) {
                    findNavController().popBackStack()
                } else {
                    showContent()
                }
            }

            // This line runs only after the whenStarted block above has completed.
            //这一行只在上面的whenStarted块完成后运行。
        }
    }
}

如果在协程处于活动状态时通过某种 when 方法销毁了 Lifecycle,协程会自动取消。 在以下示例中,一旦 Lifecycle 状态变为 DESTROYED,finally 块即会运行:

class MyFragment: Fragment {
    init {
        lifecycleScope.launchWhenStarted {
            try {
                // Call some suspend functions.
            } finally {
                // This line might execute after Lifecycle is DESTROYED.
                if (lifecycle.state >= STARTED) {
                    // Here, since we've checked, it is safe to run any
                    // Fragment transactions.
                }
            }
        }
    }
}

注意:尽管这些方法为使用 Lifecycle 提供了便利,但只有当信息在 Lifecycle 的范围(例如预计算文本)内有效时才应使用它们。 请注意,协程不会随着 activity 重启而重启。

警告:倾向于使用 repeatOnLifecycle API 收集数据流,而不是在 launchWhenX API 内部进行收集。 由于后面的 API 会挂起协程,而不是在 Lifecycle 处于 STOPPED 状态时取消。 上游数据流会在后台保持活跃状态,并可能会发出新的项并耗用资源。