Jetpack Compose开启不保留活动时observeAsState()数据丢失如何解决
开了「不保留活动」后,应用切后台当前Activity会被系统立刻销毁,绑定在这个Activity上的ViewModel实例也会跟着生命周期走销毁流程,下次切回是全新的Activity+全新的ViewModel实例。你代码里的selectedMovie是ViewModel里的普通LiveData,没有做状态留存,重建后自然回到初始空值,observeAsState()读出来是null,图片当然加载不出来。
普通ViewModel本身只能扛住转屏、深色模式切换这类配置变更的重建,扛不住系统主动杀Activity、杀进程的场景,要覆盖这两类场景,按下面的方案改就行。
方案1:用SavedStateHandle存页面状态
这是官方提供的ViewModel扩展能力,状态会自动写入Activity的保存状态Bundle,重建时自动读出来填回ViewModel,不用自己写状态读写逻辑,适配成本最低。
首先改ViewModel的构造,直接注入SavedStateHandle,把需要留存的状态托管给它:
// 不需要自己写ViewModelFactory,androidx.activity和navigation的viewModel()扩展默认支持SavedStateHandle注入 class MoviesViewModel( savedStateHandle: SavedStateHandle ) : ViewModel() { // 直接从handle拿可观察的LiveData,状态自动持久化、自动恢复 private val _selectedMovie = savedStateHandle.getLiveData<Movie>("key_selected_movie") val selectedMovie: LiveData<Movie> = _selectedMovie // 原有选中电影的更新逻辑不用改,直接赋值即可,SavedStateHandle会自动把值存下来 fun selectMovie(movie: Movie) { _selectedMovie.value = movie } }
使用注意:
- 如果Movie类实现了
Parcelable或者Serializable可以直接存,单条数据不要超过1M——这是Binder事务缓冲区的上限,超了会抛TransactionTooLargeException - 如果电影对象结构复杂、或者数据量大,别直接塞整个对象,存对应电影的唯一ID就行,ViewModel初始化的时候拿ID从仓库层重新拉取完整电影数据
方案2:对接本地持久化数据源
如果你的业务本身需要把数据留存到应用退出之后(比如用户下次打开APP还要看到上次浏览的电影),直接把选中状态、电影列表这类数据存在Room、DataStore或者其他本地存储里。ViewModel初始化的时候优先从本地读数据,不管是配置变更、Activity被系统回收还是进程被杀,重启后都能读到正确状态。
这种方案适合数据量大、需要长期留存的场景,注意本地读写要放子线程,别卡主线程。
额外的图片加载优化
你现在给Coil的AsyncImage只开了内存缓存,Activity销毁后内存会被回收,内存缓存自然失效,重建时会重新走网络拉图片。建议加上磁盘缓存配置,重建时可以直接读本地缓存的图片,省流量加载也更快:
AsyncImage( model = ImageRequest.Builder(LocalContext.current) .data(selectedMovie?.getBackdropUrl()) .placeholder(R.drawable.ic_baseline_movie_24) .crossfade(true) .memoryCachePolicy(CachePolicy.ENABLED) // 开启磁盘缓存 .diskCachePolicy(CachePolicy.ENABLED) .build(), contentDescription = stringResource(R.string.app_name), contentScale = ContentScale.Crop, )
补充:别直接用
rememberSaveable存复杂的Movie对象,它底层走的和SavedStateHandle一样的Bundle存储逻辑,大数据量同样会触发传输超限问题。简单的标识位、ID这类小数据用rememberSaveable存没问题,复杂业务数据优先存主键、再从数据源拉取完整内容。
内容的提问来源于stack exchange,提问作者Sjaak Rusma

