文章目录

  • 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();
}

看看上面这段代码做了什么:

  1. 首先创建一个ViewModelProvider实例,ViewModelProvider构造函数的第一个参数是ViewModelStoreOwner,那为什么可以传入Activity实例呢,是因为ComponentActivity实现了ViewModelStoreOwner这个接口;

  2. 通过 get(@NonNull Class<T> modelClass) 方法 获取到 ViewModel 的实例;

  3. 通过 owner.getViewModelStore()获取到 ViewModelStore,也就是ComponentActivitygetViewModelStore()方法。

然后我们看下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();}}}

从以上代码中可以看出:

  1. 先是从ActivitygetLastNonConfigurationInstance()获取一个NonConfigurationInstances实例nc

  2. 如果这个nc不等于空,就将nc的viewModelStore赋值给ComponentActivitymViewModelStore字段;

  3. 如果mViewModelStore还是为null,就创建一个新的mViewModelStore对象;

很明显ActivitygetLastNonConfigurationInstance()是缓存ViewModelStore的核心。

@Nullable
public Object getLastNonConfigurationInstance() {return mLastNonConfigurationInstances != null? mLastNonConfigurationInstances.activity : null;
}

这个方法返回的是 ActivityonRetainNonConfigurationInstance()持有的对象,但是Activity的这个方法返回的是null

根据源码注释可知,一旦配置更改销毁Activity,系统将为新配置创建一个新的Activity实例时,将由Android系统调用此方法

我们可以在这个方法返回任何对象,包括Activity实例本身,稍后可以通过在新Activity实例中调用getLastNonfigurationInstance()来检索到它。

继续跟踪源码发现在ActivityretainNonConfigurationInstances()中调用了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;
}

从以上代码中可以看出:

  1. 先是调用onRetainNonConfigurationInstance()获取一个对象,这个对象可以是任何我们想要在配置更改时要保存的对象;

  2. 只要这些对象全部为null,才会不返回数据;

  3. 创建一个静态内部类NonConfigurationInstances的实例nci,然后将赋值给nciactivity字段;

那么谁实现了 ActivityonRetainNonConfigurationInstance()呢,通过追踪代码发现是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;}
}

总结一下:

  1. 如果我们想要在系统配置发生更改时保存任何对象,并在系统为新配置创建新Activity实例时恢复这个对象,那么我们只需要重写Activity的onRetainNonConfigurationInstance()方法即可。
  2. 根据第一条,当配置更改销毁Activity时,onRetainNonConfigurationInstance()保存了ViewModelStore,而ViewModel又通过ViewModelStore来存取,因此当Activity重建时,就能获取到之前的ViewModel

系统是如何保留和恢复NonConfigurationInstances

从上一个章节我们知道,ActivityonRetainNonConfigurationInstance()是由系统调用的,那么系统在什么时机调用的呢?

根据经验,ActivityThread负责Activity的调度和执行,那么我们就去ActivityThread中搜索下ActivityretainNonConfigurationInstances()方法。

提示:Android Framework源码一般会有如下的关系链路:

schedule(安排上) ————> handler(处理) ————> perform(执行) ————> on(在进行)

ActivityThreadperformDestroyActivity()方法

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因配置更改被销毁重建时,会调用ActivityThreadhandleRelaunchActivityInner()方法:

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);
}

从上面的代码可以看出:

  1. 首先执行handleDestroyActivity ,并且将 getNonConfigInstance 设为 true, 这样就可以将ActivityonRetainNonConfigurationInstance() 保留的数据,保存到 ActivityClientRecord 中;
  2. 然后执行 handleLaunchActivity(),重建Activity,并将数据恢复。

ActivityThreadperformLaunchActivity()

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;
}

从以上代码可以看出:

  1. 创建Activity的实例;
  2. attach 数据,将因配置更改销毁Activity留存到ActivityClientRecord中持有的 lastNonConfigurationInstances 赋值给新建的 Activity
  3. 之后就可以通调用ActivitygetLastNonConfigurationInstance() 检索到 onRetainNonConfigurationInstance() 方法保留的对象。

Activityattach()方法

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 通常会在下面三种情况下被销毁:

  1. 从当前界面永久离开:用户导航至其他界面或直接关闭 Activity (通过点击返回按钮或执行的操作调用了 finish 方法)。对应 Activity 实例被永久关闭;

  2. Activity 配置 (configuration) 被改变: 例如,旋转屏幕等操作,会使 Activity 需要立即重建;

  3. 应用在后台时,其进程被系统杀死:这种情况发生在设备剩余运行内存不足,系统又亟须释放一些内存的时候。当进程在后台被杀死后,用户又返回该应用时,Activity 也需要被重建。

在后两种情况中,我们通常都希望重建 ActivityViewModel 会帮我们处理第二种情况,因为在这种情况下 ViewModel 没有被销毁;而在第三种情况下, ViewModel 被销毁了。所以一旦出现了第三种情况,便需要在 ActivityonSaveInstanceState 相关回调中保存和恢复 ViewModel 中的数据。

ViewModel是如何在配置更改后继续留存数据的相关推荐

  1. gitlab部署、配置更改、备份及恢复

    1.gitlab部署 官网 gitlab.com Ubuntu14.04安装 1.Install and configure the necessary dependencies sudoapt-ge ...

  2. 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 ...

  3. 我的世界java平台缺少证书_tomcat配置https以及配置完成后提示服务器缺少中间证书(已解决)...

    tomcat配置https 准备工作 下载好证书文件,下载的时候可以选择为tomcat文件.我这下载下来是压缩包.解压后就是下图的样子. 以.key结尾的文件是证书的key 以.pem结尾的文件是证书 ...

  4. Win10主题更改后无法变更背景怎么解决

    在电脑使用中会有各种不同的主题,我们会不断的的选择自己喜欢的主题,那么遇到主题更改后无法变更背景怎么解决?为此系统屋为你提供Win10主题更改后无法变更背景解决方法,你可以通过操作来快速的解决自己的问 ...

  5. 此问题可能是由配置更改或安装另一个扩展导致的

    未能正确加载"Microsoft.VisualStudio.Editor.Implementation.EditorPackage, Microsoft.VisualStudio.Edito ...

  6. Flutter配置好后,在Android Studio中找不到设备,no devices

    Flutter配置好后,在Android Studio中找不到设备,no devices 完成Flutter的Android配置之后,连上设备,运行flutter doctor,发现已经识别了一个可用 ...

  7. php nginx exec失败,小白问题:用nginx配置php后nginx无法启动。

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 树莓派raspbian系统用nginx和php7.3-fpm搭建一个简单的web服务器,软件安装完成并启动成功,也能正常访问. 修改/etc/nginx/ ...

  8. 【GitLab】gitlab上配置webhook后,点击测试报错:Requests to the local network are not allowed...

    gitlab上配置webhook后,点击测试报错: Requests to the local network are not allowed 操作如下: 报错: 错误原因: gitlab 10.6 ...

  9. mitmproxy配置代理后 APP连接不到网络 XPosed安装

    本文仅供学习交流使用,如侵立删! mitmproxy配置代理后 APP连接不到网络 配置mitmproxy代理后,有些APP会出现连接不到网络的情况 原因:SSL证书问题 Android5.1系统版本 ...

最新文章

  1. jquery 设置css样式
  2. Python学习笔记之基本数据结构方法
  3. 道路游戏(洛谷 P1070)
  4. AtCoder Grand Contest 013D: Piling Up 题解
  5. 索引,表增删改统计,加锁查具体情况(推荐)
  6. 关于sendmail报错“did not issue MAIL/EXPN/VRFY/ETRN during connection to
  7. simple java mail
  8. 65. Valid Number
  9. Https环境下WS接口两次连续调用出错
  10. 我的作品-图书馆信息管理系统
  11. linux配置网卡自动获取的命令,linux 命令行下配置网卡自动获取 IP
  12. java url authority,Java如何解析url,包括自定义schema的url
  13. c语言修改字符串c2133,通过create_string_buffer、create_unicode_buffer让C语言具备修改字符串的能力...
  14. 华为关联公司哈勃投资晟芯网络 持股10%
  15. 疫情持续两年多职场妈妈更努力存钱,近六成中国内地受访者储蓄可维持生活一年以上...
  16. 第三阶段应用层——1.3 数码相册—英文和汉字的点阵显示
  17. 中国关系型社会的环境如何生存发展------总结程序员如何做人做事
  18. matlab 相位解旋绕,相位解缠绕方法-南京航空航天大学学报.PDF
  19. 考研高危人群!你是否还在危险的边缘试探?
  20. CCF论文列表(2022拟定)大更新!MICCAI空降B类!PRCV空降C类!ICLR继续陪跑...

热门文章

  1. python飞机大战碰撞检测_pygame制作飞机大战4——敌机出现、碰撞检测、增加声音、分数记录...
  2. 学平面UI设计选择哪个培训机构好
  3. 平面设计转UI设计难吗?
  4. CPU处理器检测工具
  5. 网页从输入url到呈现页面流程
  6. Debug签名时候数据正常正式签名的时候数据不正常,不显示,或者数据错乱问题
  7. genicam 相机java,机器视觉必知-GenICam相机通用接口标准
  8. 京东数科Java一面面经
  9. 乐普生物再递表背后:连年巨亏,暂未放弃A股,最多撑到年底?
  10. 计算机usb接口禁用,台式机usb接口禁用了怎么办