Android-ViewModel原理解析

Android-ViewModel原理解析,第1张

在这四个方法中,其实唯一的区别就是要不要传Factory,当没有传自定义的Factory的时候,则会传入默认的Factory,我们看ViewModelProvider构造器的源码和部分of方法的源码:

在ViewModelProvider中需要传入一个VieModelStore对象,这个对象是由ViewModelStoreOwner来提供的,而在Activity或者Fragment中,是由Activity和Fragment来提供的,因为ViewModelStoreOwner是一个接口,而AppCompatActivity的祖父ComponentActivity和Fragment均实现了ViewModelStoreOwner接口。

但是ViewModelProviders在新的lifecycle-extensions库中,已经是属于被弃用的。新版的API直接使用ViewModelProvider,而不是ViewModelProviders。

比如:

可以如下的方式在baseActivity中添加,由子类Activity调用:

创建ViewModel对象,首先就需要先初始化一个ViewModelProvider对象

可以看出,ViewModelProvider构造函数其实最终都是需要两个参数,一个是ViewModelStoreOwner对象,一个是Factory。而ViewModelStoreOwner其实就是用来获取一个ViewModelStore对象来保存ViewModel对象的。而Factory就是用来创建ViewModel对象的。

这个接口的主要实现的作用就是返回一个ViewModelStore对象。在Android中,Activity和Fragment都会实现该接口,并且实现getViewModelStore()方法。

比如Activity就是在FragmentActivity的父类ComponentActivity中实现了ViewModelStoreOwner接口

Fragment的ViewModelStore其实是由FragmentManager进行管理获取

每个FragmentActivity都会有一个自己的FragmentManager对象,所以每个FragmentManagerViewModel对象,管理的是一个FragmentActivity中的所有的Fragment对应的ViewModel。具体看FragmentManagerViewModel的getViewModelStore方法

从这里可以看出,每个Fragment都会有自己的ViewModelStore对象,而ViewModelStore对象,是根据每个Fragment的唯一标识进行创建的。

ViewModelStore类对象,是每个Activity或者Fragment都有一个,目的是用于保存该页面的ViewModel对象,方便ViewModel的管理

从ViewModelProvider的get方法中,可以看出,get方法传入的是一个ViewModelclass的Class类型,然后通过这个类型,得到ViewModel的规范名称。将ViewModel对象缓存在ViewModelStore中的HashMap中。而ViewModel的创建,其实是通过ViewModelProviderFactory来实现的

ViewModelProviders的of方法,用于返回一个ViewModelProvider对象

从这里我们可以看到,如果传入的Activity或者Fragment有getDefaultViewModelProviderFactory方法实现,而factory为null的时候,则会通过getDefaultViewModelProviderFactory创建对应的Factory,而如果没有getDefaultViewModelProviderFactory的实现,那么就会调用NewInstanceFactory来创建对应的Factory,而NewInstanceFactory其实就是创建AndroidViewModelFactory对象。

最终ViewModel对象,其实就是通过AndroidViewModelFactory的create的方法实现来创建。一般就是通过ClassnewInstance或者ClassgetConstructor来创建对象。

而ViewModelProvider的第一个参数,其实最终传入的是ViewModelStore对象,这个对象内部是通过一个HashMap来保存ViewModel对象

而新版的源码,ViewModelStore对象是通过Fragment和FragmentActivity对象的getViewModelStore方法来获取,而原先的HolderFragment的功能都移植到了Fragment中

HolderFragment通过设置setRetainInstance(true),使得自身能够不受到屏幕旋转等configuration

changes影响而存活,直到依附的Activity正常结束。

因为HolderFragment的生命周期,ViewModelStore对象保存在HolderFragment中,而ViewModel又存储在ViewModelStore中,这就是为什么我们说ViewModel类能够让数据在屏幕旋转等配置信息改变导致UI重建的情况下不被销毁。

ViewModelProvider的get方法:

在ViewModel中,有两种Factory,Factory是的类型是由ViewModelProvider在初始化的时候创建的,所以是由ViewModelProvider决定Factory的类型。在ViewModelProvider中,有两种Factory,一种是默认的Factory,默认的Factory是通过在ComponentActivity或者Fragment中实现HasDefaultViewModelProviderFactory接口,然后在getDefaultViewModelProviderFactory()方法中初始化一个SavedStateViewModelFactory对象;另一种Factory则是NewInstanceFactory,这种是通过NewInstanceFactorygetInstance()的单例方式获取。

其实就是通过ViewModel的Class对象,然后通过反射创建ViewModel对象,然后保存到ViewModelStore中的Map集合中

从ViewModelProvider的get方法可以看出,在ViewModelProvider的get方法中会根据Factory的类型,进行不同方法的调用。SavedStateViewModelFactory是实现了ViewModelProviderKeyedFactory接口的,所以在创建ViewModel的时候,调用的是SavedStateViewModelFactory的create方法。

AndroidViewModel和ViewModel的构造器参数Class

ViewModel保存和恢复数据

ComponentActivity和Fragment都将数据的保存和恢复逻辑转发给了SavedStateRegistryController。在在onCreate方法里通过调用performRestore来恢复数据,在onSaveInstanceState方法里通过调用performSave来保存数据。而SavedStateRegistryController中的SavedStateRegistry对象,就是实际进行数据的保存和恢复的,在SavedStateRegistry通过唯一的key获取到一个SavedStateProvider,而SavedStateProvider其实就是返回需要保存的数据,将对应的需要缓存的数据一一返回,然后保存在系统缓存时的回调到onSaveInstanceState的方法参数Bundle中进行保存。

SavedStateRegistryperformSave()

该方法是由ComponentActivity的onSaveInstanceState方法触发调用SavedStateRegistryController的performSave,进而调用的

在SavedStateRegistry恢复数据的时候,会把恢复后的数据都交给SavedStateHandle。希望保留的数据,可以通过两种方式向mRegular保存数据。

在ComponentActivity恢复数据的时候,会通过SavedStateRegistryControllerperformSave在Activity的onSaveInstanceState方法中进行数据的保存,然后在ComponentActivity的onCreate方法中,通过调用SavedStateRegistryControllerperformRestore方法进行数据的恢复,这些恢复的数据都会保存在SavedStateHandleController对象中的SavedStateHandle属性中,然后在Activity重新创建的时候,会通过反射创建对应的ViewModel对象的时候,将SavedStateHandleController中的SavedStateHandle赋值给对应的ViewModel进行数据恢复。

这块的源码分析可以参考:

从源码看 Jetpack(7)-SavedStateHandle源码详解

这里其实就是直接使用Class的newInstance直接创建对象。Activity和Fragment一般都是使用SavedStateViewModelFactory创建ViewModel对象。

ViewModel的销毁,要分为Activity和Fragment两部分。

首先看下ViewModel在销毁的时候做的事情

而ViewModel的clear()方法的调用,是在ViewModelStore中

Activity的销毁,是通过Lifecycle监听生命周期回调,当生命周期执行到onDestroy的时候,调用ViewModelStore的clear()方法进行ViewModel的销毁。

看ComponentActivity中构造器中的实现:

Fragment的生命周期管理,如下:

Fragment的生命周期,首先会依次增大,然后在从onResume变成onPause的时候,就开始状态码减小。即先升再降的一个状态变化。在当前状态码变成CREATED的时候,就会执行onDestroy。即调用

FragmentStateManagerdestroy

在这里就会调用nonConfigclearNonConfigState方法,nonConfig其实就是FragmentManagerViewModel对象。

FragmentManagerViewModelclearNonConfigState

按照上面的逻辑,在Activity重建时会执行destory生命周期事件,那么为什么ViewModel没有销毁呢?

其实就是在屏幕旋转的时候,AMS通过Binder回调Activity的retainNonConfigurationInstances()方法,这个时候就会进行数据的保存,保存到一个NonConfigurationInstances对象;而在屏幕翻转结束之后,会再一次调用ViewModelProvider的构造函数,此时就会调用ownergetViewModelStore(),接着就会调用getLastNonConfigurationInstance(),这里就会通过Activity中的NonConfigurationInstances对象取出保存的ViewModelStore对象。

所以数据保存就是通过retainNonConfigurationInstances()方法保存在NonConfigurationInstances对象,而再一次使用取出ViewModel的数据的时候,就是从nc对象中取出ViewModelStore对象,而ViewModelStore对象保存有ViewModel集合

通过对ComponentActivity的getViewModelStore()方法进行分析。可以找到这个问题的答案。

当mViewModelStore为null的时候,会从NonConfigurationInstances中获取ViewModelStore对象。

其实在ComponentActivity和Activity中都会有一个NonConfigurationInstances类,而Activity中的NonConfigurationInstances类结构如下:

这里的Object activity其实就是保存的ComponentActivity中的NonConfigurationInstances类对象,看Activity的下面的方法:

activity这个Object对象,其实是通过onRetainNonConfigurationInstance()方法返回值赋值,而onRetainNonConfigurationInstance()方法的实现是在ComponentActivity中。

看ComponentActivity中的下面方法:

因为这里会在ComponentActivity中的NonConfigurationInstances类对象中保存ViewModelStore对象,所以这也是Activity重建时不会销毁ViewModel的原因。

onRetainNonConfigurationInstance()方法除了被Activity的retainNonConfigurationInstances()调用以外,还会被LocalActivityManager的dispatchRetainNonConfigurationInstance()方法调用

在分析ViewModel的销毁过程时,我们看到Activity与Fragment存储VieModel是分离的,那么同一个Activity下的Fragment是如何共享ViewModel的呢?

其实共享的是Activity的ViewModel。

而具体的实现逻辑,其实就是在FragmentViewModelLazykt中的:

在Fragment中可以直接调用,这是一个Fragment的扩展函数,通过实现requireActivity()viewModelStore,获取到了Activity的ViewModelStore对象后,这样就可以实现了Fragment共用Activity的ViewModel,从而实现了Fragment之间共享ViewModel。

Fragment之间共享ViewModel,需要引入

一般我们使用TabLayout切换Fragment中展示内容,既然不销毁加载好的内容,那就是不销毁Fragment就能实现了。

不销毁Fragment就是对已经存在的Fragment进行缓存

传统的java方法存储一个唯一的对象采用HashMap,但是Google用SparseArray替代HashMap来提高效率。

这里是根据position进行缓存 ,position一般就是TabLayout的子Tab的序号或者ViewPager的Postion位置。

public void intialiseViewPager()

{

List<Fragment> fragments = new Vector<Fragment>();

numberOfTabs = applicationcurrentReportgetODTabsList()size();

for (int i = 0; i < numberOfTabs; i++)

{

ODTab tempTab = applicationcurrentReportgetODTabsList()get(i);

if (tempTabgetTabType()equals(ODGridXML_GRID_ELEMENT))

{

GridFragment gridFragment = GridFragmentnewInstance(tempTabgetTabId());

fragmentsadd(gridFragment);

}

else if (tempTabgetTabType()equals(ODChartXML_CHART_ELEMENT))

{

NewChartFragment chartFragment = NewChartFragmentnewInstance(tempTabgetTabId());

fragmentsadd(chartFragment);

}

}

Logd(TAG, "Current report fragments set to adapter: "+fragmentstoString());

mPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(), fragments);

mViewPager = (ViewPager)findViewById(Ridpager);

mViewPagersetAdapter(mPagerAdapter);

mViewPagersetOffscreenPageLimit(0);

mViewPagersetOnPageChangeListener(this);

}

你可以看到我将字符串传递到片段 ( tempTabgetTabId() ),在这一行:

GridFragment gridFragment = GridFragmentnewInstance(tempTabgetTabId());

当片段中它自已我这样做对其进行初始化:

public static final GridFragment newInstance(String tabId)

{

GridFragment f = new GridFragment();

Bundle bdl = new Bundle(2);

bdlputString(TAB_ID, tabId);

fsetArguments(bdl);

return f;

}

@Override

public void onCreate(Bundle savedInstanceState)

{

String tabId = getArguments()getString(TAB_ID);

if (applicationcurrentReport != null)

{

thisodTab = applicationcurrentReportgetODTabByTabId(tabId);

}

else

{

startActivity(new Intent(getActivity(), LoginScrActivityclass));

}

superonCreate(savedInstanceState);

}

所有这一切被执行,不会覆盖默认空的构造函数的片段,按照我的理解这非常不建议。

现在我需要获取实例的 odTab 对象放进这行中的当前可见片段:

thisodTab = applicationcurrentReportgetODTabByTabId(tabId);

可能有些人请给我解释如何做到这吗?所以我可以把它的 odTab 对象,如何获取当前可见片段实例

更新:这里提出的意见与我加入了 odTab 到调用的应用程序类的对象实例 currentVisibleTab 和我设置 odTab 这样的实例:

@Override

public void setUserVisibleHint(boolean isVisibleToUser)

{

Logd(TAG, "setUserVisibleHint invoked!");

supersetUserVisibleHint(isVisibleToUser);

if (isVisibleToUser)

{

if (odTab != null && getActivity() != null)

{

Logd(TAG, "Currently visable tab: "+odTabgetTabTitle());

applicationcurrentVisibleTab = odTab;

}

}

}

更新 2:我有一种 ViewPagerAdapter :

public class ViewPagerAdapter extends FragmentPagerAdapter

{

private List<Fragment> fragments;

/

@param fm

@param fragments

/

public ViewPagerAdapter(FragmentManager fm, List<Fragment> fragments) {

super(fm);

thisfragments = fragments;

}

/ (non-Javadoc)

@see androidsupportv4appFragmentPagerAdapter#getItem(int)

/

@Override

public Fragment getItem(int position) {

return thisfragmentsget(position);

}

/ (non-Javadoc)

@see androidsupportv4viewPagerAdapter#getCount()

/

@Override

public int getCount() {

return thisfragmentssize();

}

@Override

public int getItemPosition(Object object) {

return POSITION_NONE;

}

public void removeAllFragments()

{

thisfragmentsclear();

}

public void addFragmentsListToAdapter(List<Fragment> fragments)

{

thisfragmentsaddAll(fragments);

}

public List<Fragment> getFragments()

{

return fragments;

}

}

在它正如你看到的我有 List 的片段所示的 ViewPager ,此列表初始化像这样:

List<Fragment> fragments = new Vector<Fragment>();

我不明白的是如何获取对当前段落触发从该列表中的接口方法的引用。不相关的问题,但也许你知道答案,有什么区别之间List 和 Vector

我还查阅了此选项。

先谢谢了。

解决方法 1:

我找到的方法 visbile 片段当前正在使用 setUserVisibleHint 和回视图寻呼机的接口。此方法是在类中片段。重写它和使用回叫您查看传呼机的接口。如果该片段是可见-即它返回 true-只使用对您片段 (无论是从列表中备份您的适配器或一个您存储为一个实例变量) 的引用以获取您需要的任何从片段本身。我已经添加下面的代码。

声明有关的无论你想做一个接口。我的情况我用这来禁用 ViewPager 默认 x 方向侦听器时一个谷歌地图实例是可见的因此滚动地图没有触发片段的变化。

public interface OnMapFragmetnVisibleListener{

public void mapVisible(boolean visible);

}

在片段:

@Override

public void onAttach(Activity activity){

superonAttach(activity);

try{

mapFragmentVisibilityListener = (OnMapFragmentVisibleListener) activity;

isAttached = true; //flag for whether this framgnet is attache to pager

} catch (ClassCastException e){

throw new ClassCastException(activitytoString() + " must implement interface onAttach");

}

@Override

public void setUserVisibleHint(boolean isVisibleToUser){

if(isVisibleToUser && isAttached){ //if listner is called before fragment is attached will throw NPE

mapFragmentVisibilityListenermapVisible(true);

}

}

直到我试过这个是不明显的一件事是 isAttached 变量的加法。我也重写片段 onAttach 方法,并将其设置为 true,一旦它被称为。否则会发生什么是您片段将对用户是可见的和尝试调用的接口向您 ViewPager 接口初始化之前。

在我 ViewPager 我只是实现了 OnMapFragmentVisibleListener,然后添加所需的方法

@Override

public void mapVisible(boolean visible) {

if(visible)

viewPagerallowSwipe(false); //turn off viewPager intercept touch if map visible

}

在我的案子我用它来关闭 ViewPager 刷功能但是,我可以轻松地叫回来的公共方法中我 mapFragment。我碰巧有 3 片段我 ViewPager 我保持对的引用

更新

要查找当前的片段首先显示你需要找出哪个片段正在显示你可以通过多种方式。最简单的方法就是通过一些描述符返回到您ViewPager / FragmentActivity 与您之前创建的接口。

public interface OnFragmentVisibleListener{

public void fragmentVisible(boolean true, String tag);

}

那您设置的问题是您正在使用 Vector ,是真的同步 List 来存储您片段 rerefences。其结果是,没有通过,你可以找到你片段以后的密钥值。片段有确定的一个标签,创建在片段获利的能力,它们被添加到该片段活动期间。然而,在 ViewPager设置,您没有添加片段分别,所以无法添加标签在获利。

我的解决方案一直是回我 BasePagerAdapter 与 HashMap 在其中存储由一个唯一的密钥标识每个片段。

LinkedHashMap<String, Fragment> tabs = new LinkedHashMap<String, Fragment>();

tabsput("Frag1", fragment1);

tabsput("Frag2", fragment2);

tabsput("Frag3", fragment3);

然后用此适配器

private class BasePagerAdapter extends FragmentPagerAdapter{

private LinkedHashMap<String, Fragment> tabs;

public BasePagerAdapter(LinkedHashMap<String, Fragment> tabMap, FragmentManager fm)

{

super(fm);

thistabs = tabMap;

}

// other basic methods

因为您的引用存储到一个密钥值、 容易找到因为你只是可以通过从键值哈希映射和返回键搜索你要找的 framgment。

如果您不能更改设置要逃离 Vector 你需要到任一 (1) 跟踪的哪个片段你添加的顺序 (假设片段不添加和删除动态) 这样,你可以通过对每个片段 (2) 保持单独引用或索引

private Fragment x;

//in onCreate

x = new Fragment();

//in interface

if(tagequals("x"){

xdoSomething();

}

前面介绍了ViewModel的生命周期,知道了其会在宿主activity或者fragment销毁时被销毁(config change造成的除外);如下图:

但是没有深究其中的原理,虽然这并不影响我们的使用;但是为了知识的完成性,我们最好还是深入分析下其中的原理,同时,一些设计思想还是非常巧妙的,个人觉得是有很借鉴价值的。

首先让我们回顾下ViewModel的获取方式

MyViewModel model = ViewModelProvidersof(this)get(MyViewModelclass);

前面也说过,这其实是从创建的ViewModelProvider中得到ViewModelStore;而ViewModelStore是和宿主实例一一对应的;从ViewModelStore中获取相应classmodel对应的ViewModel实例;因此ViewModel生命周期其实是与ViewModelStore一致的;我们从这里入手进行分析,为什么config change(例如,屏幕旋转)造成的宿主销毁不会影响ViewModel的生命周期呢?

需要分开进行分析;

当ViewModel的宿主是普通的activity或者fragment的时候,需要利用HolderFragment来保证config change时的生命周期,其原理如下

可见;对于normal宿主,会通过holderFragmentFor(activity)返回的HolderFragment来得到ViewModelStore实例;宿主和HolderFragment一一对应;

宿主对应的HolderFragment持有相应的ViewModelStore

主要逻辑都在HolderFragmentManager中;其主要原理是利用了嵌套fragment;需要用到的一些基础知识

参考 >

以上就是关于Android-ViewModel原理解析全部的内容,包括:Android-ViewModel原理解析、Fragment缓存并防止内存泄露、如何获取ViewPager当前的Fragment等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9770458.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-01
下一篇 2023-05-01

发表评论

登录后才能评论

评论列表(0条)

保存