想必有過必定開發經驗的同窗對 ViewModel 都不會陌生,它是 Google 推出的 MVVM 架構模式的一部分。這裏它的基礎使用咱們就不介紹了,畢竟這種類型的文章也遍地都是。今天咱們着重來探討一下它的生命週期。java
原由是這樣的,昨天在修復程序中的 Bug 的時候遇到了一個異常,是從 ViewModel 中獲取存儲的數據的時候報了空指針。我啓用了開發者模式的 「不保留活動」 以後很容易地重現了這個異常。出現錯誤的緣由也很簡單,相關的代碼以下:git
private ReceiptViewerViewModel viewModel;
@Override
protected void doCreateView(Bundle savedInstanceState) {
viewModel = ViewModelProviders.of(this).get(ReceiptViewerViewModel.class); // 1
handleIntent(savedInstanceState);
// ...
}
private void handleIntent(Bundle savedInstanceState) {
LoadingStatus loadingStatus;
if (savedInstanceState == null) {
loadingStatus = (LoadingStatus) getIntent().getSerializableExtra(Router.RECEIPT_VIEWER_LOADING_STATUS);
}
viewModel.setLoadingStatus(loadingStatus);
}
複製代碼
在方法 doCreateView()
中我獲取了 viewModel
實例,而後在 handleIntent()
方法中從 Intent
中取出傳入的參數。固然,還要使用 viewModel
的 getter
方法從其中取出 loadingStatus
並使用。在使用的時候拋了空指針。github
顯然,通常狀況下是不會出現問題的,可是若是 Activity 在後臺被銷燬了,那麼再重建的時候就會出現空指針異常。緩存
解決方法也比較簡單,在 onSaveInstanceState()
方法中將數據緩存起來便可,即:架構
private void handleIntent(Bundle savedInstanceState) {
LoadingStatus loadingStatus;
if (savedInstanceState == null) {
loadingStatus = (LoadingStatus) getIntent().getSerializableExtra(Router.RECEIPT_VIEWER_LOADING_STATUS);
} else {
loadingStatus = (LoadingStatus) savedInstanceState.get(Router.RECEIPT_VIEWER_LOADING_STATUS);
}
viewModel.setLoadingStatus(loadingStatus);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(Router.RECEIPT_VIEWER_LOADING_STATUS, viewModel.getLoadingStatus());
}
複製代碼
如今的問題是 ViewModel 的生命週期問題,有人說在 doCreateView()
方法的 1 處獲得的不是以前的 ViewModel 嗎,數據不是以前已經設置過了嗎?因此,這牽扯 ViewModel 是在何時被銷燬和重建的問題。框架
有的人但願使用 ViewModel 緩存 Activity 的信息,而後在 doCreateView()
方法的 1 處獲得以前的 ViewModel 實例,這樣 ViewModel 的數據就是 Activity 銷燬以前的數據,這可行嗎?咱們從源碼角度來看下這個問題。ide
首先,每次獲取 viewmodel
實例的時候都會調用下面的方法來獲取 ViewModel 實例。從下面的 get()
方法中能夠看出,實例化過的 ViewModel 是從 mViewModelStore
中獲取的。若是由 ViewModelStores.of(activity)
方法獲得的 mViewModelStore
不是同一個,那麼獲得的 ViewModel 也不是同一個。this
下面方法中的 get()
方法中後續的邏輯是若是以前沒有緩存過 ViewModel,那麼就構建一個新的實例並將其放進 mViewModelStore
中。這部分代碼邏輯比較簡單,咱們不繼續分析了。spa
// ViewModelProviders#of()
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
ViewModelProvider.AndroidViewModelFactory factory =
ViewModelProvider.AndroidViewModelFactory.getInstance(activity);
return new ViewModelProvider(ViewModelStores.of(activity), factory); // 1
}
// ViewModelProvider#get()
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
return (T) viewModel;
}
viewModel = mFactory.create(modelClass);
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
複製代碼
咱們回到上述 of()
方法的 1 處,來看下 ViewModelStores.of()
方法,其定義以下:.net
// ViewModelStores#of()
public static ViewModelStore of(@NonNull FragmentActivity activity) {
if (activity instanceof ViewModelStoreOwner) {
return ((ViewModelStoreOwner) activity).getViewModelStore();
}
return holderFragmentFor(activity).getViewModelStore();
}
// HolderFragment#holderFragmentFor()
public static HolderFragment holderFragmentFor(FragmentActivity activity) {
return sHolderFragmentManager.holderFragmentFor(activity);
}
複製代碼
這裏會從 holderFragmentFor()
方法中獲取一個 HolderFragment
實例,它是一個 Fragment 的實現類。而後從該實例中獲取 ViewModelStore
的實例。因此,ViewModel 對生命週期的管理與 Glide 和 RxPermission 等框架的處理方式一致,就是使用一個空的 Fragment 來進行生命週期管理。
對於 HolderFragment
,其定義以下。從下面的代碼咱們能夠看出,上述用到的 ViewModelStore 實例就是 HolderFragment
的一個局部變量。因此,ViewModel 使用空的 Fragment 管理生命週期實錘了。
public class HolderFragment extends Fragment implements ViewModelStoreOwner {
private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();
private ViewModelStore mViewModelStore = new ViewModelStore();
public HolderFragment() {
setRetainInstance(true);
}
// ...
}
複製代碼
此外,咱們注意到上面的 HolderFragment 的構造方法中還調用了 setRetainInstance(true)
這一行代碼。咱們進入該方法看它的註釋:
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
就是說,當 Activity 被重建的時候該 Fragment 會被保留,而後傳遞給新建立的 Activity. 可是,這隻適用於不處於後臺的 Fragment. 因此,若是 Activity 處於後臺的時候,Fragment 不會保留,那麼它獲得的 ViewModelStore
實例就不一樣了。
因此,總結下來,準確地將:當 Activity 處於前臺的時候被銷燬了,那麼獲得的 ViewModel 是以前實例過的 ViewModel;若是 Activity 處於後臺時被銷燬了,那麼獲得的 ViewModel 不是同一個。舉例說,若是 Activity 由於配置發生變化而被重建了,那麼當重建的時候,ViewModel 是以前的實例;若是由於長期處於後臺而被銷燬了,那麼重建的時候,ViewModel 就不是以前的實例了。
回到以前的 holderFragmentFor()
方法,咱們看下這裏具體作了什麼,其定義以下。
// HolderFragmentManager#holderFragmentFor()
HolderFragment holderFragmentFor(FragmentActivity activity) {
// 使用 FragmentManager 獲取 HolderFragment
FragmentManager fm = activity.getSupportFragmentManager();
HolderFragment holder = findHolderFragment(fm);
if (holder != null) {
return holder;
}
// 從哈希表中獲取 HolderFragment
holder = mNotCommittedActivityHolders.get(activity);
if (holder != null) {
return holder;
}
if (!mActivityCallbacksIsAdded) {
mActivityCallbacksIsAdded = true;
activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
}
holder = createHolderFragment(fm);
// 將新的實例放進哈希表中
mNotCommittedActivityHolders.put(activity, holder);
return holder;
}
複製代碼
首先,嘗試使用 FragmentManager
來獲取 HolderFragment
,若是獲取不到就從 mNotCommittedActivityHolders
中進行獲取。這裏的 mNotCommittedActivityHolders
是一個哈希表,每次實例化的新的 HolderFragment 會被添加到哈希表中。
另外,上面的方法中還使用了 ActivityLifecycleCallbacks 對 Activity 的生命週期進行監聽。其定義以下,
private ActivityLifecycleCallbacks mActivityCallbacks =
new EmptyActivityLifecycleCallbacks() {
@Override
public void onActivityDestroyed(Activity activity) {
HolderFragment fragment = mNotCommittedActivityHolders.remove(activity);
}
};
複製代碼
當 Activity 被銷燬的時候會從哈希表中移除映射關係。因此,每次 Activity 被銷燬的時候哈希表中的映射關係都不存在了。而之因此 ViewModel 可以實如今 Activity 配置發生變化的時候獲取以前的 ViewModel 是經過上面的 setRetainInstance(true)
和 findHolderFragment(fm)
來實現的。
以上就是 ViewModel 的生命週期的總結。咱們只是經過對主流程的分析研究了它的生命週期的流程,實際上內部還有許多小細節,邏輯也比較簡單,咱們就不一一說明了。
其實,從 Google 的官方文檔中,咱們也可以獲得上面的總結,
這裏使用了 Activity rotated
,也就是 Activity 處於前臺的時候配置發生變化的狀況,而不是處於後臺,不知道你以前有沒有注意這一點呢?
以上。
(若有疑問,能夠在評論中交流)
若是你喜歡這篇文章,請點贊哦!你也能夠在如下平臺關注我哦:
全部的文章維護在:Github, Android-notes