当前位置: 首页 > news >正文

android中ViewModel 和 onSaveInstanceState 的最佳使用方法

核心区别:设计目的与生命周期

特性ViewModelonSaveInstanceState
设计目的持有和管理UI相关的数据保存和恢复短暂的UI状态
数据范围“大”数据:复杂对象、列表、网络请求结果、数据库查询结果等。“小”数据:简单的、可序列化/可打包的数据(Primitive类型、String、Parcelable等)。
生命周期存活于配置更改(如屏幕旋转)。不存活于进程终止(如用户离开应用后系统为回收内存而杀死应用)。存活于配置更改和进程终止。数据被写入磁盘(Bundle),即使应用被杀死也能恢复。
存储位置内存(RAM)中。访问速度极快。序列化后写入磁盘(Bundle)。读写有开销。
适用场景保持屏幕旋转时的数据;在Fragment间共享数据;作为数据层的“前台缓存”。保存滚动位置、文本框中的临时输入、选中的ID等,确保即使在应用被杀死后也能完美还原用户体验。

一个绝佳的类比:
想象在电脑上写文档。

  • ViewModel 就像 RAM。正在编辑的整个文档都在里面,操作非常快。但如果突然断电(进程被杀死),所有没保存的东西就丢了。
  • onSaveInstanceState 就像 按 Ctrl+S 快速保存。不会保存整个文档,而是会保存一些关键信息(比如光标位置、最近编辑的段落)。即使断电,重启后也能迅速恢复到刚才的状态。

最佳实践与使用方法

核心思想:二者不是替代关系,而是互补关系。 应该结合使用它们来打造最佳用户体验。

  1. 使用 ViewModel 作为数据的“单一信源”

    • 所有核心数据(从Repository、UseCase获取的数据)都应存放在 ViewModel 中。
    • UI(Activity/Fragment)观察 ViewModel 暴露的 LiveData/StateFlow 来更新界面。
  2. 使用 onSaveInstanceState 作为UI状态的“备份”

    • 仅用它来保存那些重建UI所必需的最小化状态信息,而不是完整的数据对象。
    • 例如,保存一个项目的ID,而不是整个项目对象。在重建时,用这个ID从 ViewModel 中重新获取完整数据。

代码详解与示例

假设有一个 “用户详情”页面(UserDetailActivity),从网络加载用户数据,并有一个可滚动的TextView

1. ViewModel 代码 (UserDetailViewModel.java)
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;public class UserDetailViewModel extends ViewModel {// 使用 LiveData 或 StateFlow 持有数据(核心数据)private MutableLiveData<User> _user = new MutableLiveData<>();public LiveData<User> user = _user;private MutableLiveData<Boolean> _isLoading = new MutableLiveData<>();public LiveData<Boolean> isLoading = _isLoading;private String userId; // 要加载的用户ID// 初始化加载数据public void init(String userId) {if (this.userId != null) {// ViewModel 已经初始化过,配置旋转时直接使用现有数据return;}this.userId = userId;loadUser(userId);}private void loadUser(String userId) {_isLoading.setValue(true);// 模拟网络请求UserRepository.getUser(userId, new Callback<User>() {@Overridepublic void onSuccess(User user) {_isLoading.setValue(false);_user.setValue(user); // 数据加载成功,更新LiveData}@Overridepublic void onError(Exception e) {_isLoading.setValue(false);// 处理错误}});}// 清空资源(可选)@Overrideprotected void onCleared() {super.onCleared();// 取消正在进行的网络请求等}
}
2. Activity 代码 (UserDetailActivity.java)
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;import android.os.Bundle;
import android.widget.TextView;public class UserDetailActivity extends AppCompatActivity {private static final String SAVE_STATE_USER_ID = "SAVE_STATE_USER_ID";private static final String SAVE_STATE_SCROLL_POSITION = "SAVE_STATE_SCROLL_POSITION";private UserDetailViewModel viewModel;private TextView userBioTextView;private String userId;private int scrollPosition = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_user_detail);userBioTextView = findViewById(R.id.tv_user_bio);// 1. 从 Intent 或 SavedState 中获取 userIdif (savedInstanceState == null) {// 正常启动:从Intent获取userId = getIntent().getStringExtra("USER_ID");} else {// 重建恢复:从Bundle获取(可能是进程被杀死后)userId = savedInstanceState.getString(SAVE_STATE_USER_ID);scrollPosition = savedInstanceState.getInt(SAVE_STATE_SCROLL_POSITION, 0);}// 2. 初始化 ViewModelviewModel = new ViewModelProvider(this).get(UserDetailViewModel.class);viewModel.init(userId); // 传递ID,ViewModel内部会判断是否已加载// 3. 观察 ViewModel 中的数据viewModel.user.observe(this, user -> {if (user != null) {// 更新UI with user datauserBioTextView.setText(user.getBio());// 恢复滚动位置userBioTextView.scrollTo(0, scrollPosition);}});viewModel.isLoading.observe(this, isLoading -> {// 显示或隐藏加载进度条});}// 4. 保存UI状态(用于进程终止)@Overrideprotected void onSaveInstanceState(@NonNull Bundle outState) {super.onSaveInstanceState(outState);// 只保存最小必要信息:用户ID和滚动位置outState.putString(SAVE_STATE_USER_ID, userId);outState.putInt(SAVE_STATE_SCROLL_POSITION, userBioTextView.getScrollY()); // 获取当前滚动位置}
}

两种场景下的工作流程

场景一:屏幕旋转(配置更改)
  1. 旋转前:Activity被销毁,onSaveInstanceState被调用,userIdscrollPosition被保存到Bundle中。
  2. 旋转后:新Activity创建,onCreate(Bundle savedInstanceState)被调用,savedInstanceState不为null
  3. 恢复数据:从Bundle中取出userIdscrollPosition
  4. 获取核心数据ViewModel没有被销毁,它仍然持有完整的User对象。新Activity获取到同一个ViewModel实例,立即观察到User数据并更新UI,然后应用滚动位置。
    • 结果:用户体验无缝衔接,没有重复的网络请求
场景二:应用被系统杀死后恢复
  1. 被杀前onSaveInstanceState被调用,userIdscrollPosition被写入磁盘。
  2. 被杀后:用户重新打开应用,系统重新创建Activity,并将保存的Bundle传入onCreate
  3. 恢复数据:从Bundle中取出userIdscrollPosition
  4. 获取核心数据ViewModel是全新的、空的。viewModel.init(userId)被调用,根据保存的userId发起新的网络请求来获取完整的用户数据。
  5. 数据加载成功后,更新UI并滚动到之前的位置。
    • 结果:用户看到了和离开时几乎一样的界面,但需要等待数据重新加载。

总结与最佳方法

  1. 永远使用 ViewModel

    • 用来持有所有非UI状态的核心数据
    • 防止因配置更改导致的不必要数据重载(如网络请求、数据库查询)。
  2. 谨慎使用 onSaveInstanceState

    • 只用来保存恢复UI所必需的、轻量的、可序列化的状态(ID、位置、临时文本)。
    • 为应对进程死亡的场景提供保障。
  3. 分工合作

    • ViewModel 保证配置更改时体验流畅
    • onSaveInstanceState + ViewModel 共同保证进程死亡后体验连贯
  4. 不要滥用

    • 切勿将大型对象(如Bitmap)或复杂结构放入Bundle,这会导致TransactionTooLargeException
    • 如果UI状态非常复杂,考虑使用SavedStateHandle(与ViewModel搭配使用),它简化了保存状态的过程。

文章转载自:

http://ieS7TvZ5.tdxnz.cn
http://Ide9r485.tdxnz.cn
http://UT9TnXwc.tdxnz.cn
http://nNkYK0Zn.tdxnz.cn
http://BQUEoNpv.tdxnz.cn
http://JtLjd1Ne.tdxnz.cn
http://tSsHhVja.tdxnz.cn
http://zqjFYiPK.tdxnz.cn
http://XIGkfapy.tdxnz.cn
http://kfaydiuK.tdxnz.cn
http://jCuZcKwY.tdxnz.cn
http://5yr48bKz.tdxnz.cn
http://v2EHTf3a.tdxnz.cn
http://hFvWjFKH.tdxnz.cn
http://GB0ph3oZ.tdxnz.cn
http://UHaQORUq.tdxnz.cn
http://9xmlR63o.tdxnz.cn
http://tuLfZwwz.tdxnz.cn
http://cT7zyk9X.tdxnz.cn
http://cxpuMTdM.tdxnz.cn
http://a3zkRomJ.tdxnz.cn
http://XeI1kLVJ.tdxnz.cn
http://woc6MMLx.tdxnz.cn
http://xQteJHjP.tdxnz.cn
http://aU0TbCcK.tdxnz.cn
http://frOYI4wo.tdxnz.cn
http://BF8r95pv.tdxnz.cn
http://jwBIWFuf.tdxnz.cn
http://YHCG9Y9j.tdxnz.cn
http://Ky5pnyM6.tdxnz.cn
http://www.dtcms.com/a/380610.html

相关文章:

  • 达梦:将sql通过shell脚本的方式放在后台执行
  • 进阶向:从零开始理解Python音频处理系统
  • Centos7安装nginx
  • 数字图像处理-巴特沃斯高通滤波、低通滤波
  • Knockout数据绑定语法的入门教程
  • Serdes专题(1)Serdes综述
  • 2025年机器人项目管理推荐:三款工具破解机械设计到量产交付的协同难题
  • 后端post请求返回页面,在另一个项目中请求过来会出现的问题
  • 前端菜单权限方案
  • 【运维】-- 前端会话回放与产品分析平台之 openreplay
  • 前后端开发Mock作用说明,mock.ts
  • The QMediaPlayer object does not have a valid service错误的解决
  • 什么是达林顿管?
  • 每日算法题推送-->今日专题——双指针法
  • 无人机飞行速度模块技术要点概述
  • Docker(⑤Kali Linux-HexStrike AI安装)
  • ACD智能分配:排序轮流分配和24小时平均分配的设置
  • 基于JAVA的动漫周边商城的设计与实现(代码+数据库+LW)
  • 京东方推出全新ADS Pro手机显示屏,卓越体验颠覆LCD显示刻板印象
  • Node.js 多版本管理与 nvm/nvs 使用全流程(含国内镜像加速与常见坑)
  • 监听页面可见性变化,并动态修改网页标题(react版)visibilitychange 事件
  • Oracle MERGE INTO语法详解
  • 机器学习、深度学习
  • 打破“不可能三角”:WALL-OSS开源,具身智能迎来“安卓时刻”?
  • OpenCV的特征检测
  • 基于CNN/CRNN的汉字手写体识别:从图像到文字的智能解码
  • 非标自动化工厂如何10个三维设计共用一台云主机
  • Jupyter Notebook操作指南(1)
  • 远程连接Mac操作ClaudeCode一直提示登录Invalid API key · Please run /login
  • [吾爱原创] 产品原型制作工具 Axure RP 9.0.0.3754 完整汉化版