转载请注明出处:
使用ViewModel共享页面内的数据:ActivityDataBus
地址:http://blog.csdn.net/qq_22744433/article/details/78195155
目录
Google lifecycle-component推出了Lifecycle Manager、ViewModel、LiveData、Rom等重要的类。之前也在官网看了ViewModel的介绍,但并不感冒(感觉Lifecycle Manager确实对解耦用处很大,之前也写了文章,感兴趣的可以看看。但LiveData和ViewModel,感觉并不是很实用。LiveData可以用Observable来代替,只是多了生命周期感知。ViewModel只是能在屏幕旋转的时候保存数据。)。但最近回家在火车上没事,又翻了翻之前看的内容,突然觉得ViewModel还是很有用的。可以解耦页面block或fragment之间数据/view等的相互调用。直白点说就是页面block/fragment之间需要使用对方的数据/view时,无需之间硬性的引用,只需要activity的context参数就可以获取对方的数据/view,从而进行数据交流、view访问。而页面的context是系统类型且是很容易获取的,并不存在耦合。
如果大家比较忙,没时间看下面的内容,我这里给大家做了一个概述:
使用 Viewholder方式:ViewModelProviders.of(宿主activity).get(A.class) 其中A extend ViewHolder
用处:一个activty内,任何block,adapter,view类中都可“无显式耦合”的获得彼此间的数据。举例:我们可以在activty一开始就存一个movieId的viewHolder,那么这个activty涉及的所有类中都可以使用context来获取movieId。这对于埋点等都是很好帮助的,避免了级联引用。
大致原理:ViewModelProviders.of()用于获取ViewModelProvider实例。ViewModelProvider中含有一个ViewModelStore,ViewModelStore是用来存储viewModel的(ViewModelStore内部含有map)。ViewModelStore对于宿主activity是唯一的。其实质是宿主activity中HoldFragment的一个成员变量。
稍微封装一下ViewModel,使用bus来管理页面内的共享数据。
我们一个activity页面肯定不止一个类。尤其是页面比较复杂的时候,一个页面有很多block。如果一个block中的某些数据/view需要另外一个block中使用,那怎么办呢?我们一般的做法就是把这个数据存成一个成员变量,set到另外一个block中。或另外一个block需要时,直接拿这个成员变量。但这样会造成,两个block之间之间耦合。设想一下,如果两个block层级比较深,那么两个block之间进行共享数据时,需要把两个block之间需要的类都进行之间耦合。
举一个我以前遇到过的例子:一个页面做完了,pm找我做页面的埋点。埋点需要页面的movieId信息,但是需要埋点的那个block中并没有movieId。并且我这个block层级很深。如果想拿到movieId,我需要从activity页面层级一层层传到我这个block中,免不了中间层级的耦合和方法的创建。当时觉得这件事真是让人头大。那时候多么需要有个像事件监听形式的eventbus那样的东西,我只需要把数据放到bus里面,然后这个页面的任何一个地方都能很方便的获取。现在有了viewModel就可以这么做了:
public class ActivityDataBus {
public static T getData(Context context, Class tClass) {
return getData(checkContext(context),tClass);
}
public static T getData(Activity context, Class tClass) {
return getData(checkContext(context),tClass);
}
public static T getData(FragmentActivity context, Class tClass) {
return ViewModelProviders.of(context).get(tClass);
}
private static FragmentActivity checkContext(Context context) {
if(context instanceof FragmentActivity) return (FragmentActivity) context;
throw new IllegalContextException();
}
public static class ActivityShareData extends ViewModel {}
public static class IllegalContextException extends RuntimeException {
public IllegalContextException() {
super("ActivityDataBus 需要FragmentActivity作为上下文!");
}
}
}
为了深入理解上面功能的具体实现,以及ViewModel怎么在屏幕旋转时仍然保持数据。最好对ViewModel进行源码解析,并且源码并不难,所以这里阐述一下。好的,那么开始吧~
我们从ViewModelProvider入手。ViewModel通过ViewModelProvider的
* Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
* an activity), associated with this {@code ViewModelProvider}.
*
* The created ViewModel is associated with the given scope and will be retained
* as long as the scope is alive (e.g. if it is an activity, until it is
* finished or process is killed).
public T get(Class modelClass)
方法来获取。如果没有modelClass对应的ViewModel,那么会新生成一个。ViewModel的存在时间和相应的的宿主一致,下面会说。传入的modelClass类型需要是ViewModel的子类。
ViewModelProvider怎么创建?通过ViewModelProviders.of(宿主activty/fragment).为了便于理解。先说下ViewModelProvider构造。
ViewModelProvider构造的时候需要提供ViewModelStore和ViewModelProvider.Factory
其中ViewModelProvider.Factory用了产生viewHolder:
public interface Factory {
/**
* Creates a new instance of the given {@code Class}.
*
*
* @param modelClass a {@code Class} whose instance is requested
* @param The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
T create(Class modelClass);
}
其实现一般是使用ViewModelProviders#DefaultFactory:
@Override
public T create(Class 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);
}
super.create()为:
@Override
public T create(Class 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);
}
}
即,如果modelClass类型是AndroidViewModel的子类,那么使用带Application参数的构造生成modelClass实例。如果是ViewModel的子类,那么使用空构造生成modelClass实例。
ViewModelStore用来存储ViewModels:
private final HashMap mMap = new HashMap<>();
ViewModelStore实例怎么来呢?
ViewModelStore不是随便new出来的,而是在HolderFragment中实例化的。ViewModelStore依赖于HolderFragment而存在:
public class HolderFragment extends Fragment {
private ViewModelStore mViewModelStore = new ViewModelStore();
@Override
public void onDestroy() {
super.onDestroy();
mViewModelStore.clear();
}
public ViewModelStore getViewModelStore() {
return mViewModelStore;
}
...
}
HolderFragment的产生:通过一个宿主activity/fragment获取已经存在的或新产生的一个HolderFragment。HolderFragment的生命周期和其宿主的相同。看下怎么获取的,宿主以activty为例:
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;
}
通过findHolderFragment()先看是不是已经存在tag为HOLDER_TAG的Fragment:
Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
注意,旋转屏幕时,activty内置的HolderFragment不会被销毁(为什么旋转时不会被销毁,是因为在HolderFragment中指定了setRetainInstance(true);),且fragmentManager.findFragmentByTag(HOLDER_TAG)能够找到内置的HolderFragment。
因为HolderFragment中存储map ,所以这就解释了为什么activity在旋转的时候,viewModel不会丢失。
旋转屏幕时,原来的activty会被销毁,重新生成一个新的activty。
HolderFragmentManager中map类型的成员变量mNotCommittedActivityHolders是干什么的呢?不是为了屏幕旋转问题而生的。他里面对应的Entry的生存时间是“new出来HolderFragment时刻 到HolderFragment执行onCreate()执行完的时刻”,如果HolderFragment始终没有执行onCreate()那么等到activty销毁的时候,会清除mNotCommittedActivityHolders中对应的Entry。既然不是为屏幕旋转问题而生,那为什么要弄个这个呢?因为fragment在commit()以后不会立即执行commit()动作,会使用handler来把这次的commit放到主线程消息队列中,等待执行。所以如果fragment在没有真正执行commit()动作的时候,使用manager.findFragmentByTag(HOLDER_TAG)找不到对应的HolderFragment,那么用mNotCommittedActivityHolders这样方式来兜底。
回到ViewModelStore那里。
所以我们要想得到一个ViewModelStore,我们需要先根据宿主产生一个HolderFragment,再拿到里面初始化好的ViewModelStore成员变量。使用ViewModelStores工具类即可:
public class ViewModelStores {
private ViewModelStores() {
}
/**
* Returns the {@link ViewModelStore} of the given activity.
*
* @param activity an activity whose {@code ViewModelStore} is requested
* @return a {@code ViewModelStore}
*/
@MainThread
public static ViewModelStore of(FragmentActivity activity) {
return holderFragmentFor(activity).getViewModelStore();
}
/**
* Returns the {@link ViewModelStore} of the given fragment.
*
* @param fragment a fragment whose {@code ViewModelStore} is requested
* @return a {@code ViewModelStore}
*/
@MainThread
public static ViewModelStore of(Fragment fragment) {
return holderFragmentFor(fragment).getViewModelStore();
}
}
其中of(FragmentActivity activity)/of(Fragment fragment) 参数就是宿主。
所以ViewModelProvider是这么产生的
(在ViewModelProviders中,ViewModelProviders是生产ViewModelProvider的工具类):
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
initializeFactoryIfNeeded(activity.getApplication());
return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
}
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
initializeFactoryIfNeeded(activity.getApplication());
return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
}
所以使用ViewModelProviders of(宿主)方法来获得的ViewModelProvider。ViewModelProvider里面的viewModel的生存时间和宿主一致。
我们知道serviceloader.getService(applicationContex,xxx.class)是获取一个全局唯一的服务实例,或者new一个服务实例。并不能获取一个非单例的实例(比如在A block生成了一个view,然后B block想获取这个view,并不能通过serviceloader来实现这种解耦),而如果A block 和B block是同一个activty页面,那么使用
ViewModelProviders.of((FragmentActivity) getContext()).get(MajorCommentViewViewModel.class)是可以做到的。这让我们又多了一种解耦方法。甚至,如果方便的话,在同一个activty页面,我们不使用set() get()方法就可以在不用block类中使用其他block中的实例,用来监听通信数据交流等。
好了~这次就讲到这里吧