Android mvvm之ViewModel篇

目錄

  • 1 什么是 ViewModel
    • 1.1 先考慮兩個場景
    • 1.2 缺點
    • 1.3 特別說明
    • 1.4 ViewModel 解決的問題
  • 2 ViewModel 實現(xiàn)原理
    • 2.1 ViewModel 類
    • 2.2 ViewModel 的構(gòu)造過程
  • 3 ViewModel 與配置無關(guān)的原理(與宿主 Controller 俱生俱滅)
    • 3.1 ViewModelStore 樹
    • 3.2 系統(tǒng)級的配置無關(guān)支持
  • 4 FragmentActivity 中的 ViewModel 生命周期
  • 5 多 Controller 共享 ViewModel
  • 6 關(guān)于工廠模式的一點思考




1 什么是 ViewModel

1.1 先考慮兩個場景

  • 場景一:我們開發(fā)的 APP 可以轉(zhuǎn)屏,轉(zhuǎn)屏后將觸發(fā) Controller(Activity or Fragment) 的重建,為了維護(hù)轉(zhuǎn)屏前后數(shù)據(jù)的一致性,我們要么將需要維護(hù)的數(shù)據(jù)以 Bundle 的形式在 onSaveInstance 中保存,必要的時候需要對復(fù)合數(shù)據(jù)實現(xiàn)繁瑣的 Parceable 接口,如果數(shù)據(jù)量太大則我們必須將數(shù)據(jù)持久化,在轉(zhuǎn)屏后重新拉取數(shù)據(jù)(from database or networks);
  • 場景二:我們的 Activity 中同時維護(hù)了多個 Fragment,每個 Fragment 需要共享一些數(shù)據(jù),傳統(tǒng)的做法是由宿主 Activity 持有共享數(shù)據(jù),并暴露數(shù)據(jù)獲取接口給各個寄生 Fragment。

1.2 缺點

隨著業(yè)務(wù)規(guī)模的擴大,以上的兩種場景在傳統(tǒng)實現(xiàn)方法中顯得越來越繁瑣且不易維護(hù),且數(shù)據(jù)模塊不易獨立進(jìn)行測試。

1.3 特別說明

關(guān)于場景一,同樣的場景還適用于各種配置相關(guān)的信息發(fā)生變化的情況,比如鍵盤、系統(tǒng)字體、語言區(qū)域等,它們的共同作用是都會導(dǎo)致當(dāng)前 Controller 的重建。

1.4 ViewModel 解決的問題

ViewModel 是 android 新的 mvvm 框架的一部分,它的出現(xiàn)就是為了解決以上兩個場景中數(shù)據(jù)與 Controller 耦合過度的問題。其 基本原理 是:維護(hù)一個與配置無關(guān)的對象,該對象可存儲 Controller 中需要的任何數(shù)據(jù),其生命周期與宿主 Controller 的生命周期保持一致,不因 Controller 的重建而失效(注意:Controller 的重建仍然在 Controller 生命周期內(nèi),并不會產(chǎn)生一個新的生命周期,即 Controller 的 onDestroy 并不會調(diào)用)

這意味著無論是轉(zhuǎn)屏還是系統(tǒng)字體變化等因配置變化產(chǎn)生的 Controller 重建都不會回收 ViewModel 中維護(hù)的數(shù)據(jù),重建的 Controller 仍然可以從同一個 ViewModel 中通過獲取數(shù)據(jù)恢復(fù)狀態(tài)。




2 ViewModel 實現(xiàn)原理

2.1 ViewModel 類

如果大家去看一下 ViewModel 類的實現(xiàn),會發(fā)現(xiàn)雖然它是一個 abstract 類,但是沒有暴露任何外部可訪問的方法,其預(yù)留的方法都是 package 訪問權(quán)限的,
其預(yù)留了一些數(shù)據(jù)清理工作的功能,推測可能是系統(tǒng)保留用作以后擴展,因為與我們對 ViewModel 原理的理解沒有什么關(guān)聯(lián),我們暫且略過。

2.2 ViewModel 的構(gòu)造過程

我們用一個結(jié)構(gòu)圖來剖析 ViewModel 的構(gòu)造過程:

how_to_get_viewmodel.png

如圖所示:

  • 所有已經(jīng)實例化的 ViewModel 都緩存在一個叫做 ViewModelStore 的封裝對象中,其實質(zhì)是一個 HashMap;
  • ViewModelStore 與具體的 Controller 綁定,并與宿主 Controller 俱生俱滅,所以這就解釋了為何 ViewModel 與宿主 Controller 的生命周期是一樣長了,因為緩存它的 ViewModelStore 與宿主 Controller 壽命相等;
  • 獲取 ViewModel 實例的過程委托給了一個叫做 ViewModelProvider 的工具類,它包含一個創(chuàng)建 ViewModel 的工廠類 Factory 和一個對 ViewModelStore 的引用;
  • 總的構(gòu)造過程為:先從 ViewModelStore 中獲取緩存的 ViewModel,若沒有緩存過則用 Facotry 實例化一個新的 ViewModel 并緩存,具體的過程分為 4 步,具體可參考圖示。

本小節(jié)剩下部分分析源碼,對于只關(guān)心原理的同學(xué)此部分可以略過:

我們在獲取 ViewModel 的時候,一般通過如下方式:

// 在 Controller(這里以 Fragment 為例)的 onCreate 方法中調(diào)用
final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);

我們看一下 ViewModelProviders.of() 的實現(xiàn):

public static ViewModelProvider of(@NonNull Fragment fragment) {
    return of(fragment, null);
}
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
    Application application = checkApplication(checkActivity(fragment));
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }

    // 最終用宿主 Controller 的 ViewModelStore 和一個 Factory 實例化一個
    // ViewModelProvider
    return new ViewModelProvider(fragment.getViewModelStore(), factory);
}

我們再看一下 ViewModelProvider.get() 方法獲取 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");
    }

    // 我們看到了 ViewModel 在 ViewModelStore 中的 key 表示
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {

    // 先檢查緩存中是否存在
    ViewModel viewModel = mViewModelStore.get(key);
    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }

    // 緩存中沒有,通過 Factory 構(gòu)造
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }

    // 新實例保存緩存
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}




3 ViewModel 與配置無關(guān)的原理(與宿主 Controller 俱生俱滅)

上一節(jié)我們說到,ViewModel 之所以能夠與宿主 Controller 保持生命周期一致,是因為存儲它的 ViewModelStore 與宿主 Controller 生命周期一致。那么為什么 ViewModelStore 能夠保持和 Controller 生命周期一致呢?

這里我們需要先理清 FragmentActivity 和其寄生的 Fragment 的 ViewModelStore 之間的關(guān)系:

3.1 ViewModelStore 樹

viewmodelstore_tree.png

如圖所示:

  • 每個 ViewModelStore 依附于其宿主 Controller,所以各個 Controller 的 ViewModelStore 組成一個樹狀的引用關(guān)系;
  • 處于頂層的 ViewModelStore 依附于 FragmentActivity,它除了保存用戶級的 ViewModel 以外,還保存其兒子 Fragment 的 FragmentManagerViewModel;
  • FragmentManagerViewModel 主要維護(hù)兩個對象:所屬 Fragment 的 ViewModelStore 和其兒子 Fragment 的 FragmentManagerViewModel 的引用,注意圖中的紅色部分,所有二級及以下的子孫 Fragment 都共用同一個父節(jié)點的 Child FragmentManagerModel,這樣當(dāng)父 Fragment 銷毀的時候方便一次性清除其所有子 Fragment 共用的 FragmentManagerViewModel;
  • 但是二級及以下的子孫 Fragment 的 ViewModelStore 都是獨立的,一個 Fragment 自身的 ViewModel 變化應(yīng)該不影響其兄弟節(jié)點的 ViewModel,所以可以推導(dǎo)出,它們共同的 FragmentManagerViewModel 應(yīng)該是維護(hù)了一個保存各個子 Fragment 的 ViewModelStore 的容器,大家如果細(xì)看 FragmentManagerViewModel 的源代碼,實際上就是這么做的。

所以,我們看到,處于頂層的 FragmentActivity 的 ViewModelStore 是一個超級 Store,它引用了所有的 ViewModels,包括自身的數(shù)據(jù)、所有子孫 Fragment 的 ViewModels,只要各子孫 Fragment 不清除自有 ViewModelStore,則所有的數(shù)據(jù)都維護(hù)在這棵 ViewModelStore 樹中。

那么在配置發(fā)生變化的時候,ViewModelStore 樹如何保持不變呢?

3.2 系統(tǒng)級的配置無關(guān)支持

將 ViewModelStore 作為配置無關(guān)數(shù)據(jù)進(jìn)行保持,在 FragmentActivity 中是這么做的:

activity_retain_nonconfig.png

是的,流程就是這么簡單,只需要將 ViewModelStore 封裝在一個特殊對象中保存并在 FragmentActivity 的 onRetainNonConfigurationInstance() 方法中返回即可:

/**
 * 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.
 */
@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
    Object custom = onRetainCustomNonConfigurationInstance();
    ViewModelStore viewModelStore = mViewModelStore;

    // ...省略與原理無關(guān)代碼

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

這樣,在頂層源頭上就保證了所有 Controller 的 ViewModels 不會在發(fā)送配置變化的時候由于 Controller 重建而被銷毀。

另外在 Fragment 層中,必須區(qū)分 Fragment 實例銷毀時到底是因為調(diào)用了 onDestroy 還是配置發(fā)生了變化,如果是前者則必須清理自身持有的 ViewModelStore,如果是后者則不能清理:

fragment_viewmodelstore_clear.png

如圖所示,也說明了 Fragment 的 ViewModel 生命周期與該 Fragment 生命周期是一致的。

// FragmentManagerImpl.moveToState()
//...省略
boolean beingRemoved = f.mRemoving && !f.isInBackStack(); // 是否 destroy,如果只是配置變化,則為 false
if (beingRemoved || mNonConfig.shouldDestroy(f)) {
    boolean shouldClear;
    if (mHost instanceof ViewModelStoreOwner) {
        // 說明這是第一層 Fragment,只要頂層 ViewModelStore 沒有清除該 FragmentManagerViewModel 就說明不用清理
        shouldClear = mNonConfig.isCleared();
    } else if (mHost.getContext() instanceof Activity) {
        // 說明是 FragmentActivity 的子孫 Fragment,根據(jù)是否是配置變化來判斷是否需要清理
        Activity activity = (Activity) mHost.getContext();
        shouldClear = !activity.isChangingConfigurations();
    } else {
        shouldClear = true;
    }
    if (beingRemoved || shouldClear) {
        // 只有確實 destroy 了才需要清理
        mNonConfig.clearNonConfigState(f);
    }
    f.performDestroy();
    dispatchOnFragmentDestroyed(f, false);
}
//...省略




4 FragmentActivity 中的 ViewModel 生命周期

最后,我們還需要說明一下,F(xiàn)ragmentActivity 中的 ViewModel 的生命周期是如何保持與 FragmentActivity 一致的,除了上一節(jié)中 FragmentActivity.onRetainNonConfigurationInstance() 中的配置無關(guān)保證以外,還需要保證在 Activity 真正銷毀的時候其所持有的 ViewModel 也應(yīng)該被清理。

其代碼實現(xiàn)非常簡單,只需要觀察該 Activity 的 Lifecycle 狀態(tài),并在銷毀狀態(tài)時進(jìn)行清理即可,關(guān)于 Lifecycle 我們將用專門的章節(jié)進(jìn)行說明,以下為清理代碼:

getLifecycle().addObserver(new LifecycleEventObserver() {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            // 觀察到 Activity 被銷毀
            if (!isChangingConfigurations()) {
                // 若不是配置變化,則清理
                getViewModelStore().clear();
            }
        }
    }
});




5 多 Controller 共享 ViewModel

我們參考第3.1節(jié)的 ViewModelStore 樹可知,如果多個 Controller 需要共享同一個 ViewModel 的話,我們只需要將該 ViewModel 保存在這些 Controller 共同的父 Controller 的 ViewModelStore 中即可,而這些子 Controller 可以通過如下方式獲取這個共享的 ViewModel:

[Fragment/FragmentActivity] parentContrl = ... // 共同的父 Controller
final CommonViewModel viewModel = ViewModelProviders.of(parentContrl).get(CommonViewModel.class);

這樣我們就解決了在 1.1 節(jié)中提到的第二個場景共享數(shù)據(jù)的問題。




6 關(guān)于工廠模式的一點思考

回到第2節(jié)中如何獲取一個 ViewModel 實例的過程,我們發(fā)現(xiàn),ViewModelProviders 實際相當(dāng)于一個 簡單工廠 模式,而 Facotry 則是一個 工廠方法 模式,前者根據(jù)不同的參數(shù)構(gòu)造不同的 ViewModelProvider,后者則可以實現(xiàn)不同的具體 Factory 來構(gòu)造不同的 ViewModel。

這里有兩層抽象:

  • 如何實現(xiàn)并實例化一個數(shù)據(jù)模型,各個數(shù)據(jù)模型的實現(xiàn)細(xì)節(jié)是不可預(yù)知的 -> ViewModel;
  • 采用什么樣的規(guī)則去得到一個 ViewModel 實例,且該規(guī)則是一個統(tǒng)一可預(yù)知的規(guī)則,但并不關(guān)心 ViewModel 實現(xiàn)的細(xì)節(jié) -> ViewModelProvider。

所以我們看到,當(dāng)一個對象的構(gòu)造是采用了統(tǒng)一的規(guī)則時(比如 ViewModelProvider),適合用簡單工廠模式來實現(xiàn),因為該規(guī)則本身可以被封裝;而當(dāng)一個對象的構(gòu)造方式?jīng)]有統(tǒng)一規(guī)則可以遵循,其實現(xiàn)細(xì)節(jié)更多與業(yè)務(wù)相關(guān)時,其可被封裝的部分僅為它的 new 方法,這時更適合用工廠方法模式來實現(xiàn)。






說明:

本文參考的 androidx 版本為

core: 1.1.0

lifecyle: 2.2.0-alpha01

fragment: 1.1.0-alpha09


如果大家喜歡我寫的文章,歡迎關(guān)注我的公眾號——小舍,將有更多有趣的內(nèi)容分享給大家:

?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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