ViewModel是如何在配置更改后继续留存数据的
文章目录
- ViewModel是如何在配置更改后继续留存数据的
- 系统是如何保留和恢复`NonConfigurationInstances`的
- ViewModel 和 onSaveInstanceState 情况一样吗
ViewModel是如何在配置更改后继续留存数据的
ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据,ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。
那么我们今天就探索下,配置更改后,是如何继续留存数据的。
首先我们看下如何创建ViewModel
实例:
class CustomFactory : ViewModelProvider.Factory {override fun <T : ViewModel?> create(modelClass: Class<T>): T {return when (modelClass) {MainViewModel::class.java -> {MainViewModel()}else -> throw IllegalArgumentException("Unknown class $modelClass")} as T}
}
创建ViewModel
实例
// 1
val viewModelProvider = ViewModelProvider(this, CustomFactory())
// 2
val viewModel: MainViewModel= viewModelProvider.get(MainViewModel::class.java)
// 或者使用KTX来创建
//val model : MainViewModel by viewModels { CustomFactory() }
ViewModelProvider
源码
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull ViewModelProvider.Factory factory) {// 3this(owner.getViewModelStore(), factory);
}public ViewModelProvider(@NonNull ViewModelStore store, @NonNull ViewModelProvider.Factory factory) {this.mFactory = factory;this.mViewModelStore = store;
}
ViewModelStoreOwner
源码
public interface ViewModelStoreOwner {@NonNullViewModelStore getViewModelStore();
}
看看上面这段代码做了什么:
首先创建一个ViewModelProvider实例,
ViewModelProvider
构造函数的第一个参数是ViewModelStoreOwner
,那为什么可以传入Activity
实例呢,是因为ComponentActivity
实现了ViewModelStoreOwner
这个接口;通过
get(@NonNull Class<T> modelClass)
方法 获取到ViewModel
的实例;通过
owner.getViewModelStore()
获取到ViewModelStore
,也就是ComponentActivity
的getViewModelStore()
方法。
然后我们看下ViewModelProvider#get()
方法:
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {String canonicalName = modelClass.getCanonicalName();if (canonicalName == null) {throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");} else {return this.get("androidx.lifecycle.ViewModelProvider.DefaultKey:" + canonicalName, modelClass);}
}@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {ViewModel viewModel = this.mViewModelStore.get(key);if (modelClass.isInstance(viewModel)) {if (this.mFactory instanceof ViewModelProvider.OnRequeryFactory) {((ViewModelProvider.OnRequeryFactory)this.mFactory).onRequery(viewModel);}return viewModel;} else {if (viewModel != null) {}if (this.mFactory instanceof ViewModelProvider.KeyedFactory) {viewModel = ((ViewModelProvider.KeyedFactory)this.mFactory).create(key, modelClass);} else {viewModel = this.mFactory.create(modelClass);}this.mViewModelStore.put(key, viewModel);return viewModel;}
}
从在上面的代码中可以发现,ViewModelStore
中持有一个HashMap
,如果ViewModelStore
中有缓存的ViewModel
实例,就直接返回,否则创建新的实例并存入到ViewModelStore
中。
接下来我们看下 ComponentActivity#getViewModelStore()
方法:
@NonNull
public ViewModelStore getViewModelStore() {if (this.getApplication() == null) {throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");} else {this.ensureViewModelStore();return this.mViewModelStore;}
}void ensureViewModelStore() {if (this.mViewModelStore == null) {ComponentActivity.NonConfigurationInstances nc = (ComponentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();if (nc != null) {this.mViewModelStore = nc.viewModelStore;}if (this.mViewModelStore == null) {this.mViewModelStore = new ViewModelStore();}}}
从以上代码中可以看出:
先是从
Activity
的getLastNonConfigurationInstance()
获取一个NonConfigurationInstances
实例nc
;如果这个
nc
不等于空,就将nc
的viewModelStore赋值给ComponentActivity
的mViewModelStore
字段;如果
mViewModelStore
还是为null
,就创建一个新的mViewModelStore
对象;
很明显Activity
的getLastNonConfigurationInstance()
是缓存ViewModelStore
的核心。
@Nullable
public Object getLastNonConfigurationInstance() {return mLastNonConfigurationInstances != null? mLastNonConfigurationInstances.activity : null;
}
这个方法返回的是 Activity
的onRetainNonConfigurationInstance()
持有的对象,但是Activity
的这个方法返回的是null
。
根据源码注释可知,一旦配置更改销毁Activity,系统将为新配置创建一个新的Activity实例时,将由Android系统调用此方法。
我们可以在这个方法返回任何对象,包括Activity实例本身,稍后可以通过在新Activity实例中调用getLastNonfigurationInstance()
来检索到它。
继续跟踪源码发现在Activity
的retainNonConfigurationInstances()
中调用了onRetainNonConfigurationInstance()
方法。
NonConfigurationInstances retainNonConfigurationInstances() {// 1Object activity = onRetainNonConfigurationInstance();HashMap<String, Object> children = onRetainNonConfigurationChildInstances();FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();......ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();// 2if (activity == null && children == null && fragments == null && loaders == null&& mVoiceInteractor == null) {return null;}// 3NonConfigurationInstances nci = new NonConfigurationInstances();nci.activity = activity;nci.children = children;nci.fragments = fragments;nci.loaders = loaders;......return nci;
}
从以上代码中可以看出:
先是调用
onRetainNonConfigurationInstance()
获取一个对象,这个对象可以是任何我们想要在配置更改时要保存的对象;只要这些对象全部为
null
,才会不返回数据;创建一个静态内部类
NonConfigurationInstances
的实例nci
,然后将赋值给nci
的activity
字段;
那么谁实现了 Activity
的onRetainNonConfigurationInstance()
呢,通过追踪代码发现是ComponentActivity
重写了这个方法:
public final Object onRetainNonConfigurationInstance() {Object custom = this.onRetainCustomNonConfigurationInstance();ViewModelStore viewModelStore = this.mViewModelStore;ComponentActivity.NonConfigurationInstances nci;if (viewModelStore == null) {nci = (ComponentActivity.NonConfigurationInstances) this.getLastNonConfigurationInstance();if (nci != null) {viewModelStore = nci.viewModelStore;}}if (viewModelStore == null && custom == null) {return null;} else {nci = new ComponentActivity.NonConfigurationInstances();nci.custom = custom;nci.viewModelStore = viewModelStore;return nci;}
}
总结一下:
- 如果我们想要在系统配置发生更改时保存任何对象,并在系统为新配置创建新Activity实例时恢复这个对象,那么我们只需要重写Activity的
onRetainNonConfigurationInstance()
方法即可。 - 根据第一条,当配置更改销毁Activity时,
onRetainNonConfigurationInstance()
保存了ViewModelStore
,而ViewModel
又通过ViewModelStore
来存取,因此当Activity
重建时,就能获取到之前的ViewModel
。
系统是如何保留和恢复NonConfigurationInstances
的
从上一个章节我们知道,Activity
的onRetainNonConfigurationInstance()
是由系统调用的,那么系统在什么时机调用的呢?
根据经验,ActivityThread
负责Activity
的调度和执行,那么我们就去ActivityThread
中搜索下Activity
的retainNonConfigurationInstances()
方法。
提示:Android Framework源码一般会有如下的关系链路:
schedule
(安排上) ————>handler
(处理) ————>perform
(执行) ————>on
(在进行)
ActivityThread
的performDestroyActivity()
方法
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,int configChanges, boolean getNonConfigInstance, String reason) {ActivityClientRecord r = mActivities.get(token);Class<? extends Activity> activityClass = null;if (localLOGV) Slog.v(TAG, "Performing finish of " + r);if (r != null) {activityClass = r.activity.getClass();r.activity.mConfigChangeFlags |= configChanges;if (finishing) {r.activity.mFinished = true;}performPauseActivityIfNeeded(r, "destroy");if (!r.stopped) {callActivityOnStop(r, false /* saveState */, "destroy");}// 1if (getNonConfigInstance) {try {r.lastNonConfigurationInstances= r.activity.retainNonConfigurationInstances();} catch (Exception e) {if (!mInstrumentation.onException(r.activity, e)) {...}}}try {r.activity.mCalled = false;mInstrumentation.callActivityOnDestroy(r.activity);if (!r.activity.mCalled) {...}if (r.window != null) {r.window.closeAllPanels();}} catch (SuperNotCalledException e) {throw e;} catch (Exception e) {...}r.setState(ON_DESTROY);}schedulePurgeIdler();synchronized (mResourcesManager) {mActivities.remove(token);}StrictMode.decrementExpectedActivityCount(activityClass);return r;
}
当Activity
因配置更改被销毁重建时,会调用ActivityThread
的handleRelaunchActivityInner()
方法:
private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,PendingTransactionActions pendingActions, boolean startsNotResumed,Configuration overrideConfig, String reason) {...// 1handleDestroyActivity(r.token, false, configChanges, true, reason);...// 2handleLaunchActivity(r, pendingActions, customIntent);
}
从上面的代码可以看出:
- 首先执行
handleDestroyActivity
,并且将getNonConfigInstance
设为true
, 这样就可以将Activity
的onRetainNonConfigurationInstance()
保留的数据,保存到ActivityClientRecord
中; - 然后执行
handleLaunchActivity()
,重建Activity
,并将数据恢复。
ActivityThread
的performLaunchActivity()
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {ActivityInfo aInfo = r.activityInfo;if (r.packageInfo == null) {r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,Context.CONTEXT_INCLUDE_CODE);}ComponentName component = r.intent.getComponent();if (component == null) {component = r.intent.resolveActivity(mInitialApplication.getPackageManager());r.intent.setComponent(component);}if (r.activityInfo.targetActivity != null) {component = new ComponentName(r.activityInfo.packageName,r.activityInfo.targetActivity);}ContextImpl appContext = createBaseContextForActivity(r);// 1Activity activity = null;try {java.lang.ClassLoader cl = appContext.getClassLoader();activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);StrictMode.incrementExpectedActivityCount(activity.getClass());r.intent.setExtrasClassLoader(cl);r.intent.prepareToEnterProcess();if (r.state != null) {r.state.setClassLoader(cl);}} catch (Exception e) {if (!mInstrumentation.onException(activity, e)) {...}}Application app = r.packageInfo.makeApplication(false, mInstrumentation);...if (activity != null) {...appContext.getResources().addLoaders(app.getResources().getLoaders().toArray(new ResourcesLoader[0]));// 2appContext.setOuterContext(activity);activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback,r.assistToken);if (customIntent != null) {activity.mIntent = customIntent;}r.lastNonConfigurationInstances = null;...int theme = r.activityInfo.getThemeResource();if (theme != 0) {activity.setTheme(theme);}activity.mCalled = false;if (r.isPersistable()) {mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);} else {mInstrumentation.callActivityOnCreate(activity, r.state);}...r.activity = activity;...}r.setState(ON_CREATE);synchronized (mResourcesManager) {mActivities.put(r.token, r);}...return activity;
}
从以上代码可以看出:
- 创建
Activity
的实例; attach
数据,将因配置更改销毁Activity
留存到ActivityClientRecord
中持有的lastNonConfigurationInstances
赋值给新建的Activity
;- 之后就可以通调用
Activity
的getLastNonConfigurationInstance()
检索到onRetainNonConfigurationInstance()
方法保留的对象。
Activity
的attach()
方法
final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,Application application, Intent intent, ActivityInfo info,CharSequence title, Activity parent, String id,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, String referrer, IVoiceInteractor voiceInteractor,Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {attachBaseContext(context);mFragments.attachHost(null /*parent*/);mWindow = new PhoneWindow(this, window, activityConfigCallback);...mLastNonConfigurationInstances = lastNonConfigurationInstances;...mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);if (mParent != null) {mWindow.setContainer(mParent.getWindow());}mWindowManager = mWindow.getWindowManager();mCurrentConfig = config;...
}@Nullable
public Object getLastNonConfigurationInstance() {return mLastNonConfigurationInstances != null? mLastNonConfigurationInstances.activity : null;
}
上图是Framework源码留存和恢复NonConfigurationInstances
的大致调用流程。
ViewModel 和 onSaveInstanceState 情况一样吗
onSaveInstanceState
方法的使用:
public override fun onSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState)outState.putSerializable("currentScreen", currentScreen)outState.putParcelableArrayList("backstack", backstack)
}
onSaveInstanceState
方法在 Activity
可能被杀死之前被调用,以便当它在将来某个时间返回时可以恢复它的状态。例如,如果Activity
B 在 Activity
A 的前面被启动,在某个时间点 Activity
A 被杀死回收资源,Activity
A 将有机会通过这个方法保存其用户界面的当前状态,这样当用户返回到 Activity
A 时,用户界面的状态可以通过 onCreate()
或 onRestoreInstanceState()
来恢复。
如果该方法被调用,该方法将发生在
android.os.Build.VERSION_CODESP
以后平台版本的应用程序的onStop
之后。对于以较早平台版本为目标的应用程序,该方法将出现在onStop
之前,并且不能保证它将出现在onPause
之前还是之后。
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.leak_canary_leak_activity)if (savedInstanceState == null) {...} else {currentScreen = savedInstanceState.getSerializable("currentScreen") as Screenbackstack = savedInstanceState.getParcelableArrayList<Parcelable>("backstack") as ArrayList<BackstackFrame>}
}
Activity 通常会在下面三种情况下被销毁:
从当前界面永久离开:用户导航至其他界面或直接关闭
Activity
(通过点击返回按钮或执行的操作调用了 finish 方法)。对应Activity
实例被永久关闭;Activity
配置 (configuration) 被改变: 例如,旋转屏幕等操作,会使Activity
需要立即重建;应用在后台时,其进程被系统杀死:这种情况发生在设备剩余运行内存不足,系统又亟须释放一些内存的时候。当进程在后台被杀死后,用户又返回该应用时,
Activity
也需要被重建。
在后两种情况中,我们通常都希望重建 Activity
。ViewModel
会帮我们处理第二种情况,因为在这种情况下 ViewModel
没有被销毁;而在第三种情况下, ViewModel
被销毁了。所以一旦出现了第三种情况,便需要在 Activity
的 onSaveInstanceState
相关回调中保存和恢复 ViewModel
中的数据。
ViewModel是如何在配置更改后继续留存数据的相关推荐
- gitlab部署、配置更改、备份及恢复
1.gitlab部署 官网 gitlab.com Ubuntu14.04安装 1.Install and configure the necessary dependencies sudoapt-ge ...
- Centos7重新配置网络后出现Restarting network (via systemctl): Job for network.service failed because the contr
Centos7重新配置网络后出现Restarting network (via systemctl): Job for network.service failed because the contr ...
- 我的世界java平台缺少证书_tomcat配置https以及配置完成后提示服务器缺少中间证书(已解决)...
tomcat配置https 准备工作 下载好证书文件,下载的时候可以选择为tomcat文件.我这下载下来是压缩包.解压后就是下图的样子. 以.key结尾的文件是证书的key 以.pem结尾的文件是证书 ...
- Win10主题更改后无法变更背景怎么解决
在电脑使用中会有各种不同的主题,我们会不断的的选择自己喜欢的主题,那么遇到主题更改后无法变更背景怎么解决?为此系统屋为你提供Win10主题更改后无法变更背景解决方法,你可以通过操作来快速的解决自己的问 ...
- 此问题可能是由配置更改或安装另一个扩展导致的
未能正确加载"Microsoft.VisualStudio.Editor.Implementation.EditorPackage, Microsoft.VisualStudio.Edito ...
- Flutter配置好后,在Android Studio中找不到设备,no devices
Flutter配置好后,在Android Studio中找不到设备,no devices 完成Flutter的Android配置之后,连上设备,运行flutter doctor,发现已经识别了一个可用 ...
- php nginx exec失败,小白问题:用nginx配置php后nginx无法启动。
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 树莓派raspbian系统用nginx和php7.3-fpm搭建一个简单的web服务器,软件安装完成并启动成功,也能正常访问. 修改/etc/nginx/ ...
- 【GitLab】gitlab上配置webhook后,点击测试报错:Requests to the local network are not allowed...
gitlab上配置webhook后,点击测试报错: Requests to the local network are not allowed 操作如下: 报错: 错误原因: gitlab 10.6 ...
- mitmproxy配置代理后 APP连接不到网络 XPosed安装
本文仅供学习交流使用,如侵立删! mitmproxy配置代理后 APP连接不到网络 配置mitmproxy代理后,有些APP会出现连接不到网络的情况 原因:SSL证书问题 Android5.1系统版本 ...
最新文章
- jquery 设置css样式
- Python学习笔记之基本数据结构方法
- 道路游戏(洛谷 P1070)
- AtCoder Grand Contest 013D: Piling Up 题解
- 索引,表增删改统计,加锁查具体情况(推荐)
- 关于sendmail报错“did not issue MAIL/EXPN/VRFY/ETRN during connection to
- simple java mail
- 65. Valid Number
- Https环境下WS接口两次连续调用出错
- 我的作品-图书馆信息管理系统
- linux配置网卡自动获取的命令,linux 命令行下配置网卡自动获取 IP
- java url authority,Java如何解析url,包括自定义schema的url
- c语言修改字符串c2133,通过create_string_buffer、create_unicode_buffer让C语言具备修改字符串的能力...
- 华为关联公司哈勃投资晟芯网络 持股10%
- 疫情持续两年多职场妈妈更努力存钱,近六成中国内地受访者储蓄可维持生活一年以上...
- 第三阶段应用层——1.3 数码相册—英文和汉字的点阵显示
- 中国关系型社会的环境如何生存发展------总结程序员如何做人做事
- matlab 相位解旋绕,相位解缠绕方法-南京航空航天大学学报.PDF
- 考研高危人群!你是否还在危险的边缘试探?
- CCF论文列表(2022拟定)大更新!MICCAI空降B类!PRCV空降C类!ICLR继续陪跑...