Android Compose 可组合项的生命周期、副作用API 了解到,
LaunchedEffect 会在组合进入时,启动一个协程…
DisposableEffect 会在组合退出时,执行 onDispose 函数

现在要讲的是: 通过 rememberretain 保存的状态对象,可分别搭配 RememberObserverRetainObserver 接口,实现感知 – 该对象在组合层次结构中,何时开始和停止被记住

remember+RememberObserver

实现示例1

若有个 媒体播放业务类:

class VideoPlayerManager {
    fun initialize() {
        Log.i("VideoPlayer", "VideoPlayer: 开始加载视频并准备播放...")
    }

    fun release() {
        Log.i("VideoPlayer", "VideoPlayer: 停止播放,释放解码器和内存...")
    }

    fun play() {
        Log.i("VideoPlayer", "VideoPlayer: 开始播放...")
    }
}

composable 中,需要记住它的状态;并且,组合进入/退出时,能自动调用关联方法
remember + RememberObserver

@Composable
fun rememberVideoPlayerManager(): VideoPlayerManager {
    // 不要公开暴露实现了 RememberObserver 的类
    // 匿名内部类,隐藏 RememberObserver 的实现细节
    return remember {
        object : RememberObserver {
            val manager = VideoPlayerManager()

            // 成功进入 Compose 树时
            override fun onRemembered() {
                manager.initialize()
            }

            // 正常离开 Compose 树时
            override fun onForgotten() {
                manager.release()
            }

            // 重组被取消,根本没进入树时
            override fun onAbandoned() {
                // 如果 manager 的构造函数里分配了需要释放的资源,在这里释放。
                // 如果只是简单的对象创建,通常不需要做什么。
                Log.i("VideoPlayer", "onAbandoned")
            }
        }
    }.manager // 只把普通的 Manager 返回给外界
}

Composable 界面中,点击按钮进行播放:

@Composable
fun VideoScreen() {
    val playerManager = rememberVideoPlayerManager()

    // UI 层只需要正常使用 playerManager 即可,不需要关心它的生命周期释放问题
    Button(modifier = Modifier.padding(top = 100.dp), onClick = {
        playerManager.play()
    }) {
        Text("Play")
    }
}
当调用 `VideoScreen()`,进入组合时,输出 `VideoPlayer: 开始加载视频并准备播放`
点击 play 按钮,输出  `VideoPlayer: 开始播放`
退出该界面,输出 `VideoPlayer: 停止播放,释放解码器和内存`

实现示例2:使用协程

业务类中使用协程:

class MyDataController {
    // 创建一个受控的 Job
    private var job: Job? = null
    // 使用主线程调度器创建作用域
    private var coroutineScope: CoroutineScope? = null

    fun onStart() {
        job = Job()
        coroutineScope = CoroutineScope(Dispatchers.Main + job!!)

        // 在 onRemembered (对应这里的 onStart) 中启动协程
        coroutineScope?.launch {
        		Log.i("controller", "onStart loadData")
            loadData()
        }
        Log.i("controller", "onStart")
    }

    fun onStop() {
        // 在 onForgotten 或 onAbandoned 中取消协程
        job?.cancel()
        job = null
        coroutineScope = null
        Log.i("controller", "onStop")
    }

    fun test() {
        Log.i("controller", "test")
    }

    private suspend fun loadData() {
        // 模拟网络请求
        delay(2000)
        println("数据加载完成")
    }
}

composable 中,需要记住它的状态;并且,组合进入/退出时,能自动调用 onStartonStop 方法
remember + RememberObserver

@Composable
fun rememberDataController(): MyDataController {
    return remember {
        object: RememberObserver {
            val controller = MyDataController()
            override fun onRemembered() { // 进入
                controller.onStart()
            }

            override fun onForgotten() { // 离开
                controller.onStop()
            }

            override fun onAbandoned() { // 中断
                Log.i("controller", "onAbandoned")
            }
        }/*.controller*/
    }.controller
}

Composable 界面中的使用和之前是类似的(代码略)。

注意:若用成 `RememberObserver {}.controller;那最终的语义就是 remember { controller },即 就是 普通的 remember {},也就不会有感知等关联方法被调用了

retain+RetainObserver

retain 的生命周期会比 remember 更长,能在 配置更改时,保留状态。

复用上面的 MyDataController,提供 retain+RetainObserverComposable 函数。
RetainObserver 内部维护一个更长生命的 CoroutineScope

@Composable
fun retainDataController(): MyDataController {
    return retain {
        object: RetainObserver {
			val controller = MyDataController()
            private var scope: CoroutineScope? = null

            override fun onEnteredComposition() { // 进入
                Log.i("retain", "onEnteredComposition")
                controller.onStart()
            }

            override fun onExitedComposition() { // 离开
                Log.i("retain", "onExitedComposition")
                controller.onStop()
            }

            override fun onRetained() { // 最先执行 保留:启动/建立资源
                Log.i("retain", "onRetained: 创建长期资源")

                // SupervisorJob() 子协程隔离:一个子协程异常取消后,不会影响其它子协程继续执行
                // 普通的 Job, 同样情形下会中断属于该作用域的所有协程
                scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
                scope?.launch {
                    Log.i("retain", "开始预加载数据")
                }
            }

            override fun onRetired() { // 退休了: 释放正常使用过的资源
                Log.i("retain", "onRetired")
                scope?.cancel()
            }

            override fun onUnused() { // 未使用: 对象创建出来了,但最终根本没进入“被保留”状态
                Log.i("retain", "onUnused")
                scope?.cancel()
            }
        }
    }.controller
}

Composable 界面中的使用和之前是类似的(代码略)。

RetainObserver 内部的 CoroutineScope 生命更长? 因为它在 onRetained() 被保留回调中执行
Dispatchers.Main + SupervisorJob() 表示该协程运行在主线程;且该协程内部的子协程,若是崩溃导致取消,不会影响其它子协程继续执行

RetainObserver 中的回调方法执行顺序:

  • 初始执行 onRetained onEnteredComposition

接着日志输出 controller的 onStart (因为这是在主线程执行)
再执行 onRetained 中的协程,输出 “开始预加载数据”
再执行 controller的 onStart 中的协程,输出 “onStart loadData”,此协程执行完成,输出 “数据加载完成”

  • 配置更改后执行 onExitedComposition onRetired onRetained onEnteredComposition

onExitedComposition 内 执行 controller的 onStop
之后的日志输出同上面一样

  • 退出界面执行 onExitedComposition onRetired

onExitedComposition 内 执行 controller的 onStop

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐