现代Android架构(一):onResume 懒加载到 repeatOnLifecycle

6 阅读6分钟

Gemini_Generated_Image_qoql4oqoql4oqoql.png

一、背景:为什么我们需要 repeatOnLifecycle?

在传统 Fragment 懒加载中,我们常这样写:

override fun onResume() {
    super.onResume()
    requestData()
}

或者:

lifecycleScope.launchWhenStarted {
    flow.collect { render(it) }
}

看似正常,但在真实项目中会遇到:

  • 页面多次进入重复请求

  • Fragment View 销毁后仍在收集

  • 协程未正确取消导致泄漏

  • 接口被重复调用

  • UI 渲染错乱

在复杂业务场景下,这些问题会直接影响稳定性。

Jetpack Lifecycle 2.4.0 开始,官方推荐使用:

repeatOnLifecycle

二、repeatOnLifecycle 本质是什么?

一句话总结:

它是一个“生命周期驱动的协程取消 + 重建机制”。

函数签名:

public suspend fun Lifecycle.repeatOnLifecycle(
    state: Lifecycle.State,
    block: suspend CoroutineScope.() -> Unit
)

关键点:

  1. 它是 suspend 函数
  2. block 自带 CoroutineScope
  3. 生命周期降级时取消协程
  4. 生命周期升级时重新执行 block

三、为什么必须放在 launch 里?

因为它是 suspend 函数:

viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        flow.collect { render(it) }
    }
}

不能直接调用:

repeatOnLifecycle { } // 编译错误

四、底层原理(核心)

repeatOnLifecycle 内部做了 4 件事:

1️⃣ 注册 LifecycleObserver

2️⃣ 生命周期 ≥ 目标状态 → 启动子协程

3️⃣ 生命周期 < 目标状态 → 取消子协程

4️⃣ 使用 coroutineScope 保证结构化并发

简化模型:

coroutineScope {
    val observer = LifecycleEventObserver { _, _ ->
        if (currentState >= targetState) {
            launch { block() }
        } else {
            childJob?.cancel()
        }
    }
}

它不是挂起恢复模型,而是:

取消 + 重建模型

这比 launchWhenStarted 更安全。


五、它和 launchWhenStarted 的区别

launchWhenStartedrepeatOnLifecycle
生命周期变化挂起取消
再进入从挂起点继续重新执行 block
子协程管理不严格结构化并发
官方推荐

核心差异:

launchWhenX 是“暂停恢复”

repeatOnLifecycle 是“取消重建”

取消比挂起更安全,因为子协程会被彻底销毁,不用担心恢复时生命周期view销毁引发的异常崩溃。

六、错误示例:不要在里面直接写网络请求

错误写法:

repeatOnLifecycle(STARTED) {
    val data = repository.request()
    render(data)
}

问题:

  • 每次回到 STARTED 都会重新请求

  • 切后台再回来 → 再请求

  • 旋转屏幕 → 再请求

repeatOnLifecycle 负责的是“安全收集”,

不应该承担“业务触发”。

七、正确结构:用 repeatOnLifecycle 替代 onResume 懒加载

我们最开始的问题是:

override fun onResume() {
    super.onResume()
    requestData()
}

问题在于:

  • View 销毁后仍可能继续执行
  • 容易重复请求
  • 与协程结构不匹配
  • 不符合现代 UDF 架构

✅ 正确做法:ViewModel 负责请求

class MyViewModel : ViewModel() {

    private val _data = MutableStateFlow<Data?>(null)
    val data: StateFlow<Data?> = _data

    fun load() {
        viewModelScope.launch {
            _data.value = repository.request()
        }
    }
}

说明:

  • 网络请求在 ViewModel
  • 数据通过 StateFlow 暴露
  • UI 不直接持有业务逻辑

✅ Fragment:用 repeatOnLifecycle 替代 onResume

class MyFragment : Fragment(R.layout.fragment_my) {

    private val viewModel: MyViewModel by viewModels()

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

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {

                // 等价于 onResume 的懒加载行为
                viewModel.load()

                // 生命周期安全收集数据
                viewModel.data.collect { data ->
                    render(data)
                }
            }
        }
    }

    private fun render(data: Data?) {
        // 更新 UI
    }
}

为什么这等价于 onResume?

因为:

  • Fragment 可见时生命周期 ≥ STARTED

  • 切后台时生命周期 < STARTED

  • 再回来时重新进入 STARTED

而 repeatOnLifecycle 的机制是:

每次进入 STARTED 都重新执行 block

因此:

viewModel.load()

会在以下场景执行:

  • 页面首次显示

  • 切后台再回来

  • 从回退栈返回

  • 屏幕旋转重建

这就是“安全版 onResume”。


如果只想首次加载一次怎么办?

那就不要把 load() 放在 repeatOnLifecycle 里:

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

    // 只执行一次
    viewModel.load()

    viewLifecycleOwner.lifecycleScope.launch {
        viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
            viewModel.data.collect { render(it) }
        }
    }
}
需求写法
每次页面可见刷新(等价 onResume)在 repeatOnLifecycle 里调用 load()
只首次加载一次在 onViewCreated 里调用 load()

这样结构非常清晰:

  • repeatOnLifecycle 负责生命周期安全

  • ViewModel 负责请求

  • UI 负责收集

  • 不再使用 onResume

  • 不再使用 launchWhenX

逻辑干净、边界清晰。

八、为什么它和 Flow 天然契合?

Flow 是“数据流”,不是“任务”。

StateFlow 特性:

  • 永远持有最新值

  • 可重复订阅

  • 生命周期重新 START 时会立即发出当前值

当 STOP:

  • collect 被取消

当重新 START:

  • 重新 collect
  • 立即拿到最新状态
  • 不会重复请求(因为请求不在 UI 层)

九、Fragment 中的重要细节

错误写法:

lifecycleScope.launch {
    repeatOnLifecycle(STARTED) { }
}

正确写法:

viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(STARTED) { }
}

原因:

Fragment 生命周期 ≠ View 生命周期。

当 Fragment 进入回退栈:

  • onDestroyView 执行(View 销毁)

  • Fragment 本身仍是 STARTED

如果使用 lifecycleScope,协程仍然运行,访问 View 会 Crash。

必须使用 viewLifecycleOwner。

十、自定义 LifecycleOwner(进阶)

只要实现 LifecycleOwner,就可以使用 repeatOnLifecycle。

这意味着:

repeatOnLifecycle 并不依赖 Fragment 或 Activity。


1️⃣ 自定义 LifecycleOwner

class MyLifecycleOwner : LifecycleOwner {

    private val registry = LifecycleRegistry(this)

    override fun getLifecycle(): Lifecycle = registry

    fun onCreate() {
        registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
    }

    fun onStart() {
        registry.handleLifecycleEvent(Lifecycle.Event.ON_START)
    }

    fun onResume() {
        registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
    }

    fun onPause() {
        registry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    }

    fun onStop() {
        registry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
    }

    fun onDestroy() {
        registry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    }
}

这里我们手动驱动生命周期。

2️⃣ 在普通类中使用 repeatOnLifecycle

class TestComponent {

    private val lifecycleOwner = MyLifecycleOwner()

    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

    fun start() {

        scope.launch {
            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                println("进入 STARTED,开始执行任务")

                try {
                    while (true) {
                        delay(1000)
                        println("任务执行中...")
                    }
                } finally {
                    println("生命周期降级,任务被取消")
                }
            }
        }

        // 手动驱动生命周期
        lifecycleOwner.onCreate()
        lifecycleOwner.onStart()
    }

    fun stop() {
        lifecycleOwner.onStop()
    }

    fun destroy() {
        lifecycleOwner.onDestroy()
        scope.cancel()
    }
}

3️⃣ 运行逻辑说明

当执行:

val component = TestComponent()
component.start()

输出:

进入 STARTED,开始执行任务
任务执行中...
任务执行中...

当执行:

component.stop()

输出:

生命周期降级,任务被取消

再次执行:

component.start()

任务会重新启动。

这块有些同学可能会困惑。为什么调用 onStop()(ON_STOP 事件)会让 STARTED → CREATED。 先记住Android 生命周期状态的层级(从低到高)

WechatIMG6.jpg 核心规则:每个事件(Event)都会固定把状态切换到对应的层级,且状态只能 “降级” 或 “升级”,不能跳级。

LifecycleRegistry 收到 ON_STOP 事件后,会按照 Android 内置的规则,把当前状态从 STARTED(对应 ON_START/ON_PAUSE)强制切换到 CREATED(因为 ON_STOP 事件的唯一对应状态就是 CREATED)。

repeatOnLifecycle 的核心机制:当生命周期状态低于指定的 State(这里是 STARTED)时,会自动取消当前协程

协程被取消时,delay(1000)这个挂起函数会抛出 CancellationException,触发 try 块的中断,进而执行 finally 块里的代码。但是这个异常会被repeatOnLifecycle捕获,不会触发崩溃。

类似能抛出CancellationException的挂起函数还包括Flow.collect(),withContext()/async(),网络请求、数据库查询等封装的挂起函数(如 Retrofit 的 suspend 接口)。

而普通的非挂起函数(比如 println()for 循环)不会主动检测取消状态,此时即使协程被取消,Thread.sleep 也不会抛异常,while 循环会一直执行,finally 块永远不会触发 —— 这也是为什么协程取消是 “协作式” 的,必须通过可取消挂起函数配合。

这个例子说明什么?

说明:

  • repeatOnLifecycle 本质只是监听 Lifecycle
  • 它和 Fragment 无关
  • 只要有 LifecycleOwner 就可以使用
  • 生命周期变化会自动取消并重建协程

实际应用场景

这种模式常用于:

  • 自定义 View 容器
  • 播放器组件
  • SDK 封装
  • 跨端桥接
  • 非 Activity / Fragment 场景

十一、总结

repeatOnLifecycle 优化的不是 CPU 性能。

它优化的是:

  • 生命周期安全

  • 协程结构管理

  • 重复订阅控制

  • 稳定性

它代表的是:

  • 取消重建模型

  • 结构化并发

  • 单向数据流

真正理解 repeatOnLifecycle,

才算真正理解现代 Android 生命周期与协程协作模型。