Jetpack-VM再不懂你就out了(vm client)
liuian 2025-04-30 18:01 72 浏览
作者:heiyulong
授权原文:https://mp.weixin.qq.com/s/yZ7ZQYn7KjYxQiZT956NqQ
前言
ViewModel 作为 Jetpack 组件库组件之一,它的出现释放了 Activity/Fragment 管理数据的压力,ViewModel 经常会搭配 LiveData 一起用于 MVVM 的开发模式。
一、认识ViewModel
1、什么是 ViewModel?
- ViewModel 具备宿主生命后期感知能力的数据存储与数据管理组件。
- 使用 ViewModel 保存的数据,在页面因配置变更(如,发生屏幕旋转等配置更改后)导致页面销毁重建之后依然也是存在的;
- ViewModel还处理Activity/Fragment与应用程序其余部分的通信(例如,调用业务逻辑类);
- ViewModel的目的是获取并保留Activity或Fragment所需的信息。Activity或Fragment应能够观察ViewModel中的更改;
- ViewModel通常通过LiveData或DataBinding公开此信息;
- ViewModel的唯一责任是管理UI的数据。它永远都不应访问您的视图层次结构或保留对Activity或Fragment的引用。
2、ViewModel 的优势
页面配置更改数据不丢失
- 当设备因配置更改导致 Activity/Fragment 重建,ViewModel 中的数据并不会因此而丢失,配合 LiveData 可以在页面重建后立马能收到最新保存的数据用以重新渲染页面。
Android的Activity的生命周期有很多状态,并且由于配置更改,单个Activity可能会在这些不同状态之间循环多次。参照下面Activity生命周期图:
Activity生命周期
当一个Activity经历了所有这些状态,您可能还需要将瞬态UI数据保存在内存中。我将瞬态UI数据定义为UI所需的数据,包括用户输入的数据,运行时生成的数据或从数据库加载的数据。这些数据可以是图片位图、用于RecyclerView的对象列表等。
如,用户旋转屏幕时,以前我们通过onSaveInstanceState()和onRestoreInstanceState()在配置更改期间保存或还原此数据。
用户旋转屏幕时如下图:当系统开始停止您的Activity时:
- (1)它会调用onSaveInstanceState(),以便您可以指定要保存的其他状态数据,以防Activity必须重新创建实例。
- (2)如果Activity被破坏并且必须重新创建相同的实例,则系统将(1)中定义的状态数据传递给onCreate()方法和onRestoreInstanceState()方法。
屏幕旋转:Avtivity生命周期流程
但是,如果您的数据不需要知道或管理“Activity”所处于的生命周期状态,或者不将其存储在Activity内该怎么做呢?这就是ViewModel类的目的。
- ViewModel 和 onSaveIntanceState 方法区别?
- onSaveIntanceState 只能存储轻量级的 key-value 键值对数据,非配置变更导致的页面被回收时才会触发,此时数据存储在 ActivityRecord 中;
- ViewModel 可以存放任意 Object 数据,因配置变更导致的页面被回收才有效。此时存在ActivityThread#ActivityClientRecord 中。
生命周期感应
- 在 ViewModel 中难免会做一些网络请求或数据的处理,可以复写 onCleared() 方法,终止清理一些操作,释放内存。该方法在宿主 onDestroy 时被调用。
如下图:
你可以看到Activity的生命周期在旋转过程中的状态流转直到finish。ViewModel的生命周期显示在关联的Activity的生命周期的旁边。请注意,这个ViewModels可以简单轻松地与Activities/Fragments结合使用。
您通常在系统首次调用 Activity 对象的 onCreate() 方法时请求 ViewModel。系统可能会在 Activity 的整个生命周期内多次调用 onCreate(),如在旋转设备屏幕时。ViewModel 存在的时间范围是从您首次请求 ViewModel 直到 Activity 完成并销毁。
数据共享
- 对于单 Activity 对 Fragment 的页面,可以使用 ViewModel 实现页面之间的数据共享,实际上不同的 Activity也可以实现数据共享。
Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的情况。
想象一下主从 Fragment 的常见情况,假设您有一个 Fragment,在该 Fragment 中,用户从列表中选择一项,还有另一个 Fragment,用于显示选定项的内容。
这种情况不太容易处理,因为这两个 Fragment 都需要定义某种接口描述,并且所有者 Activity 必须将两者绑定在一起。
此外,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。
可以使用 ViewModel 对象解决这一常见的难点。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信,如以下示例代码所示:
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//!!!请注意:这两个 Fragment 都会检索包含它们的 Activity
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//!!!请注意:这两个 Fragment 都会检索包含它们的 Activity
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
model.getSelected().observe(getViewLifecycleOwner(), { item ->
// Update the UI.
});
}
}
- 通过ViewModel在 Fragment 之间共享数据的优势:
- 1、Activity 不需要执行任何操作,也不需要对此通信有任何了解。
- 2、除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
- 3、每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。
二、ViewModel 的使用
使用ViewModel的三个步骤:
- 通过创建一个继承自ViewModel的类来从你的UI控制器(Activity或Fragment)中分离出你的ViewModel
- 在你的UI控制器和ViewModel之间建立通讯
- 在你的UI控制器中使用ViewModel
LiveData , ViewModel 组件都是基于Lifecycle来实现的,使用 ViewModel 之前需要先添加依赖:
声明依赖项
dependencies {
def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
}
1、创建一个ViewModel类
引用官方:一个非常简单的例子
- 通常情况下,你需要为你的app的每个页面创建一个ViewModel类。
- 这个ViewModel类会掌控和管理所有的与页面相关数据,并且提供get/set方法用于存取数据。
- 这会将显示Activity的UI代码与您的用于显示UI的数据分离开(显示UI的数据现在位于ViewModel中)。
public class ScoreViewModel extends ViewModel {
// Tracks the score for Team A
public int scoreTeamA = 0;
// Tracks the score for Team B
public int scoreTeamB = 0;
}2、关联UI控制器和ViewModel
你的UI控制器(即Activity或Fragment)需要了解您的ViewModel。您的UI控制器就可以在发生UI交互时显示数据并更新数据
但是,ViewModels不应保留对Activity,Fragment或Context的引用。 此外,ViewModels不应包含那些拥有对UI控制器的引用的元素,例如Views,因为这将创建对Context的间接引用。
你不该保存这些对象的原因是, ViewModels的寿命超出了特定的UI控制实例之外——如果您将Activity旋转3次,则您刚刚创建了三个不同的Activity实例,但是只有一个ViewModel实例。
考虑到这一点,让我们来创建这个UI控制器/ViewModel关联。你想要为在UI控制器中的ViewModel创建一个成员变量。那么在onCreate中,你应该这样写:
ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class)引用最新版本2.2.0版本ViewModel你会发现,ViewModelProviders该类被弃用了。
参考一下lifecycle更新文档:
lifecycle更新文档
那么在onCreate中,你应该这样写:
mViewModel = new ViewModelProvider(<Your UI controller>).get(<Your ViewModel>.class)@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = new ViewModelProvider(this).get(ScoreViewModel.class);
// Other setup code below...
}注意:“ViewModel中没有上下文”的规则有一个例外:
有时您可能需要一个Application的Context(而不是Activity的Context)来与诸如系统服务之类的东西一起使用。
那么,您可以将应用程序上下文储存在ViewModel中,因为应用程序上下文与应用程序生命周期相关联。
这不同于与Activity生命周期相关的Activity的Context。实际上如果需要Application的Context,则应该拓展AndroidViewModel,它只是一个包含Application引用的ViewModel。
3、在UI控制器中使用ViewModel
要访问或更改UI数据,现在可以在ViewModel中使用数据。这里有一个新的onCreate方法和一个为队伍A增加分数的更新方法的例子:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = new ViewModelProvider(this).get(ScoreViewModel.class);
displayForTeamA(mViewModel.scoreTeamA);
displayForTeamB(mViewModel.scoreTeamB);
}
// An example of both reading and writing to the ViewModel
public void addOneForTeamA(View v) {
mViewModel.scoreTeamA = mViewModel.scoreTeamA + 1;
displayForTeamA(mViewModel.scoreTeamA);
}ViewModel也可以很好地和其他架构组件一起工作。比如: LiveData,DataBinding等等。
三、ViewModel复用的实现原理
上面说到,ViewModel 可以实现因配置变更导致页面销毁重建之后依然可以复用。
准确点来说,应该是 页面恢复重建前后获取到的是同一个 ViewModel 实例对象,以至于页面恢复重建后还能接着复用。
通过ViewModelProvider获取 ViewModel 实例的方式入手:
mViewModel = new ViewModelProvider(this).get(ScoreViewModel.class);
//指定factory
mViewModel = new ViewModelProvider(this,factory).get(ScoreViewModel.class);ViewModelProvider 本质是从传递进去的 ViewModelStore 来获取实例。如果没有传递,则利用 factory 去创建一个新的,并存储到 ViewModelStore。
1、ViewModelProvider获取ViewModel实例get()方法源码分析
public class ViewModelProvider {
private static final String DEFAULT_KEY =
"androidx.lifecycle.ViewModelProvider.DefaultKey";
private final Factory mFactory;
private final ViewModelStore mViewModelStore;
//1、
//ViewModelProvider 本质是从传递进去的 ViewModelStore 来获取实例。
//如果没有传递,则利用 factory 去创建一个新的,并存储到 ViewModelStore。
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
//5、ViewModelStore()的由来,通过owner.getViewModelStore()?
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
//2、根据传递的modelClass 构建一个默认的Key
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
@NonNull
@MainThread
//3、获取viewmodel实例时,也可以自行指定Key
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//4、在ViewModelStore中拿到VM实例
//ViewModelStore 一个真正用来存储ViewModel实例的集合。本质上是HashMap<String,ViewModel>
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
}2、ViewModelStore()的由来,通过owner.getViewModelStore()查看源码:
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
//1、首先从`NonConfigurationInstances`来获取`ViewModelStore`实例对象
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
//2、如果不为空岂不是获取到的是同一个 ViewModel 实例对象,
//以至于页面恢复重建后还能接着复用。
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
//3、`ViewModelStore`何时被存储到`NonConfigurationInstances`里面的?
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
}3、onRetainNonConfigurationInstance 源码
因系统原因页面被回收时,会触发onRetainNonConfigurationInstance方法,所以 viewModelStore 对象此时会被存储在NonConfigurationInstance 中。在页面恢复重建时,会再次把这个 NonConfigurationInstance 对象传递到新的Activity 中实现对象复用。
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {
/**
* Retain all appropriate non-config state. You can NOT
* override this yourself! Use a {@link androidx.lifecycle.ViewModel} if you want to
* retain your own non config state.
*/
@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last
//3、如果NonConfigurationInstance保存了viewModelStore,把它取出来NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
//1、把viewModelStore放到NonConfigurationInstances中并返回
nci.viewModelStore = viewModelStore;
//2、当页面被销毁时ViewModelStore就被保存起来了。
return nci;
}
}后台私信回复 1024 免费领取 SpringCloud、SpringBoot,微信小程序、Java面试、数据结构、算法等全套视频资料。
相关推荐
-
- 驱动网卡(怎么从新驱动网卡)
-
网卡一般是指为电脑主机提供有线无线网络功能的适配器。而网卡驱动指的就是电脑连接识别这些网卡型号的桥梁。网卡只有打上了网卡驱动才能正常使用。并不是说所有的网卡一插到电脑上面就能进行数据传输了,他都需要里面芯片组的驱动文件才能支持他进行数据传输...
-
2026-01-30 00:37 liuian
- win10更新助手装系统(微软win10更新助手)
-
1、点击首页“系统升级”的按钮,给出弹框,告诉用户需要上传IMEI码才能使用升级服务。同时给出同意和取消按钮。华为手机助手2、点击同意,则进入到“系统升级”功能华为手机助手华为手机助手3、在检测界面,...
- windows11专业版密钥最新(windows11专业版激活码永久)
-
Windows11专业版的正版密钥,我们是对windows的激活所必备的工具。该密钥我们可以通过微软商城或者通过计算机的硬件供应商去购买获得。获得了windows11专业版的正版密钥后,我...
-
- 手机删过的软件恢复(手机删除过的软件怎么恢复)
-
操作步骤:1、首先,我们需要先打开手机。然后在许多图标中找到带有[文件管理]文本的图标,然后单击“文件管理”进入页面。2、进入页面后,我们将在顶部看到一行文本:手机,最新信息,文档,视频,图片,音乐,收藏,最后是我们正在寻找的[更多],单击...
-
2026-01-29 23:55 liuian
- 一键ghost手动备份系统步骤(一键ghost 备份)
-
步骤1、首先把装有一键GHOST装系统的U盘插在电脑上,然后打开电脑马上按F2或DEL键入BIOS界面,然后就选择BOOT打USDHDD模式选择好,然后按F10键保存,电脑就会马上重启。 步骤...
- 怎么创建局域网(怎么创建局域网打游戏)
-
1、购买路由器一台。进入路由器把dhcp功能打开 2、购买一台交换机。从路由器lan端口拉出一条网线查到交换机的任意一个端口上。 3、两台以上电脑。从交换机任意端口拉出网线插到电脑上(电脑设置...
- 精灵驱动器官方下载(精灵驱动手机版下载)
-
是的。驱动精灵是一款集驱动管理和硬件检测于一体的、专业级的驱动管理和维护工具。驱动精灵为用户提供驱动备份、恢复、安装、删除、在线更新等实用功能。1、全新驱动精灵2012引擎,大幅提升硬件和驱动辨识能力...
- 一键还原系统步骤(一键还原系统有哪些)
-
1、首先需要下载安装一下Windows一键还原程序,在安装程序窗口中,点击“下一步”,弹出“用户许可协议”窗口,选择“我同意该许可协议的条款”,并点击“下一步”。 2、在弹出的“准备安装”窗口中,可...
- 电脑加速器哪个好(电脑加速器哪款好)
-
我认为pp加速器最好用,飞速土豆太懒,急速酷六根本不工作。pp加速器什么网页都加速,太任劳任怨了!以上是个人观点,具体性能请自己试。ps:我家电脑性能很好。迅游加速盒子是可以加速电脑的。因为有过之...
- 任何u盘都可以做启动盘吗(u盘必须做成启动盘才能装系统吗)
-
是的,需要注意,U盘的大小要在4G以上,最好是8G以上,因为启动盘里面需要装系统,内存小的话,不能用来安装系统。内存卡或者U盘或者移动硬盘都可以用来做启动盘安装系统。普通的U盘就可以,不过最好U盘...
- u盘怎么恢复文件(u盘文件恢复的方法)
-
开360安全卫士,点击上面的“功能大全”。点击文件恢复然后点击“数据”下的“文件恢复”功能。选择驱动接着选择需要恢复的驱动,选择接入的U盘。点击开始扫描选好就点击中间的“开始扫描”,开始扫描U盘数据。...
- 系统虚拟内存太低怎么办(系统虚拟内存占用过高什么原因)
-
1.检查系统虚拟内存使用情况,如果发现有大量的空闲内存,可以尝试释放一些不必要的进程,以释放内存空间。2.如果系统虚拟内存使用率较高,可以尝试增加系统虚拟内存的大小,以便更多的应用程序可以使用更多...
-
- 剪贴板权限设置方法(剪贴板访问权限)
-
1、首先打开iphone手机,触碰并按住单词或图像直到显示选择选项。2、其次,然后选取“拷贝”或“剪贴板”。3、勾选需要的“权限”,最后选择开启,即可完成苹果剪贴板权限设置。仅参考1.打开苹果手机设置按钮,点击【通用】。2.点击【键盘】,再...
-
2026-01-29 21:37 liuian
- 平板系统重装大师(平板重装win系统)
-
如果你的平板开不了机,但可以连接上电脑,那就能好办,楼主下载安装个平板刷机王到你的个人电脑上,然后连接你的平板,平板刷机王会自动识别你的平板,平板刷机王上有你平板的我刷机包,楼主点击下载一个,下载完成...
- 联想官网售后服务网点(联想官网售后服务热线)
-
联想3c服务中心是联想旗下的官方售后,是基于互联网O2O模式开发的全新服务平台。可以为终端用户提供多品牌手机、电脑以及其他3C类产品的维修、保养和保险服务。根据客户需求层次,联想服务针对个人及家庭客户...
- 一周热门
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)
