Jetpack Compose中onDispose的异常行为疑问
我的预期行为:
- 首次组合时,
factory回调执行并将view赋值为非null的View实例; - 重组时,
var view: View? = null会重新执行,将view重置为null。
实际表现:
SideEffect和update回调中view确实为null;- 离开页面触发
onDispose时,view却仍为factory中赋值的View实例。
想了解:
- Compose编译器是否对
onDispose做了特殊包装? - 为何仅
onDispose保留该值,而其他副作用不保留? - 已知用
var view: View? by remember { mutableStateOf(null) }可符合预期,但想明白onDispose保留值的根本原因。
相关代码:
@Composable fun AndroidViewContainer(modifier: Modifier = Modifier, int: () -> Int) { int() // for causing recomposition var view: View? = null SideEffect { println("Log sideeffect $view") } DisposableEffect(Unit) { onDispose { println("Log detached $view") } } AndroidView( modifier = modifier, factory = { context -> View(context).also { println("Log factory") view = it } }, update = { println("Log: $view") } ) }
核心原因:DisposableEffect的生命周期绑定与捕获值的时机
Compose并没有对onDispose做特殊包装,本质是DisposableEffect的回调捕获变量的时机和其他副作用不同:
DisposableEffect的创建与回调捕获时机:
当DisposableEffect首次执行(首次组合)时,它会捕获当前作用域内的view变量引用——此时view已经被factory赋值为实际的View实例。之后这个onDispose回调会被Compose保存下来,直到组件被销毁时才触发执行。重组时的局部变量重新初始化:
每次重组时,var view: View? = null都会重新声明一个全新的局部变量,和首次组合时的view不是同一个引用。SideEffect和AndroidView的update回调会在每次重组时重新捕获这个新的、值为null的变量,所以打印结果为null。但之前保存的onDispose回调仍然持有首次组合时捕获的旧view引用,自然能输出当时的实例。remember的作用逻辑:
用remember包裹的变量会被保存在Compose的状态容器中,重组时不会重新创建新变量,而是复用同一个引用。不管是首次组合还是重组,所有回调捕获的都是同一个view变量,所以onDispose时拿到的就是正确的最新值。
简单总结:onDispose回调是在首次创建DisposableEffect时捕获了当时的view变量,后续重组中新生成的view变量和它无关;而SideEffect和update每次重组都会重新捕获新的局部变量,因此表现出差异。
内容的提问来源于stack exchange,提问作者Kacper Kalinowski

