作者: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- selected = new MutableLiveData
- ();
public void select(Item item) {
selected.setValue(item);
}
public LiveData
- 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().get(.class)
引用最新版本2.2.0版本ViewModel你会发现,ViewModelProviders该类被弃用了。
参考一下lifecycle更新文档:
lifecycle更新文档
那么在onCreate中,你应该这样写:
mViewModel = new ViewModelProvider().get(.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 get(@NonNull Class 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 get(@NonNull String key, @NonNull Class modelClass) {
//4、在ViewModelStore中拿到VM实例
//ViewModelStore 一个真正用来存储ViewModel实例的集合。本质上是HashMap
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面试、数据结构、算法等全套视频资料。