稀有猿诉

十年磨一剑,历炼出锋芒,说话千百句,不如码二行。

降Compose十八掌之『羝羊触蕃』| Handle Platform Lifecycles

Jetpack Compose是一个独立的声明式UI框架,它并不受限于任何操作系统平台,从框架定位的角度来讲,它是跨平台的,也应该要跨平台。但是我们的应用程序必然是为某些操作系统平台(后面简称平台Platform)构建的,也就是说要运行在某些平台上面。这就免不了要与平台进行打交道。这篇文章将以Android平台为例,学习在Compose中如何处理平台的生命周期事件。

感知平台生命周期事件

对于移动应用程序来说,感知平台的生命周期是非常重要的,比如最为典型的场景,对于一个新闻消息类的应用来说,当首次进入页面的时候肯定 要刷新拉取最新的消息,当用户切换到另外一个应用时,比如接了个电话,或者分享,之后再回到你的应用页面,这时也应该主动刷新消息,而不是要等着用户手动的去点击刷新按扭;再比如说当使用了硬件资源(位置,Camera或者Sensors等)时,更是要当离开应用页面的时候就应该立即释放硬件,以停止对硬件资源的占用。

从前面的文章降Compose十八掌之『损则有孚』| Lifecycle中我们了解到Composable本身的生命周期与平台是无关的且非常不一致,光靠Compose自己的节奏是无法感知到在平台生命周期事件的。这就需要我们使用一些桥接工具来感知平台生命周期事件,以能让我们针对感兴趣的事件执行一些操作。

生命周期事件副作用函数(LifecycleEffects)

幸运的是Jetpack组件中的lifecycle已经添加了对Compose的支持,定义了一些生命周期副作用函数),在这些副作用函数中可以针对 不同的事件设置代码块,当相应的生命周期发生时就会执行这些代码块:

1
2
3
LifecycleEventEffect(Lifecycle.Event.ON_START) {
  // onStar时执行一些操作
}

上面的代码就是指定要在onStart时做一些事情。需要注意的是,无法监听onDestroy(即事件Lifecycle.Event.ON_DESTROY),因为Compose的组合会在onDestroy之前就结束了。

除了上面的用法之外,还有更为为方便的LifecycleStartEffect)和LifecycleResumeEffect)可以直接使用,它们是针对onStart/onStop和onResume/onPause两对事件的,因为生命周期中最为常用的就是这四个事件了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LifecycleStartEffect {
  // onStart中需要做的事情

  onStopOrDispose {
    // onStop需要做的事情
  }
}

LifecycleResumeEffect {
  // onResume需要做的事情

  onPauseOrDispose {
    // onPause需要做的事
  }
}

需要注意,这两个副作用函数是针对事件对的,也就是说必须要带着后面的onStopOrDispose和onPauseOrDispose。如果仅对onStart感兴趣,而无须在onStop中做清理,那么应该直接使用LifecycleEventEffect(Lifecycle.Event.ON_START) {}(对于onResume也是同理)。

扩展阅读:

监听生命周期事件

除了直接使用生命周期副作用函数以外,也可以用lifecycle原生的方式,直接向LifecycleOwner注册一个LifecycleEventObserver来监听生命周期。通过Compose提供的LifeCycleOwner.current可以获得当前的LifecycleOwner,然后向其注册一个LifecycleEventObserver,当平台生命周期发生变化时,就会带着事件类型回调给监听者,监听者可以针对感兴趣的事件做操作。还需要注意的是,需要在组合结束(离开)时反注册observer,因此这里要用DisposableEffect。对于副作用函数不熟悉的同学可以去复习一下降Compose十八掌之『龙战于野』| Side Effects

来看个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_CREATE -> { /* onCreate */ }
                Lifecycle.Event.ON_START -> { /* onStart */ }
                Lifecycle.Event.ON_RESUME -> { /* onResume */ }
                Lifecycle.Event.ON_PAUSE -> { /* onPause */ }
                Lifecycle.Event.ON_STOP -> { /* onStop */ }
                Lifecycle.Event.ON_DESTROY -> { /* onDestroy */ }
                Lifecycle.Event.ON_ANY -> { /* Any event */ }
                else -> {}
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }

这样就可以监听到生命周期事件,然后针对不同的事件做相应的操作。

当然,如果事件不止做一件事情,或者说对事件的响应不光光是执行一些函数,可能还会有页面的修改,那么这时最好就是把事件保存为一个状态(State),更为方便:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
val lifecycleOwner = LocalLifecycleOwner.current

var lifecycleEvent by remember { mutableStateOf(Lifecycle.Event.ON_ANY) }

DisposableEffect(lifecycleOwner) {
    val observer = LifecycleEventObserver { _, event ->
        lifecycleEvent = event
    }

    lifecycleOwner.lifecycle.addObserver(observer)

    onDispose {
        lifecycleOwner.lifecycle.removeObserver(observer)
    }
}

LaunchedEffect(lifecycleEvent) {
    if (lifecycleEvent == Lifecycle.Event.ON_RESUME) {
        viewModel.refresh()
    }
}

Column() {
    if (lifecycleEvent == Lifecycle.Event.ON_RESUME) {
        Text("Welcome back")
    }
}

扩展阅读:

以数据流的方式来处理生命周期事件

生命周期是由系统控制,不时发生变化,每次变化会向监听者回调一个事件,如果以一定的时间跨度来看待,这些事件就形成了一个数据流。因此,Lifecycle还提供了一个Flow接口,用以发送Lifecycle事件。可以当作状态(State)来收集此Flow,这样事件的变化就能驱动Compose的重组,进而感知到最新的生命周期事件:

1
2
3
4
5
6
7
val lifecycleOwner = LocalLifecycleOwner.current
val stateFlow = lifecycleOwner.lifecycle.currentStateFlow
val currentLifecycleState by stateFlow.collectAsState()

// 或者
val lifecycleOwner = LocalLifecycleOwner.current
val currentLifecycleState = lifecycleOwner.lifecycle.currentStateAsState()

注意: 对于Flow不熟悉的同学可以复习一下包教包会的Kotlin Flow教程

不要在ViewModel中感知生命周期

根据现代安卓开发架构原则,ViewModel应该处理与UI相关的业务逻辑,它应该独立于平台,因此,千万不要在ViewModel去感知生命周期,事实上你也做不到,因为ViewModel是没任何对平台的依赖的,非常独立的一个类型,也即拿不到LifecycleOwner。

当然了,有同学说,我可以从Compose的Composable中把LifecycleOwner当作参数传给ViewModel,但仍然强烈不建议这样做。深层的原因在于,ViewModel是独立于平台的,它有自己的生命周周期,平台的组件(如Activity)是由系统控制的,但ViewModel是由我们自己控制的,它的生命周期要长于平台的组件,也就是说ViewModel的生命周期要长于它持有的LifecycleOwner,故LifecycleOwner可能会变得过时(非当前的Activity了),同时因为被更长生命的ViewModel持有,原LifecycleOwner可能无法被回收而引发内存泄漏。

ViewModel只应该负责业务逻辑相关的事情,在Composable中监听生命周期事件很方便,也很合适,然后调用ViewModel的相应的接口(如refresh())即可。

总结

得益于Jetpack中的Lifecycle组件,在Compose中感知生命周期没有想像中的那样难。在实际项目中,推荐使用更符合Compose的方式,也即生命周期副作用函数以及事件数据流。如果仅是在某些生命周期事件发生时执行一些操作,那就用LifecycleEventEffect函数;如果不止一处需要使用事件,那就用事件数据流。

Comments