ViewModel原理分析

認識 ViewModel

ViewModel 是一種用來存儲和管理UI相關數據的類。

ViewModel 的作用可以從兩個方面去理解:

  • UI界面控制器:在最初的MVC模式中,由于 Activity / Fragment 承擔的職責過重,因此在后續的 MVP、MVVM 模式中,選擇將 Activity / Fragment 中與視圖無關的職責抽離出來,在 MVP 模式中叫作 Presenter,在 MVVM 模式中叫作 ViewModel。使用 ViewModel 來承擔界面控制器的職責,并且配合 LiveData / Flow 實現數據驅動。
  • 數據存儲:由于 Activity 存在因配置變更銷毀重建的機制,會造成 Activity 中的所有瞬態數據丟失,例如網絡請求得到的用戶信息、視頻播放信息或者異步任務都會丟失。而 ViewModel 的特點是生命周期長于 Activity ,因此能夠應對 Activity 因配置變更而重建的場景,在重建的過程中恢復 ViewModel 數據,從而降低用戶體驗受損。

ViewModel 生命周期示意圖:


ViewModel 生命周期示意圖

使用 ViewModel

使用步驟:

  1. 自定義 ViewModel 繼承 ViewModel。
  2. 在自定義 ViewModel 中編寫獲取UI數據的邏輯。
  3. 配合使用 LiveData / Flow 實現數據驅動。
  4. 在 Activity / Fragment中 獲取 ViewModel 實例。
  5. 監聽或收集 ViewModel 中的 LiveData / Flow 數據,進行對應的UI更新。

簡單示例:

class TestFlowViewModel : ViewModel() {
    private val _state: MutableStateFlow<Int> = MutableStateFlow(0)
    val state: StateFlow<Int> get() = _state
    private val _live: MutableLiveData<String> = MutableLiveData<String>()
    val live: LiveData<String> get() = _live
    
    fun test() {
        _live.value = "1"
        for (state in 1..5) {
            viewModelScope.launch(Dispatchers.IO) {
                delay(100L * state)
                _state.emit(state)
            }
        }
    }
}

Activity / Fragment 中相關代碼:

private val viewModel: TestFlowViewModel by viewModels()

lifecycleScope.launch {
    launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            viewModel.state.collect {
                Log.d(TAG, "state: $it ")
            }
        }
    }
    delay(100)
    viewModel.test()
}
viewModel.live.observe(this) {
    Log.d(TAG, "it: $it")
}

在 Activity / Fragment中 獲取 ViewModel 實例有兩種方式,一種是通過ViewModelProvider獲取,也key自定義ViewModelProvider.Factory,

private val viewModel = ViewModelProvider(this).get(TestFlowViewModel::class.java)

另一種就是上面示例里面使用的方式:使用 Kotlin by 委托屬性,本質上是間接使用了 ViewModelProvider。

@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline extrasProducer: (() -> CreationExtras)? = null,
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }

    return ViewModelLazy(
        VM::class,
        { viewModelStore },
        factoryPromise,
        { extrasProducer?.invoke() ?: this.defaultViewModelCreationExtras }
    )
}

public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
    private val viewModelClass: KClass<VM>,
    private val storeProducer: () -> ViewModelStore,
    private val factoryProducer: () -> ViewModelProvider.Factory,
    private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
) : Lazy<VM> {
    private var cached: VM? = null

    override val value: VM
        get() {
            val viewModel = cached
            return if (viewModel == null) {
                val factory = factoryProducer()
                val store = storeProducer()
                ViewModelProvider(
                    store,
                    factory,
                    extrasProducer()
                ).get(viewModelClass.java).also {
                    cached = it
                }
            } else {
                viewModel
            }
        }

    override fun isInitialized(): Boolean = cached != null
}

分析 ViewModel 原理

ViewModel 創建過程

上面說到了創建 ViewModel 實例的方法最終都是通過 ViewModelProvider 完成的。ViewModelProvider 可以理解為創建 ViewModel 的工具類,它需要 2 個參數:

  • ViewModelStoreOwner: 它對應于 Activity / Fragment 等持有 ViewModel 的宿主,它們內部通過 ViewModelStore 維持一個 ViewModel 的映射表,ViewModelStore 是實現 ViewModel 作用域和數據恢復的關鍵;
  • Factory: 它對應于 ViewModel 的創建工廠,缺省時將使用默認的 NewInstanceFactory 工廠來反射創建 ViewModel 實例。

創建 ViewModelProvider 工具類后,通過 get() 方法來獲取 ViewModel 的實例。get() 方法內部首先會從 ViewModelStore 中取緩存,沒有緩存才會通過 ViewModel 工廠創建實例再緩存到 ViewModelStore 中。

    @MainThread
    public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
        // 先從 ViewModelStore 中取緩存
        val viewModel = store[key]
        // 存在 ViewModel,直接返回
        if (modelClass.isInstance(viewModel)) {
            (factory as? OnRequeryFactory)?.onRequery(viewModel!!)
            return viewModel as T
        } else {
            @Suppress("ControlFlowWithEmptyBody")
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        val extras = MutableCreationExtras(defaultCreationExtras)
        extras[VIEW_MODEL_KEY] = key
        // AGP has some desugaring issues associated with compileOnly dependencies so we need to
        // fall back to the other create method to keep from crashing.
        // 不存在則使用 ViewModel 工廠創建實例,并放入 ViewModelStore
        return try {
            factory.create(modelClass, extras)
        } catch (e: AbstractMethodError) {
            factory.create(modelClass)
        }.also { store.put(key, it) }
    }

ViewModelStore 是 ViewModel 存儲器,內部通過 LinkedHashMap 存儲 ViewModel。

ViewModelStoreOwner 是一個接口,ViewModelStore 的持有者是ViewModelStoreOwner 的實現類,包括有ComponentActivity和Fragment,它們內部都保存著一個 ViewModelStore。

interface ViewModelStoreOwner {

    /**
     * The owned [ViewModelStore]
     */
    val viewModelStore: ViewModelStore
}
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
    ContextAware,
    LifecycleOwner,
    ViewModelStoreOwner ... {
        
    private ViewModelStore mViewModelStore;
    private ViewModelProvider.Factory mDefaultFactory;

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        ...
        ensureViewModelStore();
        return mViewModelStore;
    }
}

正因為 ViewModel 宿主內部都保存著一個 ViewModelStore ,因此在同一個宿主上重復調用 ViewModelProvider#get() 返回同一個 ViewModel 實例,這也就做到了 fragment 共享 activity 的ViewModel 實例以及 fragment 之間共享 ViewModel。

為什么 Activity 在屏幕旋轉重建后可以恢復 ViewModel?

上面說到 ViewModel 其實是被保存在 ViewModelStore 里,所以 Activity 在屏幕旋轉重建后恢復 ViewModel 其實是重新獲取到了原有的 ViewModelStore。那么 Activity 里的 ViewModelStore 究竟是怎么生成的呢?

ViewModelStore 的生成

ComponentActivity 在構造函數中設置了對 lifecycle 的監聽,當 activity 的生命周期變化時會調用ensureViewModelStore方法,確保 ViewModelStore 的存在。

        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                ensureViewModelStore();
                getLifecycle().removeObserver(this);
            }
        });
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }

當 ViewModelStore 不存在時,ensureViewModelStore 方法會進行 ViewModelStore 的生成,首先通過 getLastNonConfigurationInstance 獲取到 NonConfigurationInstances,并從中獲取 ViewModelStore,若 ViewModelStore 仍未空,則創建 new 一個新的。

看到這,知道了 ViewModelStore 的生成來源于兩處,一處為 NonConfigurationInstances 中獲取,另一處是新創建。那么 Activity 在屏幕旋轉重建后獲取到了原有的 ViewModelStore 是不是就是從 NonConfigurationInstances 中獲取的呢?

接下去看一下 Activity 在屏幕旋轉重建后,ViewModelStore 都干什么去了呢?

Activity 因配置變更而重建時(比如屏幕旋轉),可以將頁面上的數據或狀態可以定義為 2 類:

  • 配置數據:例如窗口大小、多語言字符、多主題資源等,當設備配置變更時,需要根據最新的配置重新讀取新的數據,因此這部分數據在配置變更后便失去意義,自然也就沒有存在的價值;
  • 非配置數據:例如用戶信息、視頻播放信息、異步任務等非配置相關數據,這些數據跟設備配置沒有一點關系,如果在重建 Activity 的過程中丟失,不僅沒有必要,而且會損失用戶體驗(無法快速恢復頁面數據,或者丟失頁面進度)。
    基于以上考慮,Activity 是支持在設備配置變更重建時恢復非配置數據的,源碼中存在 NonConfiguration 字眼的代碼,就是與這個機制相關的代碼。

當 Activity 因配置變更而重建時,ActivityThreadhandleRelaunchActivity 方法會執行,先 handleDestroyActivity 銷毀 Activity,然后 handleLaunchActivity 重建 Activity。

Activity 銷毀過程

在 handleDestroyActivity 方法里執行到 performDestroyActivity 時,會執行 activity 的 retainNonConfigurationInstances 方法,將非配置數據臨時存儲在當前 Activity 的 ActivityClientRecord(當前進程內存)。

    void performDestroyActivity(ActivityClientRecord r, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
        Class<? extends Activity> activityClass = null;
        if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
        activityClass = r.activity.getClass();
        r.activity.mConfigChangeFlags |= configChanges;
        if (finishing) {
            r.activity.mFinished = true;
        }

        performPauseActivityIfNeeded(r, "destroy");

        if (!r.stopped) {
            callActivityOnStop(r, false /* saveState */, "destroy");
        }
        if (getNonConfigInstance) {
            try {
                // 將非配置數據臨時存儲在 ActivityClientRecord
                r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException("Unable to retain activity "
                            + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
                }
            }
        }
        ...
    }
// 獲取 Activity 的非配置相關數據
NonConfigurationInstances retainNonConfigurationInstances() {
    // 構造 Activity 級別的非配置數據
    Object activity = onRetainNonConfigurationInstance();
    ...
    // 構造 Fragment 級別的費配置數據數據
    FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
    ...
    // 構造并返回 NonConfigurationInstances 非配置相關數據類
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.activity = activity;
    nci.fragments = fragments;
    ...
    return nci;
}

// 默認返回 null,由 Activity 子類定義
public Object onRetainNonConfigurationInstance() {
    return null;
}

看一下onRetainNonConfigurationInstance在 ComponentActivity 中的實現:

    public final Object onRetainNonConfigurationInstance() {
        // Maintain backward compatibility.
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last NonConfigurationInstance
            // 這一個 if 語句是處理異常邊界情況: 
            // 如果重建的 Activity 沒有調用 getViewModelStore(),那么舊的 Activity 中的 ViewModel 并沒有被取出來, 
            // 因此在準備再一次存儲當前 Activity 時,需要檢查一下舊 Activity 傳過來的數據。
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }
        
        // ViewModelStore 為空說明當前 Activity 和舊 Activity 都沒有 ViewModel,沒必要存儲和恢復
        if (viewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        // 保存 ViewModelStore 對象
        nci.viewModelStore = viewModelStore;
        return nci;
    }

看到了 ViewModelStore 實例的保存,就這樣,當 Activity 被銷毀時,ViewModelStore 實例被保存進了 NonConfigurationInstances 中,進而被臨時存儲在了 ActivityClientRecord 里。

Activity 重建過程

在 handleLaunchActivity 方法里執行到 performLaunchActivity 時,會執行 activity 的attach方法,并將 ActivityClientRecord中的 NonConfigurationInstances 傳入。

// 在 Activity#attach() 中傳遞舊 Activity 的數據
NonConfigurationInstances mLastNonConfigurationInstances;

final void attach(Context context, ActivityThread aThread,
    ...
    NonConfigurationInstances lastNonConfigurationInstances) {
    ...
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    ...
}

至此,舊 Activity 的數據就傳遞到新 Activity 的成員變量 mLastNonConfigurationInstances 中了,ViewModelStore 將從 mLastNonConfigurationInstances 中獲取。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容