ViewModel 這些知識點你都知道嗎?

成都.JPG

前言

ViewModel 作為 Jetpack 中的明星組件,相信大家都對其有一定的了解。在 Google 的官方介紹中也詳細的羅列了 ViewModel 的優(yōu)點,如:

  1. 可以提供和管理UI界面數(shù)據(jù)。(將加載數(shù)據(jù)與數(shù)據(jù)恢復(fù)從 Activity or Fragment中解耦)
  2. 可感知生命周期的組件。
  3. 不會因配置改變而銷毀。
  4. 可以配合 LiveData 使用。
  5. 多個 Fragment 可以共享同一 ViewModel。
  6. 等等等....

你也可以通過下列兩個視頻,更為詳細的了解 ViewModel:

在本篇文章中,不會講解 ViewModel 的使用方式及使用 ViewModel 的原因,而是著重于講解 ViewModel 的原理。通過閱讀本篇文章你能了解到:

  • ViewModel 在 Activity 中的綁定過程。
  • ViewModel 在 Activity 中不會因配置改變而銷毀的原理。
  • ViewModel 在 Fragment 中的綁定過程。
  • ViewModel 在 Fragment 中不會因配置改變而銷毀的原理。
  • ViewMode 能在 Fragment 中共享的原理。

希望通過該篇文章,大家能對 ViewModel 有更深入的了解。

ViewModel 與 Activity 的綁定過程

一般情況下使用 ViewModel,我們一般會先聲明自己的 ViewModel,并在 Activity 中的 onCreate 方法中使用 ViewModelProviders 來創(chuàng)建 ViewModel。 如下代碼所示:

 MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);

在谷歌的最新代碼中,不推薦使用 ViweModelProviders(注意是有s的呦) ,而是直接使用 ViewModelProvider 的構(gòu)造函數(shù)來創(chuàng)建 ViewModelProvider 對象。

通過使用 ViewModelProviders 類的 of() 方法,我們會得到一個 ViewModelProvider 對象。如下代碼所示:

   public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return new ViewModelProvider(activity);
    }

ViewModelProvider 類需要我們傳遞 ViewModelStoreFactory 對象。其構(gòu)造函數(shù)聲明如下:

    //使用ViewModelStoreOwner對象構(gòu)造函數(shù)
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

    //使用ViewModelStoreOwner與Factory對象的構(gòu)造函數(shù)
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }

    //使用ViewModelStore與Factory對象的構(gòu)造函數(shù)
    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

在 ViewModelProvider 內(nèi)部,擁有三種類型構(gòu)造函數(shù):

  • (ViewModelStoreOwner owner):
    • 該構(gòu)造函數(shù)使用 owner 對象的 getViewModelStore() 方法來獲取 ViewModelStore 對象,如果傳入的 owner 對象也實現(xiàn)了 HasDefaultViewModelProviderFactory 接口時,那么會調(diào)用 getDefaultViewModelProviderFactory() 方法獲取 Factory。反之,使用內(nèi)部靜態(tài)的 NewInstanceFactory 對象來創(chuàng)建 Factory 對象。
  • (ViewModelStoreOwner owner, Factory factory):
    • 該構(gòu)造函數(shù)使用 owner 對象的 getViewModelStore() 方法來獲取 ViewModelStore 對象,使用傳遞的 Factory 對象
  • (ViewModelStore store, Factory factory)
    • 使用 ViewModelStoreFactory 對象的構(gòu)造函數(shù)

Factory 接口介紹

在 ViewModelProvider 中,Factory 主要用于創(chuàng)建 ViewModel,F(xiàn)actory 的聲明如下:

    public interface Factory {
        /**
         * 通過給定的Class對象創(chuàng)建ViewModel對象
         * <p>
         *
         * @param modelClass 所需ViewModel的Class對象
         * @param <T>        ViewModel的泛型參數(shù)
         * @return 新創(chuàng)建的ViewModel對象
         */
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

通過實現(xiàn) Factory 接口,我們可以實現(xiàn)自己想要的工廠以創(chuàng)建所需的 ViewModel。在 Android 中有多個類都實現(xiàn)了該接口(如 KeyedFactory, AndroidViewModelFactory),這里以默認的 NewInstanceFactory 為例:

    public static class NewInstanceFactory implements Factory {

        private static NewInstanceFactory sInstance;

        @NonNull
        static NewInstanceFactory getInstance() {
            if (sInstance == null) {
                sInstance = new NewInstanceFactory();
            }
            return sInstance;
        }

        @SuppressWarnings("ClassNewInstance")
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            try {
                //默認使用對應(yīng)ViewModel類無參的構(gòu)造函數(shù)創(chuàng)建實例對象
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
    }

默認情況下, NewInstanceFactory 會調(diào)用 ViewModel 的無參構(gòu)造函數(shù)創(chuàng)建實例對象,當然如果你需要在 ViewModel 中使用其他參數(shù),你也可以傳遞自定義的 Factory。

ViewModelStore 介紹

ViewModelStore 內(nèi)部維護了一個 HashMap,其 key 為 DEFAULT_KEY + ViewModel的Class對象底層類規(guī)范名稱,其 value 為對應(yīng) ViewModel 對象。每個 Activity 與 Fragment 都對應(yīng)著一個 ViewModelStore ,用于存儲所需的 ViewModel。ViewModelStore 類聲明如下所示:

DEFAULT_KEY 值為:"androidx.lifecycle.ViewModelProvider.DefaultKey"

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    /**
     * 當內(nèi)部的 ViewModel 不再使用時,清除所占的內(nèi)存
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            //下面調(diào)用ViewModel的clear方法
            vm.clear();
        }
        mMap.clear();
    }
}

Activity 中創(chuàng)建與獲取 ViewModel 流程

ViewModel 最終的創(chuàng)建與獲取,需要 ViewProvider 類調(diào)用 get(Class<T> modelClass)方法(該方法內(nèi)部通過 ViewModelStore 與 Factory 的配合,創(chuàng)建并保存了所需的 ViewModel 對象),具體代碼如下所示:

 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");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

該方法內(nèi)部會調(diào)用 get(String key, Class<T> modelClass) 方法:

 public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        //??根據(jù)key值從ViewModelStore中取對應(yīng)的ViewModel
        ViewModel viewModel = mViewModelStore.get(key);
        //??判斷所傳入的Class對象是否是ViewModel的Class類或其子類的對象,如果是,直接返回
        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        //??如果為null,根據(jù)傳入的Factory創(chuàng)建新的VideModel
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        //??將新的 ViewModel 存入ViewModelStore,并返回
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

在該方法中,會在 ViewModelStore 中根據(jù)傳入的 key 獲取并保存 ViewModel。其具體邏輯如下:

  • 根據(jù) key 值從 ViewModelStore 中取對應(yīng)的 ViewModel。
  • 判斷所傳入的 Class 對象是否是 ViewModel 的 Class 類或其子類的對象,如果是,直接返回。(當 Object.isInstance(class) 接受的參數(shù)為 null 時,該方法會返回 false
  • 如果獲取的 ViewModel 為 null,會根據(jù)傳入的 Factory 對象創(chuàng)建新的 VideModel,并將創(chuàng)建好的 ViewModel 放入 ViewModelStore中。

Activity 中創(chuàng)建與獲取 ViewModel 的整體流程如下所示:

Activity下ViewModel的創(chuàng)建過程.png

ViewModel 在 Activity 中不會因配置改變而銷毀的原理

我們都知道 ViewModel 不會因為 Activity 的配置發(fā)生改變而銷毀,ViewModel 作用域如下所示:

viewmodel-lifecycle.png

觀察上圖,我相信小伙伴們肯定有如下疑惑:

  • 當 Activity 因配置發(fā)生改變時,系統(tǒng)會重新創(chuàng)建一個新的 Activity 。那老的 Activity 中的 ViewModel 是如何傳遞給新的 Activity 的呢?
  • ViewModel 又是如何感知配置是否改變,進而判斷是否銷毀的呢?

要解決如上問題,我們需要了解 Android 中數(shù)據(jù)恢復(fù)的方式以及 Activity 生命周期中 ViewModel 實際處理流程。

數(shù)據(jù)恢復(fù)的常見方式

在 Android 系統(tǒng)中,需要數(shù)據(jù)恢復(fù)有如下兩種場景:

  • 場景1:資源相關(guān)的配置發(fā)生改變導(dǎo)致 Activity 被殺死并重新創(chuàng)建。
  • 場景2:資源內(nèi)存不足導(dǎo)致低優(yōu)先級的 Activity 被殺死。

針對上述場景,分別對應(yīng)三種不同的數(shù)據(jù)恢復(fù)方式。

對應(yīng)場景1,不考慮在清單文件中配置 android:configChanges 的特殊情況。

使用 onSaveInstanceState 與 onRestoreInstanceState

使用 onSaveInstanceState 與 onRestoreInstanceState 方法,能處理場景1與場景2的情況。當你的界面數(shù)據(jù)簡單且輕量時,例如原始數(shù)據(jù)類型或簡單對象(比如 String),則我們可以采用該方式。如果你需要恢復(fù)的數(shù)據(jù)較為復(fù)雜,那你應(yīng)該考慮使用 ViewModle + onSaveInstanceState() (為什么要配合使用,會在下文進行講解),因為使用 onSaveInstanceState() 會導(dǎo)致序列化或反序列化,而這,有一定的時間消耗。

onSaveInstanceState() 更為詳細的介紹以及使用,可參考官方文檔:

使用 Fragment 的 setRetainInstance

當配置發(fā)生改變時,F(xiàn)ragment 會隨著宿主 Activity 銷毀與重建,當我們調(diào)用 Fragment 中的 setRetainInstance(true) 方法時,系統(tǒng)允許 Fragment 繞開銷毀-重建的過程。使用該方法,將會發(fā)送信號給系統(tǒng),讓 Activity 重建時,保留 Fragment 的實例。需要注意的是:

  • 使用該方法后,不會調(diào)用 Fragment 的 onDestory() 方法,但仍然會調(diào)用 onDetach() 方法
  • 使用該方法后,不會調(diào)用 Fragment 的 onCreate(Bundle) 方法。因為 Fragment 沒有被重建。
  • 使用該方法后,F(xiàn)ragment 的 onAttach(Activity)onActivityCreated(Bundle) 方法仍然會被調(diào)用。

以下示例代碼展示了如何在配置發(fā)生改變時,保留 Fragment 實例,并進行數(shù)據(jù)的恢復(fù)。

public class MainActivity extends AppCompatActivity {

    private SaveFragment mSaveFragment;

    public static final String TAG_SAVE_FRAGMENT = "save_fragment";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FragmentManager fm = getSupportFragmentManager();
        mSaveFragment = (SaveFragment) fm.findFragmentByTag(TAG_SAVE_FRAGMENT);

        // fragment 不為空,是因為配置發(fā)生改變,F(xiàn)ragment 被重建
        if (mSaveFragment == null) {
            mSaveFragment = SaveFragment.newInstance();
            fm.beginTransaction().add(mSaveFragment, TAG_SAVE_FRAGMENT).commit();
        }

        //獲取保存的數(shù)據(jù)
        int saveData = mSaveFragment.getSaveData();
    }
}

Fragment :

public class SaveFragment extends Fragment {

    private int saveData;

    public static SaveFragment newInstance() {
        Bundle args = new Bundle();
        SaveFragment fragment = new SaveFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //保存當前Fragment實例
        setRetainInstance(true);
        saveData = 1010;//通過網(wǎng)絡(luò)請求或查詢數(shù)據(jù)庫,賦值需要保存的數(shù)據(jù)
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }

    public int getSaveData() {
        return saveData;
    }
}

關(guān)于 Fragment 的 setRetainInstance 更多用法與注意事項,可以參看文章
Handling Configuration Changes with Fragments

使用 onRetainNonConfigurationInstance 與 getLastNonConfigurationInstance

在 Activity 中提供了 onRetainNonConfigurationInstance 方法,用于處理配置發(fā)生改變時數(shù)據(jù)的保存。隨后在重新創(chuàng)建的 Activity 中調(diào)用 getLastNonConfigurationInstance 獲取上次保存的數(shù)據(jù)。我們不能直接重寫上述方法,如果想在 Activity 中自定義想要恢復(fù)的數(shù)據(jù),需要我們調(diào)用上述兩個方法的內(nèi)部方法:

  • onRetainCustomNonConfigurationInstance()
  • getLastCustomNonConfigurationInstance()

注意:onRetainNonConfigurationInstance 方法系統(tǒng)調(diào)用時機介于 onStop - onDestory 之間,getLastNonConfigurationInstance 方法可在 onCreateonStart 方法中調(diào)用。

以下代碼展示了,在 Actiity 中恢復(fù)自定義的數(shù)據(jù):

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String name = (String) getLastCustomNonConfigurationInstance();
        if (!TextUtils.isEmpty(name)) {
            //獲取恢復(fù)后的數(shù)據(jù),執(zhí)行相應(yīng)操作
        }
    }

//你可以可以在onStart中,獲取恢復(fù)的數(shù)據(jù)
//    @Override
//    protected void onStart() {
//        super.onStart();
//        String name = (String) getLastCustomNonConfigurationInstance();
//        if (!TextUtils.isEmpty(name)) {
//        }
//    }

    @Nullable
    @Override
    public Object onRetainCustomNonConfigurationInstance() {
        return "AndyJennifer";
    }  
}

在 Android 3.0 后,官方推薦使用 Fragment#setRetainInstance(true) 的方式進行數(shù)據(jù)的恢復(fù)。之所以推薦這種方式,個人猜測是為了降低 Activity 的冗余,將數(shù)據(jù)恢復(fù)的任務(wù)從 Activity 抽離出來,這更符合單一職責的設(shè)計模式。

幾種數(shù)據(jù)恢復(fù)方式的總結(jié)

通過了解數(shù)據(jù)恢復(fù)的幾種方式,我們能得到如下對比圖:

數(shù)據(jù)恢復(fù)對比.png

ViewModel 的恢復(fù)

ViewModel 在官方設(shè)計之初就傾向于在配置改變時進行數(shù)據(jù)的恢復(fù)??紤]到數(shù)據(jù)恢復(fù)時的效率,官方最終采用了 onRetainNonConfigurationInstance 的方式來恢復(fù) ViewModel 。

在 SDK 27 之前,官方一直采用 Fragment#setRetainInstance(true) 的方式恢復(fù)數(shù)據(jù)。導(dǎo)致官方修改了其內(nèi)部實現(xiàn)的原因,猜測是因為 Fragment 的坑,程序的擴展性等其他因素。

知道了 ViewModel 的恢復(fù)方式,那現(xiàn)在一起來解決我們之前的疑惑。當 Activity 因配置發(fā)生改變時,系統(tǒng)會重新創(chuàng)建一個新的 Activity 。那老的 Activity 中的 ViewModel 是如何傳遞給新的 Activity ?

在 Androidx 中的 Activity 的最新代碼中,官方重寫了 onRetainNonConfigurationInstance 方法,在該方法中保存了 ViewModelStore (ViweModelStore 中存儲了 ViewModel ),進而也保存了 ViewModel,具體代碼如下所示:

    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }

        if (viewModelStore == null && custom == null) {
            return null;
        }

        //將ViewModel存儲在 NonConfigurationInstances 對象中
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

當新的 Activity 重新創(chuàng)建,并調(diào)用 ViewModelProviders.of(this).get(xxxModel.class) 時,又會在 getViewModelStore() 方法中獲取老 Activity 保存的 ViewModelStore。那么也就拿到了 ViewModel。具體代碼如下所示:

    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            //??獲取保存的NonConfigurationInstances,
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                //??從該對象中獲取ViewModelStore
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

ViewModel 何時判斷是否被移除

ViewModel 最重要的特性就是不會在配置發(fā)生改變的時候被移除。其內(nèi)部實現(xiàn)也非常簡單,監(jiān)聽 Activity 聲明周期,在 onDestory 方法被調(diào)用時,判斷配置是否改變。如果沒有發(fā)送改變,則調(diào)用 Activity 中的 ViewModelStore 的 clear() 方法,清除所有的 ViewModel。具體代碼如下所示:

    public ComponentActivity() {
        Lifecycle lifecycle = getLifecycle();
        //省略更多....
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if (!isChangingConfigurations()) {
                       //??在配置沒發(fā)生改變且走到onDestory方法時,清除所有的ViewModel
                        getViewModelStore().clear();
                    }
                }
            }
        });
    }

ViewModel 在 Fragment 的綁定過程

在官方的最新代碼實現(xiàn)中,F(xiàn)ragment 中的 ViewModel 與其宿主 Activity 有著密切的聯(lián)系。要了解 ViewModel 與 Fragment 的綁定過程,我們需要先了解 FragmentManagerFragmentManagerViewModel 相關(guān)知識。

FragmentManager 介紹

每個 Fragment 及宿主 Activity (繼承自 FragmentActivity)都會在創(chuàng)建時,初始化一個 FragmentManager 對象,了解 Fragment 中的 ViewModel 與 Activity 的聯(lián)系的關(guān)鍵,就是理清這些不同階級的棧視圖。

下面給出一個簡要的關(guān)系圖:

FragmentManager棧對應(yīng)關(guān)系.png
  • 對于宿主 Activity , getSupportFragmentManager()獲取的是 FragmentActivity 的 FragmentManager 對象;
  • 對于 Fragment , getFragmentManager() 是獲取的父 Fragment (如果沒有,則是 FragmentActivity )的 FragmentManager 對象,而 getChildFragmentManager() 是獲取自身的 FragmentManager 對象。

FragmentManagerViewModel 介紹

每個 Fragment 創(chuàng)建時,都會創(chuàng)建一個 FragmentManagerViewModel 對象,在該對象中主要存儲其 子Fragment 的 ViewModelStore 與 FragmentManagerViewMoel。具體結(jié)構(gòu)如下所示:

FragmentManagerViewModel.png

在 FragmentManagerViewModel 中:

  • mViewModelStore 是類型為 <String, FragmentManagerViewModel> 的 HashMap
  • mChildNonConfigs 是類型為 <String, ViewModelStore> 的 HashMap

上述兩個 Map 對應(yīng)的 Key 值都為 Fragment 的唯一 UUID。該 UUID 會在 Fragment 對象創(chuàng)建時自動生成。也就是每個 Fragment 對應(yīng)唯一 UUID。

ViewModel 在 Fragment 綁定具體流程

ViewModel 與 Fragment 的綁定流程比較復(fù)雜,主要分為三個流程:

  • 第一步:在宿主 Activity 創(chuàng)建時,默認會在其 FramgentManager 中創(chuàng)建一個 FragmentManagerViewModel。同時將生成的 FragmentManagerViewModel 存儲在其本身的 ViewModelStore 中。同時使用自身的FragmentManager
  • 第二步:在 Fragment 創(chuàng)建時,從 宿主Activity父Fragment 中的 FramgentManager 中獲取對應(yīng)的 FragmentManagerViewModel,并使用自身的 ChildFragmentManagermNonConfig 變量進行保存。
  • 第三步:將 Fragment 中所創(chuàng)建的 ViewModel 與其自身的 ViewModelStore 關(guān)聯(lián) ,并自身的 ViewModelStore 存儲在 mNonConfig 所指向的 FragmentManaerViewModel 中的 mViewModelStores 中。

下面我將結(jié)合源碼對這三個流程進行詳細的介紹。

第一步流程

在宿主 Activity 創(chuàng)建時,默認會在其 FramgentManager 中創(chuàng)建一個 FragmentManagerViewModel。同時將生成的 FragmentManagerViewModel 存儲在其本身的 ViewModelStore 中。同時使用自身的FragmentManager

FragmentActivity 中的 onCreate 方法:

    protected void onCreate(@Nullable Bundle savedInstanceState){
        mFragments.attachHost(null /*parent*/);//??傳入null
        //省略更多...
    }

mFragments 是 FragmentController,內(nèi)部通過 FragmentHostCallback 間接控制 FragmentManager。

該方法最終會執(zhí)行 FragmentActivity 中 FragmentManager 的 attachController 方法:

 void attachController(@NonNull FragmentHostCallback<?> host,
            @NonNull FragmentContainer container, @Nullable final Fragment parent) {
        //省略更多...
        if (parent != null) {
            mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
        } else if (host instanceof ViewModelStoreOwner) {
            //??走這里
            ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
            mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
        } else {
            mNonConfig = new FragmentManagerViewModel(false);
        }
    }

因為傳入的 parent = null,且 Activity 默認實現(xiàn)了 ViewModelStoreOwner 接口,所以會獲取 Activity 中的 ViewModelStore,接著調(diào)用 FragmentManagerViewModel 的 getInstance() 方法:

    static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
        ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,
                FACTORY);
        return viewModelProvider.get(FragmentManagerViewModel.class);
    }

在該方法中,會創(chuàng)建 FragmentManagerViewModel,并將其添加到 Activity 中的 ViewModelStore 中。

整體流程如下所示:

第一步流程.png

第二步流程

在 Fragment 創(chuàng)建時,從 宿主Activity父Fragment 中的 FramgentManager 中獲取對應(yīng)的 FragmentManagerViewModel,并使用自身的 ChildFragmentManagermNonConfig 變量進行保存。

當 Fragment 與 Activity 關(guān)聯(lián)時,在其 performAttach() 方法中

    void performAttach() {
        //??又會調(diào)用attachController
        mChildFragmentManager.attachController(mHost, new FragmentContainer() {
            @Override
            @Nullable
            public View onFindViewById(int id) {
                if (mView == null) {
                    throw new IllegalStateException("Fragment " + this + " does not have a view");
                }
                return mView.findViewById(id);
            }

            @Override
            public boolean onHasView() {
                return (mView != null);
            }
        }, this);//??注意這里的this傳入的parent是當前Fragment
      //省略更多...
    }

該方法會調(diào)用 Fragment 中 ChildFragmentManager 中的 attachController 方法如下所:

 void attachController(@NonNull FragmentHostCallback<?> host,
            @NonNull FragmentContainer container, @Nullable final Fragment parent) {
        //省略更多...
        if (parent != null) {
            //??因為parent為this,故我們會獲取Activity的FragmentManager
            mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
        } else if (host instanceof ViewModelStoreOwner) {
            ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
            mNonConfig = FragmentManagerViewModel.getInstance(a);
        } else {
            mNonConfig = new FragmentManagerViewModel(false);
        }
    }

注意,當 Fragment 是 子Fragment 時,parent.fragmentManager 的值為父Fragment 的 FragmentManager,否則為 Activity 中的 FragmentManager。

繼續(xù)追蹤 FragmentManager 下的 getChildNonConfig 方法:

  private FragmentManagerViewModel getChildNonConfig(Fragment f){
        return mNonConfig.getChildNonConfig(f);
    }

mNonConfig 本身為 FragmentManagerViewModel,我們繼續(xù)跟蹤:

  FragmentManagerViewModel getChildNonConfig(Fragment f){
        FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
        if (childNonConfig == null) {
            //??創(chuàng)建Fragment的FragmentManagerViewModel
            childNonConfig = new FragmentManagerViewModel(mStateAutomaticallySaved);
            mChildNonConfigs.put(f.mWho, childNonConfig);
        }
        return childNonConfig;
    }

在該方法中,會從 Activity 中的 FragmentManagerViewModel 中的 mChildNonConfigs 中獲取 Fragment 的 FragmentManagerViewModel,如果有,直接返回。反之,存入mChildNonConfigs 中。

整體流程如下所示:

第二步流程.png

第三步流程

將 Fragment 中所創(chuàng)建的 ViewModel 與其自身的 ViewModelStore 關(guān)聯(lián) ,并將該 ViewModelStore 存儲在 mNonConfig 所指向的 FragmentManaerViewModel 中的 mViewModelStores 中。

在 Fragment 中,ViewModelStore 是通過其 FragmentManager 創(chuàng)建與獲取的。具體代碼如所示:

    public ViewModelStore getViewModelStore() {
        if (mFragmentManager == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
        return mFragmentManager.getViewModelStore(this);
    }

注意,當 Fragment 是 子Fragment 時,mFragmentManager 的值為 父Fragment 的 FragmentManager,否則為 Activity 中的 FragmentManager。

繼續(xù)追蹤 FragmentManager 下的 getChildNonConfig 方法:

  ViewModelStore getViewModelStore(@NonNull Fragment f) {
        return mNonConfig.getViewModelStore(f);
    }

mNonConfig 本身為 FragmentManagerViewModel,最終會走 getViewModelStore 方法。

  ViewModelStore getViewModelStore(@NonNull Fragment f) {
        ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
        if (viewModelStore == null) {
            viewModelStore = new ViewModelStore();
            //將創(chuàng)建好的ViewStore,放入FragmentManagerViewModel中
            mViewModelStores.put(f.mWho, viewModelStore);
        }
        return viewModelStore;
    }

在該方法中最終會將 Fragment 的 ViewModelStore 存入 FragmentManagerViewModel 中的 mViewModelStores 集合中。

那么 Fragment 的創(chuàng)建并獲取 ViewModel 的流程如下所示:

第三步流程.png

ViewModel 在 Fragment 中不會因配置改變而銷毀的原理

ViewModel 在 Fragment 中不會因配置改變而銷毀的原因其實是因為其聲明的 ViewModel 是存儲在 FragmentManagerViewModel 中的,而 FragmentManagerViewModel 是存儲在宿主 Activity 中的 ViewModelStore 中,又因 Activity 中 ViewModelStore不會因配置改變而銷毀,故 Fragment 中 ViewModel 也不會因配置改變而銷毀。

當然在 Google 的代碼實現(xiàn)中,也能很好的處理 Fragment 嵌套的情況。在下述例子中展示了 Fragment 嵌套下 ViewModel 存儲的情況。

Activity與Fragment嵌套.png

在上圖中,我們在 Activity 中 分別添加了 Fragment A、B、C。并在 Fragment C 中有嵌套了 Fragment D、E、F。

結(jié)合本篇文章所講解的知識,我們能得到如下結(jié)構(gòu):

嵌套下實際結(jié)構(gòu).jpg

從上圖中,我們可以看出,當存在嵌套 Fragment 的情況下,ViewModel 總是以線性的結(jié)構(gòu)進行存儲。在這種結(jié)構(gòu)下,就能讓宿主 Activity 良好的統(tǒng)一管理與所有的 ViewModel。

ViewModel 能在 Fragment 中共享的原理

ViewModel 的另一大特性就是能在 Fragment 中共享數(shù)據(jù)。還是以上圖例:

假如我們想 Fragment D 獲取 Fragment A 中的數(shù)據(jù),那么我們只有在 Activity 中的 ViewModelStore 下添加 ViewModel。只有這樣,我們才能在不同 Fragment 中獲取相同的數(shù)據(jù)。這也是為什么在 Fragment 中使用共享的 ViewModel 時,我們要在調(diào)用ViewModelProvider.of() 創(chuàng)建 ViewModel 時需要傳入 getActivity() 的原因。

具體例子如下所示:

    public class SharedViewModel extends ViewModel {
        private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

        public void select(Item item) {
            selected.setValue(item);
        }

        public LiveData<Item> getSelected() {
            return selected;
        }
    }

    public class FragmentA extends Fragment {
        private SharedViewModel model;
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //??傳入的是宿主Activity
            model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
            itemSelector.setOnClickListener(item -> {
                model.select(item);
            });
        }
    }

    public class FragmentD extends Fragment {
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
             //??傳入的是宿主Activity
            SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
            model.getSelected().observe(this, { item ->
               // Update the UI.
            });
        }
    }

最后

站在巨人的肩膀上,才能看的更遠~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容