最近一直在用Google出那套android-architecture框架,感受挺好用的,尤爲喜歡Room。而後在用ViewModel的時候發現一個頗有意思的現象,ViewModel不會隨着onDestory的銷燬而重建,搜了好多回答,都說到了Fragment.setRetainInstance(boolean},但本質緣由說的都雲裏霧裏的(由於如今版本Google已經徹底移除了HolderFragment和靜態變量的實現)。下面帶你們刨根問底的具體弄清楚這究竟是爲何。java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewModelProvider provider = ViewModelProviders.of(this);
mViewModel = provider.get(SimpleViewModel.class);
}
複製代碼
利用上面的代碼能夠很是簡單的建立你須要的ViewModel,好比這裏的SimpleViewModel。 咱們判斷一個Java對象是否有變化咱們能夠簡單的經過hashCode來判斷。固然,若是目標對象複寫了hashCode,咱們能夠經過System.identityHashCode來判斷。android
object.hashCode();
System.identityHashCode(object);
複製代碼
因此,咱們把代碼作簡單的改造下git
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "activity: " + this.hashCode());
ViewModelProvider provider = ViewModelProviders.of(this);
Log.d(TAG, "provider: " + provider.hashCode());
mViewModel = provider.get(SimpleViewModel.class);
Log.d(TAG, "mViewModel: " + mViewModel.hashCode());
}
複製代碼
利用上面代碼咱們能夠很輕鬆的觀察這些實例是否發生了變化。上面代碼寫好了,旋轉一下屏幕,實際觀察下旋轉先後打印的日誌(注意不要配置旋轉時候不重建Activity)。很天然的,咱們發現Activity重建了,並且provider也重建了,可是很是神奇的是mViewModel竟然沒有重建。 添加onDestroy的日誌發現生命週期徹底正常。之前我寫過一篇關於Activity的Activity源碼分析裏面詳細說明了Activity在哪裏建立的,以及爲何onDestroy後Activity實例爲何能被回收。這裏順着前面的思路,咱們猜下爲何mViewModel沒有被從新建立? mViewModel確定被比前一個被銷燬的Activity生命週期更長的對象持有了。github
Activity有個能夠複寫的方法叫:onRetainNonConfigurationInstancebash
/** * Called by the system, as part of destroying an * activity due to a configuration change, when it is known that a new * instance will immediately be created for the new configuration. You * can return any object you like here, including the activity instance * itself, which can later be retrieved by calling * {@link #getLastNonConfigurationInstance()} in the new activity * instance. * * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB} * or later, consider instead using a {@link Fragment} with * {@link Fragment#setRetainInstance(boolean) * Fragment.setRetainInstance(boolean}.</em> * * <p>This function is called purely as an optimization, and you must * not rely on it being called. When it is called, a number of guarantees * will be made to help optimize configuration switching: * <ul> * <li> The function will be called between {@link #onStop} and * {@link #onDestroy}. * <li> A new instance of the activity will <em>always</em> be immediately * created after this one's {@link #onDestroy()} is called. In particular, * <em>no</em> messages will be dispatched during this time (when the returned * object does not have an activity to be associated with). * <li> The object you return here will <em>always</em> be available from * the {@link #getLastNonConfigurationInstance()} method of the following * activity instance as described there. * </ul> * * <p>These guarantees are designed so that an activity can use this API * to propagate extensive state from the old to new activity instance, from * loaded bitmaps, to network connections, to evenly actively running * threads. Note that you should <em>not</em> propagate any data that * may change based on the configuration, including any data loaded from * resources such as strings, layouts, or drawables. * * <p>The guarantee of no message handling during the switch to the next * activity simplifies use with active objects. For example if your retained * state is an {@link android.os.AsyncTask} you are guaranteed that its * call back functions (like {@link android.os.AsyncTask#onPostExecute}) will * not be called from the call here until you execute the next instance's * {@link #onCreate(Bundle)}. (Note however that there is of course no such * guarantee for {@link android.os.AsyncTask#doInBackground} since that is * running in a separate thread.) * * <p><strong>Note:</strong> For most cases you should use the {@link Fragment} API * {@link Fragment#setRetainInstance(boolean)} instead; this is also * available on older platforms through the Android support libraries. * * @return any Object holding the desired state to propagate to the * next activity instance */
public Object onRetainNonConfigurationInstance() {
return null;
}
複製代碼
註釋烏拉烏拉說了特別多,簡單說就是這個方法若是返回了一個對象,那麼這個對象會被保存起來。同時還說了一些兼容問題,Fragment須要本身調用setRetainInstance(boolean)等等一大堆。那光說保存,那怎麼拿到這個對象呢?是下面這個方法。app
/** * Retrieve the non-configuration instance data that was previously * returned by {@link #onRetainNonConfigurationInstance()}. This will * be available from the initial {@link #onCreate} and * {@link #onStart} calls to the new instance, allowing you to extract * any useful dynamic state from the previous instance. * * <p>Note that the data you retrieve here should <em>only</em> be used * as an optimization for handling configuration changes. You should always * be able to handle getting a null pointer back, and an activity must * still be able to restore itself to its previous state (through the * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this * function returns null. * * <p><strong>Note:</strong> For most cases you should use the {@link Fragment} API * {@link Fragment#setRetainInstance(boolean)} instead; this is also * available on older platforms through the Android support libraries. * * @return the object previously returned by {@link #onRetainNonConfigurationInstance()} */
@Nullable
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
複製代碼
嗚嗚啦啦一大堆。簡單說就是能夠經過這個方法拿到以前保存的實例。同時注意這個實例只能在onCreate onStart之間拿,onResume或者這以後就拿不到了。你們能夠寫一個最簡單的Activity複寫上面的方法嘗試下效果。固然若是你繼承的不是Activity而是AppCompatActivity,你會發現這個方法不能複寫,由於這個方法被FragmentActivity標記爲final了禁止你複寫。框架
/** * Retain all appropriate fragment state. You can NOT * override this yourself! Use {@link #onRetainCustomNonConfigurationInstance()} * if you want to retain your own state. */
@Override
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
if (fragments == null && mViewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = mViewModelStore;
nci.fragments = fragments;
return nci;
}
複製代碼
這裏Google爲了保證ViewModel相關功能的正常禁止你手動複寫這個方法,由於你本身複寫後返回新的對象會破壞ViewModel的保存實例的功能,但Google也給了咱們曲線救國的方法,複寫onRetainCustomNonConfigurationInstance,固然與之對應的,拿實例時候就須要調用getLastCustomNonConfigurationInstance了。但願你們都能本身嘗試下,看看這樣修改後,旋轉屏幕,是否能在onCreate時候拿到以前保存的對象,同時仍是同一個實例。 而後這是保存,那ViewModel實際怎麼恢復的呢?在FragmentActivity類的onCreate方法裏。ide
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
FragmentManagerNonConfig fragments;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);
super.onCreate(savedInstanceState);
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
mViewModelStore = nc.viewModelStore;
}
......
}
複製代碼
這裏Google經過getLastNonConfigurationInstance拿到以前保存的ViewModelStore,間接的把ViewModel的實例恢復了,並且沒有重建。另外注意到裏面的FragmentManagerNonConfig fragments;了嗎?這個就是用來保持Fragment不重建的。 看到這裏若是不關心背後更詳細的實現,其實就差很少了,這已經很準確(比你搜到的其餘單說Fragment.setRetainInstance(boolean}要準確的多)。oop
上面其實已經從API層面上解釋的很清楚了,爲啥ViewModel能恢復。但仍是不知足,看過以前Activity源碼分析的都知道Activity的建立和銷燬,那這裏就有個疑問了,ViewModel沒有被銷燬是由於ViewModelStore,ViewModelStore沒有被銷燬是由於onRetainNonConfigurationInstance,那ViewModelStore從onRetainNonConfigurationInstance返回後,跑哪裏去了,他到底被什麼東西持有了致使下次Activity重建的時候還能從新還給Activity? 怎麼找呢?咱們先看下onRetainNonConfigurationInstance回調放生時候主線程的方法堆棧。源碼分析
at com.aesean.SimpleActivity.onRetainCustomNonConfigurationInstance(LoginActivity.java:191)
at androidx.fragment.app.FragmentActivity.onRetainNonConfigurationInstance(FragmentActivity.java:569)
at android.app.Activity.retainNonConfigurationInstances(Activity.java:2423)
at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4469)
at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4515)
at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:4799)
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4732)
at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:69)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6718)
at java.lang.reflect.Method.invoke(Method.java:-1)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
複製代碼
這裏咱們很清楚的看到這個方法回調是ActivityThread.performDestroyActivity的時候被調用的,咱們看下ActivityThread(這個類源碼在AndroidSdk裏有,雙擊Shift輸入ActivityThread能夠找到這個類,若是找不到能夠先打開Activity的源碼,而後直接Ctrl+F查找ActivityThread,找到後直接Ctrl+B打開ActivityThread)
/** Core implementation of activity destroy call. */
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = mActivities.get(token);
...
if (getNonConfigInstance) {
try {
r.lastNonConfigurationInstances
= r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
...
mInstrumentation.callActivityOnDestroy(r.activity);
...
...
mActivities.remove(token);
...
return r;
}
複製代碼
我已經把主要代碼貼出來了,能夠清楚的看到在Activity的onDestroy被調用以前回調了retainNonConfigurationInstances,這時候把咱們返回的實例對象賦值給了r.lastNonConfigurationInstances,r是ActivityClientRecord,他直接持有Activity等Activity相關的幾乎全部信息。看到這裏咱們知道r間接持有了ViewModel,因此只要r能在下次新Activity建立後回傳給新Activity,那麼咱們在onCreate裏就能夠拿到同一個ViewModel實例了。看前面的方法堆棧, -> performDestroyActivity -> handleDestroyActivity -> handleRelaunchActivityInner -> handleRelaunchActivity performDestroyActivity執行後會向下一層層返回。咱們詳細看下handleRelaunchActivityInner
private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents, PendingTransactionActions pendingActions, boolean startsNotResumed, Configuration overrideConfig, String reason) {
...
handleDestroyActivity(r.token, false, configChanges, true, reason);
...
handleLaunchActivity(r, pendingActions, customIntent);
...
複製代碼
這裏handleRelaunchActivityInner把r.token傳給了handleDestroyActivity又繼續傳給了performDestroyActivity
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = mActivities.get(token);
...
}
複製代碼
ActivityClientRecord r = mActivities.get(token); 這一行拿到的r其實和handleRelaunchActivityInner裏的r是同一個。因此handleLaunchActivity這裏的r並非新建的,而是剛剛保存了咱們的lastNonConfigurationInstances實例的r,並且這個r又傳了新建的Activity。
@Override
public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
...
final Activity a = performLaunchActivity(r, customIntent);
...
}
複製代碼
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
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);
if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
...
}
複製代碼
這裏把r.lastNonConfigurationInstances傳給了activity的attach方法,同時r清除了lastNonConfigurationInstances的引用。
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) {
...
mLastNonConfigurationInstances = lastNonConfigurationInstances;
...
}
複製代碼
這裏mLastNonConfigurationInstances實際拿到了lastNonConfigurationInstances,此時只有新Activity經過mLastNonConfigurationInstances持有了lastNonConfigurationInstances。那前面爲何說只能在onCreate和onStart拿到ViewModelStore呢?
final void performResume(boolean followedByPause, String reason) {
...
mLastNonConfigurationInstances = null;
...
}
複製代碼
Activity的performResume裏把mLastNonConfigurationInstances重置爲了null。
好了到這裏算是完全從源碼層面一層層刨根問底,完全把這個事情講清楚了。簡單總結下,之因此旋轉屏幕ViewModel沒有重建是由於屏幕旋轉的時候發生了Activity的RELAUNCH,在RELAUNCH的時候ActivityClientRecord會被複用,同時ViewModelStore會被保存在ActivityClientRecord裏,當新Activity被LAUNCH的時候複用的ActivityClientRecord把持有的ViewModelStore傳給了新Activity,而後FragmentActivity拿到了以前保存的ViewModelStore實例,而後Activity重建完成,但ViewModel並無重建。再回到文章開頭,
另外,爲何你搜到的不少文章都告訴你ViewModel實例不重建是由於Fragment.setRetainInstance(boolean}呢?主要是由於早期ViewModel實現仍是經過HolderFragment和靜態變量實現的,但如今已經徹底移除了HolderFragment。很顯然如今的實現要比HolderFragment好的多,如今的版本徹底沒有使用任何靜態變量,利用了Api1就已經出現的onRetainCustomNonConfigurationInstance完美解決實例持有的問題。 若是還有什麼疑問,歡迎評論留言。