1、前言
最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。
Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:
- 带你领略Android Jetpack组件的魅力
- Android Jetpack 架构组件之 Lifecycle(使用篇)
- Android Jetpack 架构组件之 Lifecycle(源码篇)
- Android Jetpack 架构组件之 ViewModel (源码篇)
- Android Jetpack 架构组件之 LiveData(使用、源码篇)
- Android Jetpack架构组件之 Paging(使用、源码篇)
- Android Jetpack 架构组件之 Room(使用、源码篇)
- Android Jetpack 架构组件之Navigation
- Android Jetpack架构组件之WorkManger
- 实战:从0搭建Jetpack版的WanAndroid客户端
上述时Android Architecture所提供的架构组件,本文主要从使用和源码的角度了解一下ViewModel组件的机制
2、VIewModel简介
Android框架管理UI控制器的生命周期,例如活动和片段。框架可以决定销毁或重新创建UI控制器以响应完全不受您控制的某些用户动作或设备事件,那设想一种情况当用户在界面操作录入了一些信息后,因为某种原因导致Activity重新创建,那此时用户写好的信息呢?如果要重头再来可能有的用户就会不耐烦了,进而减少了使用。。。,可能有人说,可以使用该 onSaveInstanceState()方法并从包中恢复其数据 onCreate(),但此方法仅适用于可以序列化然后反序列化的少量数据,如果要恢复的数据量比较大,此时就时VIewModel的厉害之处了。
ViewModel之所以能在Activity重建时保存并恢复数据,因为Activity初次创建时会初始化创建VIewModel,在Activity销毁时,ViewModel对象不会销毁,在新的Activity重新创建后,仍然会执行之前的获取ViewModel的过程,Android系统采取了处理机制,使现在拿到的ViewModel就是前一次创建的对象,设想一下数据都储存在VIewModel中,而两次拿到的都是同一个VIewModel,那显示的数据自然就和之前的一样喽,这里先放一张系统处理ViewModel的创建、存储和获取流程图:
3、ViewModel的使用
- 创建ViewModel类继承系统的ViewModel
class Model : ViewModel() {
var textName = "Empty"
}
- 在使用的Activity、Fragment中获取ViewModel
val model = ViewModelProviders.of(this)[Model::class.java]
- 设置ViewModel中储存的数据
tvModel.text = model.textName
- 实例:先添加一个按钮,点击按钮会修改Model中textName的值,然后在旋转手机,如果显示的为第二次设置的值,那么说明数据被保存且恢复了;
btn_change.setOnClickListener {
model.textName = "Change = 22222"
tvModel.text = model.textName
}
- 注意:因为ViewModel在Activity销毁时是不会重新创建的,这也意味者ViewModel中不可以引用Activity的对象,否则会有内存泄露的问题,那么当Model中需要Context呢?Android为我们提供了AndroidViewModel,只需继承AndroidViewModel即可
4、生命周期
- ViewModel对象的范围是在获取ViewModel时传递给ViewModelProvider的Lifecycle生命周期
- ViewModel在内存中直到Activity销毁或Fragment被移除
- 系统首次调用活动对象的onCreate()方法时,通常会请求ViewModel
- 系统可能会在整个活动的整个生命周期中多次调用onCreate(),例如当设备屏幕旋转时
- ViewModel从第一次请求ViewModel直到活动完成并销毁时存在
5、源码解析
5.1 、ViewModelProviders.of(this)[Model::class.java]
从上面的方法中可以看出ViewModel的获取过程分为两步:
- 获取ViewProvider:
ViewModelProviders提供四个构造方法创建VIewProvider,两个带有factory两个没有,不过没有factory的其实使用的是默认的Factory,所以四个方法基本一致只是Fragment和Activity的区分
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment) {
return of(fragment, null);
}
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
return of(activity, null);
}
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
Application application = checkApplication(checkActivity(fragment));
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(ViewModelStores.of(fragment), factory);
}
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
Application application = checkApplication(activity);
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(ViewModelStores.of(activity), factory);
}
- 获取ViewModelStore:由前面的源码可以知道创建ViewProvider时传入两个参数:ViewModelStore 和 Factory;显然从名字就可以看出他们的作用,Factory负责创建,ViewModelStore负责存储
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
//noinspection unchecked
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
viewModel = mFactory.create(modelClass);
mViewModelStore.put(key, viewModel);
//noinspection unchecked
return (T) viewModel;
}
上面的执行是,先去ViewModelStore中获取,如果为空就调用Factory的create()创建ViewModel,并储存在VIewmoStore中,与我们所想一致;
- ViewModelProviders.of(this)[Model::class.java]方法,忽略创建和储存细节,执行逻辑为:
5.2 、VIewModelStore
- ViewModelStore.of(this)
上述过程中使用ViewModelStore.of(this) 创建ViewModelStore,方法源码:
@NonNull
@MainThread
public static ViewModelStore of(@NonNull FragmentActivity activity) {
if (activity instanceof ViewModelStoreOwner) {
return ((ViewModelStoreOwner) activity).getViewModelStore();
}
return holderFragmentFor(activity).getViewModelStore();
}
先判断Activity是否为 ViewModelStoreOwner,如果是直接获取其中的ViewModelStore,否则调用holderFragmentFor(activity).getViewModelStore()获取
- holderFragmentFor(activity).
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static HolderFragment holderFragmentFor(FragmentActivity activity) {
return sHolderFragmentManager.holderFragmentFor(activity);
}
// HolderFragmentManger
HolderFragment holderFragmentFor(FragmentActivity activity) {
FragmentManager fm = activity.getSupportFragmentManager();
HolderFragment holder = findHolderFragment(fm); // 根据TAG查找Fragment
if (holder != null) {
return holder;
}
holder = mNotCommittedActivityHolders.get(activity); // 从储存的集合中获取
if (holder != null) {
return holder;
}
if (!mActivityCallbacksIsAdded) {
mActivityCallbacksIsAdded = true;
activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
}
holder = createHolderFragment(fm); // 创建HolderFragment
mNotCommittedActivityHolders.put(activity, holder); // 保存到集合
return holder;
}
- findHolderFragment() & createHolderFragment()
private static HolderFragment findHolderFragment(FragmentManager manager) {
if (manager.isDestroyed()) {
throw new IllegalStateException("Can't access ViewModels from onDestroy");
}
// 根据TAG查找
Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
...
return (HolderFragment) fragmentByTag;
}
private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
HolderFragment holder = new HolderFragment();
fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss(); // 设置TAG
return holder;
}
5.3、HolderFragment
上面的过程都是获取或创建HolderFragment的过程,有没有想过我们要的是储存ViewModel的地方,为什么一值在操作Fragment,答案就在其中:
- 首先记得前面的判断吗?instanceof ViewModelStoreOwner?相信此时应该会想到HolderFragment就是ViewModelStoreOwner的实现类了吧
public class HolderFragment extends Fragment implements ViewModelStoreOwner {
}
- HolderFragment中保存了ViewStore的实例,
private ViewModelStore mViewModelStore = new ViewModelStore();
@NonNull
@Override
public ViewModelStore getViewModelStore() {
return mViewModelStore;
}
- 获取ViewStore的执行逻辑:
- ViewStore是如何获取、储存Viewmodel的呢?
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
整个ViewModel的源码分析到此结束了,总结一下内部的储存逻辑
- 根据传入的Activity获取、创建、添加并已键值对保存Fragment
- 获取Fragment中保存的ViewStore对象(ViewStore中使用Map储存ViewModel)
- 创建ViewProvider实例,ViewProvider中封装了获取的ViewStore和创建用的Factory
- 从VIewStore的Map中或Factory的create()中获取ViewModel
6、思考
- 如何保证两次创建的activity(旋转前后)获取到的为同一个ViewModel,
在上面获取Fragment中时,创建过HolderFragment后保存在Map中,现在我们看一下这个map
private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
Map的键就是Activity的实例,所以无论多少次创建都是此Activity的实例,也就获得唯一的一个对应的Fragment
- 针对上面的特性是否有其他用处
按照上一个问题的逻辑,主要是传入的Activity的对象一致,那获取到就是同一个Fragment,存储的也是同一个VIewStore,那设想一下,如果一个Activity中有多个Fragment,利用这个特性就可以实现数据交互了;例如:两个Fragment之间。一个显示标题列表,点击某一个标题,另一个Fragment显示内容,此时使用一个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 onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item); // 设置数据
});
}
}
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}
- 上述代码中使用了LiveData,暂且把他看成一个观察改变并发射数据的功能,在第一个Fragment中修改,第二个Fragment也会响应变化;
- 在获取ViewModelProvider时,这两个片段都使用getActivity() 因此,这两个片段都会收到相同的SharedViewModel实例,该实例的作用域为Acyivity
- 使用ViewModel共享数据的好处,两个Fragment之间,Fragment和Activity之间无需任何联系,实现真正的解耦,每个片段都有其自己的生命周期,并且不受其他生命周期的影响
到此ViewModel的介绍完成了,将ViewModel和LiveData或其他组件联合使用,构造数据驱动型的界面,相信一定会带来不一样的体验。