本文主要介绍remember、retain、rememberSaveable、rememberSerializable,以及ViewModel 的用法,以及他们之间的区别,何时使用。
1、remember
作用域限定在组合的对象,重组后继续存在,不能在activity重建后继续存在,不能在进程中止后继续存在。
创建内部对象,避免每次重组是创建巨大开销的对象。
@Composable
private fun rememberState() {
val count = remember { mutableStateOf(0) }
var counter = remember { mutableStateOf(Counter()) }
Column {
Text(
text = "login in with email ${count.value}", // 会导致重组
style = TextStyle.Default,
modifier = Modifier
.padding(vertical = 20.dp)
.clickable {
count.value++
}
)
Button(onClick = {
counter.value.num ++ // 不会导致重组
}) {
Text("存储普通对象 : ${counter.value.num}")
}
}
}
class Counter(var num : Int = 0)
2、retain
重组后对象继续存在,旋转activity重建后对象存在,不能在进程中止后继续存在。
适合创建成本高且不可序列化的对象,如播放器Player、Bitmap、图片缓存对象等
class RetainPalyer {
val scope = CoroutineScope(Dispatchers.IO + Job())
var playCount = 0
init {
Log.d("RetainPalyer", "创建实例:${this.hashCode()}")
}
fun play() {
playCount++
Log.d("RetainPalyer", "播放,次数:$playCount")
}
fun release() {
scope.cancel()
Log.d("RetainPalyer", "释放资源:${this.hashCode()}")
}
}
@Composable
fun RetainDemoPage() {
Column(modifier = Modifier.padding(20.dp)) {
Text(text = "retain vs remember 实例对比\n旋转屏幕查看日志hashCode")
Spacer(modifier = Modifier.height(16.dp))
val rememberPlayer = remember { RetainPalyer() }
val retainPlayer = retain { RetainPalyer() }
DisposableEffect(Unit) {
onDispose {
rememberPlayer.release()
retainPlayer.release()
}
}
Button(onClick = { rememberPlayer.play() }) {
Text("remember播放器播放")
}
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { retainPlayer.play() }) {
Text("retain播放器播放")
}
Spacer(modifier = Modifier.height(24.dp))
Text("=== 实例地址 ===")
Text("remember hash: ${rememberPlayer.hashCode()}")
Text("retain hash: ${retainPlayer.hashCode()}")
}
}
旋转之前

image.png
旋转之后

image.png
可以看到旋转之后,retainPlayer没有重新创建。
2.1、同一个activity,验证跳转到另一个compose页面,retain对象是否相同。
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun MainTab(receiver : String) {
Box(modifier = Modifier.fillMaxWidth()) {
Scaffold(
topBar = {
TopAppBar(title = { Text(text = "页面2: ") }, navigationIcon = {})
},
content = { it ->
Column(
modifier = Modifier.padding(it),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
val retainPlayer = retain { RetainPalyer() }
val retainPalyer1 = retain { RetainPalyer() }
Text(text = AnnotatedString(text = "retainPlayer hash: ${retainPlayer.hashCode()}"), modifier = Modifier.padding(horizontal = 14.dp, vertical = 14.dp) )
Text(text = AnnotatedString(text = "retainPlayer1 hash: ${retainPalyer1.hashCode()}"), modifier = Modifier.padding(horizontal = 14.dp, vertical = 14.dp) )
Text(text = AnnotatedString(text = "从上一个页面传递的hashCode ${receiver}"), modifier = Modifier.padding(horizontal = 14.dp, vertical = 14.dp))
}
}
)
}
}

image.png
从上图可以看出,同一个compose页面创建的RetainPalyer创建的是不同的对象。不同compose页面创建RetainPalyer也不是同一个对象。retain只是在屏幕旋转时,同一位置从activity中恢复的对象是同一个。
3、rememberSaveable
重组后对象存在,旋转activity后对象存在,进程中止后继续存在。
老版本的API,它通过 Android 的 SavedStateRegistry 保存数据,是往 Bundle 里面存数据,适合简单的数据,需要自定义Saver。
class User {
var name: String = ""
var age: Int = 0;
constructor(name: String, age: Int) {
this.age = age
this.name = name
}
companion object {
val stateSaver: Saver<MutableState<User>, List<Any?>> = Saver(
save = { state -> listOf(state.value.name, state.value.age) },
restore = { mutableStateOf(User(it[0] as String, it[1] as Int)) }
)
}
}
@Composable
private fun remeberSave() {
var user = rememberSaveable(saver = User.stateSaver) {
mutableStateOf(User("zhangsan", 18))
}
var count = rememberSaveable { mutableIntStateOf(0) }
Column {
Text(
text = "保存的age值 ${user.value.age}", modifier = Modifier.clickable {
user.value = User("lisi", 19)
}
)
Text(
text = "savecount count = ${count.value}", modifier = Modifier
.clickable {
count.value++
}
.absolutePadding(top = 10.dp))
}
}
4、rememberSerializable
新版本的API,自动序列化对象,适合存储复杂的数据,比如List<Any>等。
@Serializable
class Person (val name:String,var age : Int){
}
var person by rememberSerializable { mutableStateOf(Person("abc", 190)) }

image.png
5、ViewModel
在ComposeA中操作UI,当从ComposeA跳转到ComposeB,ComposeA里面保留的数据就被销毁了,当再次返回时,会重组。
5.1、将状态保存在remember中,可以看到返回时,重组创建了新的对象。

remember

image.png
5.2、将状态保存在ViewModel中,可以看到返回时,还是原来的ViewModel对象。

viewmodel

打印
总结:remember 的生命周期跟随 Composition,而 ViewModel 的生命周期跟随 ViewModelStoreOwner。每一个Composition都有一个ViewModelStoreOwner。