Android Architecture Components分析记录(三)
August 18, 2017
记录分析AAC
第三篇---ViewModel
,官方地址
https://developer.android.com/topic/libraries/architecture/viewmodel.html?hl=zh-cn
前言
由于UI组件
可能会因为某些原因(比如旋转屏幕等)导致被系统销毁或者重建从而导致所持数据丢失,通知针对这种状况采取的做法是通过onSaveInstanceState
保存数据,并在界面重建后取出来,例如EditText
编辑框中的内容,然而这种做法只适应于少量数据甚至最好只存储基本类型的数据。
官方针对此问题设计了ViewModel
(当然不止这个原因),例如一个列表页,以往的做法下旋转屏幕后还需要重新请求数据,使用ViewModel
后可以将减少这一次请求,而且这一切恢复操作完全不需要我们管理,这样又能减轻界面的负担。
下面先看看ViewModel
的使用方法
使用
先创建ViewModel
public class UserInfoModel extends ViewModel {
private MutableLiveData<UserInfo> mUserInfoMutableLiveData;
public LiveData<UserInfo> getUserInfoLiveData() {
if (mUserInfoMutableLiveData == null) {
mUserInfoMutableLiveData = new MutableLiveData<>();
loadUserInfo();
}
return mUserInfoMutableLiveData;
}
private void loadUserInfo() {
//例如网络加载操作
}
}
然后就可以在Activity
使用
UserInfoModel userInfoModel = ViewModelProviders.of(this).get(UserInfoModel.class);
userInfoModel.getUserInfoLiveData().observe(this, new Observer<UserInfo>() {
@Override
public void onChanged(@Nullable UserInfo userInfo) {
//更新UI
}
});
注意是通过
ViewModelProviders
而不是new
拿到ViewModel
实例
分析
在上门的例子可以看到实例ViewModel
的方法不是new
而是通过ViewModelProviders
取得
of()
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
initializeFactoryIfNeeded(activity.getApplication());
return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
}
private static DefaultFactory sDefaultFactory;
private static void initializeFactoryIfNeeded(Application application) {
if (sDefaultFactory == null) {
sDefaultFactory = new DefaultFactory(application);
}
}
of()
方法其实有四个,分别是
- of(@NonNull Fragment fragment)
- of(@NonNull FragmentActivity activity)
- of(@NonNull Fragment fragment, @NonNull Factory factory)
- of(@NonNull FragmentActivity activity,@NonNull Factory factory)
第二个参数是支持自定义Factory
,由于ViewModel
是通过反射实例化的,所以默认的构造函数的无参的,如果需要操作系统服务,可以选择继承AndroidViewModel
,这也是第一句initializeFactoryIfNeeded(activity.getApplication())
初始化sDefaultFactory
的原因,为了在后面反射时传入application
。
of()
返回的是一个ViewModelProvider
public ViewModelProvider(ViewModelStore store, Factory factory) {
mFactory = factory;
this.mViewModelStore = store;
}
只是赋值了下ViewModelStore
和Factory
,具体实例化是在get()
中。
ViewModelStore
先看看ViewModelStore
怎么来的
ViewModelStores.of(activity)
public static ViewModelStore of(FragmentActivity activity) {
return holderFragmentFor(activity).getViewModelStore();
}
public static HolderFragment holderFragmentFor(FragmentActivity activity) {
return sHolderFragmentManager.holderFragmentFor(activity);
}
HolderFragment holderFragmentFor(FragmentActivity activity) {
FragmentManager fm = activity.getSupportFragmentManager();
HolderFragment holder = findHolderFragment(fm);
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);
mNotCommittedActivityHolders.put(activity, holder);
return holder;
}
private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
HolderFragment holder = new HolderFragment();
fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
return holder;
}
ViewModelStore
是通过一个HolderFragment.getViewModelStore()
获得,这个HolderFragment
是一个透明无界面的Fragment
,ViewModelStores
就是保存在HolderFragment
中。随后通过FragmentManager
将HolderFragment
添加到Activity
,这也是为什么这个库要依赖于FragmentActivity
的原因。
接着看getViewModelStore()
public ViewModelStore getViewModelStore() {
return mViewModelStore;
}
private ViewModelStore mViewModelStore = new ViewModelStore();
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.get(key);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
mMap.put(key, viewModel);
}
final ViewModel get(String key) {
return mMap.get(key);
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
}
所以ViewModel
是被保存在这里,通过一个HashMap
进行存取。
咦,等一下,这个HashMap
以及这个ViewModelStore
都是局部变量,旋转屏幕也会销毁呀,明明不能保存数据。
setRetainInstance
这时候看到了HolderFragment
的构造函数
public HolderFragment() {
setRetainInstance(true);
}
查看API
/**
* Control whether a fragment instance is retained across Activity
* re-creation (such as from a configuration change). This can only
* be used with fragments not in the back stack. If set, the fragment
* lifecycle will be slightly different when an activity is recreated:
* <ul>
* <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still
* will be, because the fragment is being detached from its current activity).
* <li> {@link #onCreate(Bundle)} will not be called since the fragment
* is not being re-created.
* <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b>
* still be called.
* </ul>
*/
public void setRetainInstance(boolean retain) {
mRetainInstance = retain;
}
设定了retain=true
之后,Fragment
的生命周期将会改变,不会因为旋转屏幕等操作重建,但是会有以下几点后果
onDestroy()
将不会回调onDetach
仍会回调,因为Activity
重建了,所以Fragment
暂时分离onCreate(Bundle)
也不会回调,因为Fragment
并没有销毁onAttach()
、onActivityCreated(Bundle)
仍会回调
通过setRetainInstance(true)
,改变了HolderFragment
的部分生命周期流程,从而保持ViewModelStore
从而保持ViewModel
,所以保持了ViewModel
中的数据。
get()
最后看ViewModelProvider.get()
public <T extends ViewModel> T get(Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
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
拿viewModel
,拿到直接返回,拿不到就通过Factory.create()
反射实例化
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.getConstructor(Application.class).newInstance(mApplication);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
return super.create(modelClass);
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
如果是AndroidViewModel
的话,则会带多个application
参数,所以官方提供自定义Factory
自行操作
/**
* Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
*/
public interface Factory {
/**
* Creates a new instance of the given {@code Class}.
* <p>
*
* @param modelClass a {@code Class} whose instance is requested
* @param <T> The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
<T extends ViewModel> T create(Class<T> modelClass);
}
总结
ViewModel
负责帮UI
准备数据,减轻 UI 负担,并能够自动保存数据防止某些场景丢失、快速读取更新 UI。
由于LiveData
的观察者特性,所以ViewModel
还可以实现数据共享,例如最常见的ViewPager
绑定着多个Fragment
,某些场景会涉及到Fragment
之间的通信,以往的做法要不是通过Activity
进行中转通信,要不是使用事件总线。使用ViewModel
可以很方便解决这个问题,通过of(getActivity())
绑定到Activity
,这样的ViewModel
将会是同一个实例,也就能共同使用同一份数据从而进行通信等操作。
ViewModel
的生命周期会持续到Activity.onDestroy
、Fragment.onDetach
,并会回调onCleared
,所以有些大型操作依旧得进行解除。
— Evil Mouth