Jetpack-VM再不懂你就out了(vm client)
liuian 2025-04-30 18:01 60 浏览
作者: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面试、数据结构、算法等全套视频资料。
相关推荐
- 搭建一个20人的办公网络(适用于20多人的小型办公网络环境)
-
楼主有5台机上网,则需要一个8口路由器,组网方法如下:设备:1、8口路由器一台,其中8口为LAN(局域网)端口,一个WAN(广域网)端口,价格100--400元2、网线N米,这个你自己会看了:)...
- 笔记本电脑各种参数介绍(笔记本电脑各项参数新手普及知识)
-
1、CPU:这个主要取决于频率和二级缓存,频率越高、二级缓存越大,速度越快,现在的CPU有三级缓存、四级缓存等,都影响相应速度。2、内存:内存的存取速度取决于接口、颗粒数量多少与储存大小,一般来说,内...
- 汉字上面带拼音输入法下载(字上面带拼音的输入法是哪个)
-
使用手机上的拼音输入法打成汉字的方法如下:1.打开手机上的拼音输入法,在输入框中输入汉字的拼音,例如“nihao”。2.根据输入法提示的候选词,选择正确的汉字。例如,如果输入“nihao”,输...
- xpsp3安装版系统下载(windowsxpsp3安装教程)
-
xpsp3纯净版在采用微软封装部署技术的基础上,结合作者的实际工作经验,融合了许多实用的功能。它通过一键分区、一键装系统、自动装驱动、一键设定分辨率,一键填IP,一键Ghost备份(恢复)等一系列...
- 没有备份的手机数据怎么恢复
-
手机没有备份恢复数据方法如下1、使用数据线将手机与电脑连接好,在“我的电脑”中可以看到手机的盘符。 2、将手机开启USB调试模式。在手机设置中找到开发者选项,然后点击“开启USB调试模式”。 3、...
- 电脑怎么激活windows11专业版
-
win11专业版激活方法有多种,以下提供两种常用的激活方式:方法一:使用激活密钥激活。在win11桌面上右键点击“此电脑”,选择“属性”选项。进入属性页面后,点击“更改产品密钥或升级windows”。...
- 华为手机助手下载官网(华为手机助手app下载专区)
-
华为手机助手策略调整,已不支持从应用市场下载手机助手,目前华为手机助手是需要在电脑上下载或更新手机助手到最新版本,https://consumer.huawei.com/cn/support/his...
- 光纤线断了怎么接(宽带光纤线断了怎么接)
-
宽带光纤线断了可以重接,具体操作方法如下:1、光纤连接的时候要根据束管内,同色相连,同芯相连,按顺序进行连接,由大到小。一般有三种连接方法,分别是熔接、活动连接和机械连接。2、连接的时候要开剥光缆,抛...
- win7旗舰版和专业版区别(win7旗舰版跟专业版)
-
1、功能区别:Win7旗舰版比专业版多了三个功能,分别是Bitlocker、BitlockerToGo和多语言界面; 2、用途区别:旗舰版的功能是所有版本中最全最强大的,占用的系统资源,...
- 万能连接钥匙(万能wifi连接钥匙下载)
-
1、首先打开wifi万能钥匙软件,若手机没有开启WLAN,就根据软件提示打开WLAN开关;2、打开WLAN开关后,会显示附近的WiFi,如果知道密码,可点击相应WiFi后点击‘输入密码’连接;3、若不...
- 雨林木风音乐叫什么(雨林木风是啥)
-
雨林木风的创始人是陈年鑫先生。陈年鑫先生于1999年创立了雨林木风公司,其初衷是为满足中国市场对高品质、高性能电脑的需求。在陈年鑫先生的领导下,雨林木风以技术创新、产品质量和客户服务为核心价值,不断推...
- aics6序列号永久序列号(aics6破解序列号)
-
关于AICS6这个版本,虽然是比较久远的版本,但是在功能上也是十分全面和强大的,作为一名平面设计师的话,AICS6的现有的功能已经能够应付几乎所有的设计工作了……到底AICC2019的功能是不是...
- 手机可以装电脑系统吗(手机可以装电脑系统吗怎么装)
-
答题公式1:手机可以通过数据线或无线连接的方式给电脑装系统。手机安装系统需要一定的技巧和软件支持,一般需要通过数据线或无线连接的方式与电脑连接,并下载相应的软件和系统文件进行安装。对于大部分手机用户来...
- 一周热门
- 最近发表
- 标签列表
-
- 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)
