Android mvvm框架之ViewModel原理


目錄

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


1 什么是 ViewModel

1.1 先考慮兩個場景

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

1.2 缺點

隨着業務規模的擴大,以上的兩種場景在傳統實現方法中顯得越來越繁瑣且不易維護,且數據模塊不易獨立進行測試。

1.3 特別說明

關於場景一,同樣的場景還適用於各種配置相關的信息發生變化的情況,比如鍵盤、系統字體、語言區域等,它們的共同作用是都會導致當前 Controller 的重建。

1.4 ViewModel 解決的問題

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

這意味着無論是轉屏還是系統字體變化等因配置變化產生的 Controller 重建都不會回收 ViewModel 中維護的數據,重建的 Controller 仍然可以從同一個 ViewModel 中通過獲取數據恢復狀態。



2 ViewModel 實現原理

2.1 ViewModel 類

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

2.2 ViewModel 的構造過程

我們用一個結構圖來剖析 ViewModel 的構造過程:

如圖所示:

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

本小節剩下部分分析源碼,對於只關心原理的同學此部分可以略過:

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

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

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

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 構造
    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 與配置無關的原理(與宿主 Controller 俱生俱滅)

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

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

3.1 ViewModelStore 樹

如圖所示:

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

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

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

3.2 系統級的配置無關支持

將 ViewModelStore 作為配置無關數據進行保持,在 FragmentActivity 中是這么做的:

是的,流程就是這么簡單,只需要將 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;

    // ...省略與原理無關代碼

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

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

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

如圖所示,也說明了 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,根據是否是配置變化來判斷是否需要清理
        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 生命周期

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

其代碼實現非常簡單,只需要觀察該 Activity 的 Lifecycle 狀態,並在銷毀狀態時進行清理即可,關於 Lifecycle 我們將用專門的章節進行說明,以下為清理代碼:

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節的 ViewModelStore 樹可知,如果多個 Controller 需要共享同一個 ViewModel 的話,我們只需要將該 ViewModel 保存在這些 Controller 共同的父 Controller 的 ViewModelStore 中即可,而這些子 Controller 可以通過如下方式獲取這個共享的 ViewModel:

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

這樣我們就解決了在 1.1 節中提到的第二個場景共享數據的問題。



6 關於工廠模式的一點思考

回到第2節中如何獲取一個 ViewModel 實例的過程,我們發現,ViewModelProviders 實際相當於一個 簡單工廠 模式,而 Facotry 則是一個 工廠方法 模式,前者根據不同的參數構造不同的 ViewModelProvider,后者則可以實現不同的具體 Factory 來構造不同的 ViewModel。

這里有兩層抽象:

  • 如何實現並實例化一個數據模型,各個數據模型的實現細節是不可預知的 -> ViewModel;
  • 采用什么樣的規則去得到一個 ViewModel 實例,且該規則是一個統一可預知的規則,但並不關心 ViewModel 實現的細節 -> ViewModelProvider。

所以我們看到,當一個對象的構造是采用了統一的規則時(比如 ViewModelProvider),適合用簡單工廠模式來實現,因為該規則本身可以被封裝;而當一個對象的構造方式沒有統一規則可以遵循,其實現細節更多與業務相關時,其可被封裝的部分僅為它的 new 方法,這時更適合用工廠方法模式來實現。




說明:

本文參考的 androidx 版本為

core: 1.1.0

lifecyle: 2.2.0-alpha01

fragment: 1.1.0-alpha09


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM