淺談 ViewModel 的生命週期控制

一、從一個 Bug 提及

想必有過必定開發經驗的同窗對 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 中取出傳入的參數。固然,還要使用 viewModelgetter 方法從其中取出 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 的生命週期

有的人但願使用 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 的官方文檔中,咱們也可以獲得上面的總結,

viewmodel-lifecycle

這裏使用了 Activity rotated,也就是 Activity 處於前臺的時候配置發生變化的狀況,而不是處於後臺,不知道你以前有沒有注意這一點呢?

以上。

(若有疑問,能夠在評論中交流)


若是你喜歡這篇文章,請點贊哦!你也能夠在如下平臺關注我哦:

全部的文章維護在:Github, Android-notes

相關文章
相關標籤/搜索