赞
踩
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) {
// 3
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull ViewModelProvider.Factory factory) {
this.mFactory = factory;
this.mViewModelStore = store;
}
ViewModelStoreOwner
源码
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore 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() { // 1 Object activity = onRetainNonConfigurationInstance(); HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig(); ...... ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig(); // 2 if (activity == null && children == null && fragments == null && loaders == null && mVoiceInteractor == null) { return null; } // 3 NonConfigurationInstances 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; } }
总结一下:
onRetainNonConfigurationInstance()
方法即可。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"); } // 1 if (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) {
...
// 1
handleDestroyActivity(r.token, false, configChanges, true, reason);
...
// 2
handleLaunchActivity(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); // 1 Activity 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])); // 2 appContext.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
的大致调用流程。
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 Screen
backstack = savedInstanceState.getParcelableArrayList<Parcelable>(
"backstack"
) as ArrayList<BackstackFrame>
}
}
Activity 通常会在下面三种情况下被销毁:
从当前界面永久离开:用户导航至其他界面或直接关闭 Activity
(通过点击返回按钮或执行的操作调用了 finish 方法)。对应 Activity
实例被永久关闭;
Activity
配置 (configuration) 被改变: 例如,旋转屏幕等操作,会使 Activity
需要立即重建;
应用在后台时,其进程被系统杀死:这种情况发生在设备剩余运行内存不足,系统又亟须释放一些内存的时候。当进程在后台被杀死后,用户又返回该应用时,Activity
也需要被重建。
在后两种情况中,我们通常都希望重建 Activity
。ViewModel
会帮我们处理第二种情况,因为在这种情况下 ViewModel
没有被销毁;而在第三种情况下, ViewModel
被销毁了。所以一旦出现了第三种情况,便需要在 Activity
的 onSaveInstanceState
相关回调中保存和恢复 ViewModel
中的数据。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。