為什么 Activity 都重建了 ViewModel 還存在?—— Jetpack 系列

請點贊,你的點贊對我意義重大,滿足下我的虛榮心。

?? Hi,我是小彭。本文已收錄到 GitHub · Android-NoteBook 中。這里有 Android 進階成長知識體系,有志同道合的朋友,歡迎跟我一起成長。

前言

ViewModel 是 Jetpack 組件中較常用的組件之一,也是實現(xiàn) MVVM 模式或 MVI 模式的標準組件之一。在這篇文章里,我將與你討論 ViewModel 實用和面試常見的知識點。如果能幫上忙請務(wù)必點贊加關(guān)注,這對我非常重要。


這篇文章是 Jetpack 系列文章第 3 篇,專欄文章列表:

一、架構(gòu)組件:

二、其他:

  • 1、AppStartup:輕量級初始化框架
  • 2、DataStore:新一代鍵值對存儲方案
  • 3、Room:ORM 數(shù)據(jù)庫訪問框架
  • 4、WindowManager:加強對多窗口模式的支持
  • 5、WorkManager:加強對后臺任務(wù)的支持
  • 6、Compose:新一代視圖開發(fā)方案

1. 認識 ViewModel

1.1 為什么要使用 ViewModel?

ViewModel 的作用可以區(qū)分 2 個維度來理解:

  • 1、界面控制器維度: 在最初的 MVC 模式中,Activity / Fragment 中承擔(dān)的職責(zé)過重,因此,在后續(xù)的 UI 開發(fā)模式中,我們選擇將 Activity / Fragment 中與視圖無關(guān)的職責(zé)抽離出來,在 MVP 模式中叫作 Presenter,在 MVVM 模式中叫作 ViewModel。因此,我們使用 ViewModel 來承擔(dān)界面控制器的職責(zé),并且配合 LiveData / Flow 實現(xiàn)數(shù)據(jù)驅(qū)動。
  • 2、數(shù)據(jù)維度: 由于 Activity 存在因配置變更銷毀重建的機制,會造成 Activity 中的所有瞬態(tài)數(shù)據(jù)丟失,例如網(wǎng)絡(luò)請求得到的用戶信息、視頻播放信息或者異步任務(wù)都會丟失。而 ViewModel 能夠應(yīng)對 Activity 因配置變更而重建的場景,在重建的過程中恢復(fù) ViewModel 數(shù)據(jù),從而降低用戶體驗受損。

關(guān)于 MVVM 等模式的更多內(nèi)容,我們在 5、從 MVC 到 MVP、MVVM、MVI:Android UI 架構(gòu)演進 這篇文章討論過。

MVVM 模式示意圖:

MVI 模式示意圖:

ViewModel 生命周期示意圖:

1.2 ViewModel 的使用方法

  • 1、添加依賴: 在 build.gradle 中添加 ViewModel 依賴,需要注意區(qū)分過時的方式:
// 過時方式(lifecycle-extensions 不再維護)
implementation "androidx.lifecycle:lifecycle-extensions:2.4.0"

// 目前的方式:
def lifecycle_version = "2.5.0"
// Lifecycle 核心類
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
  • 2、模板代碼: ViewModel 通常會搭配 LiveData 使用,以下為使用模板,相信大家都很熟悉了:

NameViewModel.kt

class NameViewModel : ViewModel() {
    val currentName: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }
}

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private val model: NameViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // LiveData 觀察者
        val nameObserver = Observer<String> { newName ->
            // 更新視圖
            nameTextView.text = newName
        }

        // 注冊 LiveData 觀察者,this 為生命周期宿主
        model.currentName.observe(this, nameObserver)

        // 修改 LiveData 數(shù)據(jù)
        button.setOnClickListener {
            val anotherName = "John Doe"
            model.currentName.value = anotherName
        }
    }
}

1.3 ViewModel 的創(chuàng)建方式

創(chuàng)建 ViewModel 實例的方式主要有 3 種,它們最終都是通過第 1 種 ViewModelProvider 完成的:

  • 方法 1: ViewModelProvider 是創(chuàng)建 ViewModel 的工具類:

示例程序

// 不帶工廠的創(chuàng)建方式
val vm = ViewModelProvider(this).get(MainViewModel::class.java)
// 帶工廠的創(chuàng)建方式
val vmWithFactory = ViewModelProvider(this, MainViewModelFactory()).get(MainViewModel::class.java)

// ViewModel 工廠
class MainViewModelFactory(

) : ViewModelProvider.Factory {

    private val repository = MainRepository()

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return MainViewModel(repository) as T
    }
}
  • 方法 2: 使用 Kotlin by 委托屬性,本質(zhì)上是間接使用了 ViewModelProvider:

示例程序

// 在 Activity 中使用
class MainActivity : AppCompatActivity() {
    // 使用 Activity 的作用域
    private val viewModel : MainViewModel by viewModels()
}

// 在 Fragment 中使用
class MainFragment : Fragment() {
    // 使用 Activity 的作用域,與 MainActivity 使用同一個對象
    val activityViewModel : MainViewModel by activityViewModels()
    // 使用 Fragment 的作用域
    val viewModel : MainViewModel by viewModels()
}
  • 方法 3: Hilt 提供了注入部分 Jetpack 架構(gòu)組件的支持

示例程序

@HiltAndroidApp
class DemoApplication : Application() { ... }

@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
    ...
}

@AndroidEntryPoint
class MainHiltActivity : AppCompatActivity(){
    val viewModel by viewModels<MainViewModel>()
    ...
}

依賴項

// Hilt ViewModel 支持
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0"
// Hilt 注解處理器
kapt "androidx.hilt:hilt-compiler:1.0.0"

需要注意的是,雖然可以使用依賴注入普通對象的方式注入 ViewModel,但是這相當于繞過了 ViewModelProvider 來創(chuàng)建 ViewModel。這意味著 ViewModel 實例一定不會存放在 ViewModelStore 中,將失去 ViewModel 恢復(fù)界面數(shù)據(jù)的特性。

錯誤示例

@AndroidEntryPoint
class MainHiltActivity : AppCompatActivity(){
    @Inject
    lateinit var viewModel : MainViewModel
}

2. ViewModel 實現(xiàn)原理分析

2.1 ViewModel 的創(chuàng)建過程

上一節(jié)提到,3 種創(chuàng)建 ViewModel 實例的方法最終都是通過 ViewModelProvider 完成的。ViewModelProvider 可以理解為創(chuàng)建 ViewModel 的工具類,它需要 2 個參數(shù):

  • 參數(shù) 1 ViewModelStoreOwner: 它對應(yīng)于 Activity / Fragment 等持有 ViewModel 的宿主,它們內(nèi)部通過 ViewModelStore 維持一個 ViewModel 的映射表,ViewModelStore 是實現(xiàn) ViewModel 作用域和數(shù)據(jù)恢復(fù)的關(guān)鍵;
  • 參數(shù) 2 Factory: 它對應(yīng)于 ViewModel 的創(chuàng)建工廠,缺省時將使用默認的 NewInstanceFactory 工廠來反射創(chuàng)建 ViewModel 實例。

創(chuàng)建 ViewModelProvider 工具類后,你將通過 get() 方法來創(chuàng)建 ViewModel 的實例。get() 方法內(nèi)部首先會通過 ViewModel 的全限定類名從映射表(ViewModelStore)中取緩存,未命中才會通過 ViewModel 工廠創(chuàng)建實例再緩存到映射表中。

正因為同一個 ViewModel 宿主使用的是同一個 ViewModelStore 映射表,因此在同一個宿主上重復(fù)調(diào)用 ViewModelProvider#get() 返回同一個 ViewModel 實例。

ViewModelProvider.java

// ViewModel 創(chuàng)建工廠
private final Factory mFactory;
// ViewModel 存儲容器
private final ViewModelStore mViewModelStore;

// 默認使用 NewInstanceFactory 反射創(chuàng)建 ViewModel
public ViewModelProvider(ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), ... NewInstanceFactory.getInstance());
}

// 自定義 ViewModel 創(chuàng)建工廠
public ViewModelProvider(ViewModelStoreOwner owner, Factory factory) {
    this(owner.getViewModelStore(), factory);
}

// 記錄宿主的 ViewModelStore 和 ViewModel 工廠
public ViewModelProvider(ViewModelStore store, Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}

@NonNull
@MainThread
public <T extends ViewModel> T get(Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
        // 使用類名作為緩存的 KEY
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

// Fragment
@NonNull
@MainThread
public <T extends ViewModel> T get(String key, Class<T> modelClass) {
    // 1. 先從 ViewModelStore 中取緩存
    ViewModel viewModel = mViewModelStore.get(key);
    if (modelClass.isInstance(viewModel)) {
        return (T) viewModel;
    }
    // 2. 使用 ViewModel 工廠創(chuàng)建實例
    viewModel = mFactory.create(modelClass);
    ...
    // 3. 存儲到 ViewModelStore
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

// 默認的 ViewModel 工廠
public static class NewInstanceFactory implements Factory {

    private static NewInstanceFactory sInstance;

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

    @NonNull
    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        // 反射創(chuàng)建 ViewModel 對象
        return modelClass.newInstance();
    }
}

ViewModelStore.java

// ViewModel 本質(zhì)上就是一個映射表而已
public class ViewModelStore {
    // <String - ViewModel> 哈希表
    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());
    }

    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

ViewModel 宿主是 ViewModelStoreOwner 接口的實現(xiàn)類,例如 Activity:

ViewModelStoreOwner.java

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

androidx.activity.ComponentActivity.java

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
    ContextAware,
    LifecycleOwner,
    ViewModelStoreOwner ... {
        
    // ViewModel 的存儲容器
    private ViewModelStore mViewModelStore;
    // ViewModel 的創(chuàng)建工廠
    private ViewModelProvider.Factory mDefaultFactory;

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (mViewModelStore == null) {
            // 已簡化,后文補全
            mViewModelStore = new ViewModelStore();
        }
        return mViewModelStore;
    }
}

2.2 by viewModels() 實現(xiàn)原理分析

by 關(guān)鍵字是 Kotlin 的委托屬性,內(nèi)部也是通過 ViewModelProvider 來創(chuàng)建 ViewModel。關(guān)于 Kotlin 委托屬性的更多內(nèi)容,我們在 Kotlin | 委托機制 & 原理 & 應(yīng)用 這篇文章討論過,這里不重復(fù)。

ActivityViewModelLazy.kt

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

    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

ViewModelLazy.kt

public class ViewModelLazy<VM : ViewModel> (
    private val viewModelClass: KClass<VM>,
    private val storeProducer: () -> ViewModelStore,
    private val factoryProducer: () -> ViewModelProvider.Factory
) : 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 創(chuàng)建 ViewModel 實例
                ViewModelProvider(store, factory).get(viewModelClass.java).also {
                    cached = it
                }
            } else {
                viewModel
            }
        }

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

2.3 ViewModel 如何實現(xiàn)不同的作用域

ViewModel 內(nèi)部會為不同的 ViewModel 宿主分配不同的 ViewModelStore 映射表,不同宿主是從不同的數(shù)據(jù)源來獲取 ViewModel 的實例,因而得以區(qū)分作用域。

具體來說,在使用 ViewModelProvider 時,我們需要傳入一個 ViewModelStoreOwner 宿主接口,它將在 getViewModelStore() 接口方法中返回一個 ViewModelStore 實例。

  • 對于 Activity 來說,ViewModelStore 實例是直接存儲在 Activity 的成員變量中的;
  • 對于 Fragment 來說,ViewModelStore 實例是間接存儲在 FragmentManagerViewModel 中的 <Fragment - ViewModelStore> 映射表中的。

這樣就實現(xiàn)了不同的 Activity 或 Fragment 分別對應(yīng)不同的 ViewModelStore 實例,進而區(qū)分不同作用域。

androidx.activity.ComponentActivity.java

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
    ContextAware,
    LifecycleOwner,
    ViewModelStoreOwner ... {

        @NonNull
        @Override
        public ViewModelStore getViewModelStore() {
            if (mViewModelStore == null) {
                // 已簡化,后文補全
                mViewModelStore = new ViewModelStore();
            }
            return mViewModelStore;
        }
}

Fragment.java

@NonNull
@Override
public ViewModelStore getViewModelStore() {
    // 最終調(diào)用 FragmentManagerViewModel#getViewModelStore(Fragment)
    return mFragmentManager.getViewModelStore(this);
}

FragmentManagerViewModel.java

// <Fragment - ViewModelStore> 映射表
private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();

@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
    if (viewModelStore == null) {
        viewModelStore = new ViewModelStore();
        mViewModelStores.put(f.mWho, viewModelStore);
    }
    return viewModelStore;
}

2.4 為什么 Activity 在屏幕旋轉(zhuǎn)重建后可以恢復(fù) ViewModel?

ViewModel 底層是基于原生 Activity 因設(shè)備配置變更重建時恢復(fù)數(shù)據(jù)的機制實現(xiàn)的,這個其實跟 Fragment#setRetainInstance(true) 持久 Fragment 的機制是相同的。當 Activity 因配置變更而重建時,我們可以將頁面上的數(shù)據(jù)或狀態(tài)可以定義為 2 類:

  • 第 1 類 - 配置數(shù)據(jù): 例如窗口大小、多語言字符、多主題資源等,當設(shè)備配置變更時,需要根據(jù)最新的配置重新讀取新的數(shù)據(jù),因此這部分數(shù)據(jù)在配置變更后便失去意義,自然也就沒有存在的價值;
  • 第 2 類 - 非配置數(shù)據(jù): 例如用戶信息、視頻播放信息、異步任務(wù)等非配置相關(guān)數(shù)據(jù),這些數(shù)據(jù)跟設(shè)備配置沒有一點關(guān)系,如果在重建 Activity 的過程中丟失,不僅沒有必要,而且會損失用戶體驗(無法快速恢復(fù)頁面數(shù)據(jù),或者丟失頁面進度)。

基于以上考慮,Activity 是支持在設(shè)備配置變更重建時恢復(fù) 第 2 類 - 非配置數(shù)據(jù) 的,源碼中存在 NonConfiguration 字眼的代碼,就是與這個機制相關(guān)的代碼。我將整個過程大概可以概括為 3 個階段:

  • 階段 1: 系統(tǒng)在處理 Activity 因配置變更而重建時,會先調(diào)用 retainNonConfigurationInstances 獲取舊 Activity 中的數(shù)據(jù),其中包含 ViewModelStore 實例,而這一份數(shù)據(jù)會臨時存儲在當前 Activity 的 ActivityClientRecord(屬于當前進程,下文說明);
  • 階段 2: 在新 Activity 重建后,系統(tǒng)通過在 Activity#onAttach(…) 中將這一份數(shù)據(jù)傳遞到新的 Activity 中;
  • 階段 3: Activity 在構(gòu)造 ViewModelStore 時,會優(yōu)先從舊 Activity 傳遞過來的這份數(shù)據(jù)中獲取,為空才會創(chuàng)建新的 ViewModelStore。

對于 ViewModel 來說,相當于舊 Activity 中所有的 ViewModel 映射表被透明地傳遞到重建后新的 Activity 中,這就實現(xiàn)了恢復(fù) ViewModel 的功能??偨Y(jié)一下重建前后的實例變化,幫助你理解:

  • Activity: 構(gòu)造新的實例;
  • ViewModelStore: 保留舊的實例;
  • ViewModel: 保留舊的實例(因為 ViewModel 存儲在 ViewModelStore 映射表中);
  • LiveData: 保留舊的實例(因為 LiveData 是 ViewModel 的成員變量);

現(xiàn)在,我們逐一分析這 3 個階段的源碼執(zhí)行過程:

階段 1 源碼分析:

Activity.java

// 階段 1:獲取 Activity 的非配置相關(guān)數(shù)據(jù)
NonConfigurationInstances retainNonConfigurationInstances() {
    // 1.1 構(gòu)造 Activity 級別的非配置數(shù)據(jù)
    Object activity = onRetainNonConfigurationInstance();
    // 1.2 構(gòu)造 Fragment 級別的費配置數(shù)據(jù)數(shù)據(jù)
    FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

    ...

    // 1.3 構(gòu)造并返回 NonConfigurationInstances 非配置相關(guān)數(shù)據(jù)類
    NonConfigurationInstances nci = new NonConfigurationInstances();
        
    nci.activity = activity;
    nci.fragments = fragments;
        ...
    return nci;
}

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

androidx.activity.ComponentActivity.java

private ViewModelStore mViewModelStore;

// 1.1 ComponentActivity 在 onRetainNonConfigurationInstance() 中寫入了 ViewModelStore
@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
    ViewModelStore viewModelStore = mViewModelStore;
    // 這一個 if 語句是處理異常邊界情況:
    // 如果重建的 Activity 沒有調(diào)用 getViewModelStore(),那么舊的 Activity 中的 ViewModel 并沒有被取出來,
    // 因此在準備再一次存儲當前 Activity 時,需要檢查一下舊 Activity 傳過來的數(shù)據(jù)。
    if (viewModelStore == null) {
        NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }
    // ViewModelStore 為空說明當前 Activity 和舊 Activity 都沒有 ViewModel,沒必要存儲和恢復(fù)
    if (viewModelStore == null) {
        return null;
    }
    NonConfigurationInstances nci = new NonConfigurationInstances();
    // 保存 ViewModelStore 對象
    nci.viewModelStore = viewModelStore;
    return nci;
}

ActivityThread.java

// Framework 調(diào)用 retainNonConfigurationInstances() 獲取非配置數(shù)據(jù)后,
// 會通過當前進程內(nèi)存臨時存儲這一份數(shù)據(jù),這部分源碼我們暫且放到一邊。

階段 2 源碼分析:

Activity.java

// 階段 2:在 Activity#attach() 中傳遞舊 Activity 的數(shù)據(jù)
NonConfigurationInstances mLastNonConfigurationInstances;

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

至此,舊 Activity 的數(shù)據(jù)就傳遞到新 Activity 的成員變量 mLastNonConfigurationInstances 中。

階段 3 源碼分析:

androidx.activity.ComponentActivity.java

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
    ContextAware,
    LifecycleOwner,
    ViewModelStoreOwner ... {
        
    private ViewModelStore mViewModelStore;
    private ViewModelProvider.Factory mDefaultFactory;

    // 階段 3:Activity 的 ViewModelStore 優(yōu)先使用舊 Activity 傳遞過來的 ViewModelStore
    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (mViewModelStore == null) {
            // 3.1 優(yōu)先使用舊 Activity 傳遞過來的 ViewModelStore
            NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                mViewModelStore = nc.viewModelStore;
            }
            // 3.2 否則創(chuàng)建新的 ViewModelStore
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }
}

Activity.java

// 這個變量在階段 2 賦值
NonConfigurationInstances mLastNonConfigurationInstances;

// 返回從 attach() 中傳遞過來的舊 Activity 數(shù)據(jù)
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity : null;
}

至此,就完成 ViewModel 數(shù)據(jù)恢復(fù)了。


現(xiàn)在,我們回過頭來分析下 ActivityThread 這一部分源碼:

ActivityThread 中的調(diào)用過程:

在 Activity 因配置變更而重建時,系統(tǒng)將執(zhí)行 Relaunch 重建過程。系統(tǒng)在這個過程中通過同一個 ActivityClientRecord 來完成信息傳遞,會銷毀當前 Activity,緊接著再馬上重建同一個 Activity。

  • 階段 1: 在處理 Destroy 邏輯時,調(diào)用 Activity#retainNonConfigurationInstances() 方法獲取舊 Activity 中的非配置數(shù)據(jù),并臨時保存在 ActivityClientRecord 中;
  • 階段 2: 在處理 Launch 邏輯時,調(diào)用 Activity#attach(…) 將 ActivityClientRecord 中臨時保存的非配置數(shù)據(jù)傳遞到新 Activity 中。

ActivityThread.java

private void handleRelaunchActivityInner(ActivityClientRecord r, ...) {
    final Intent customIntent = r.activity.mIntent;
    // 處理 onPause()
    performPauseActivity(r, false, reason, null /* pendingActions */);
    // 處理 onStop()
    callActivityOnStop(r, true /* saveState */, reason);
    // 階段 1:獲取 Activity 的非配置相關(guān)數(shù)據(jù)
    handleDestroyActivity(r.token, false, configChanges, true, reason);

    // 至此,Activity 中的 第 2 類 - 非配置數(shù)據(jù)就記錄在 ActivityClientRecord 中,
    // 并通過同一個 ActivityClientRecord 重建一個新的 Activity

    // 階段 2:在 Activity#attach() 中傳遞舊 Activity 的數(shù)據(jù)
    handleLaunchActivity(r, pendingActions, customIntent);

    // 至此,舊 Activity 中的非配置數(shù)據(jù)已傳遞到新 Activity
}

public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance, reason);
    ...
    if (finishing) {
        ActivityTaskManager.getService().activityDestroyed(token);
    }
}

// 階段 1:獲取 Activity 的非配置相關(guān)數(shù)據(jù)
// 參數(shù) finishing 為 false
// 參數(shù) getNonConfigInstance 為 true
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    // 保存非配置數(shù)據(jù),調(diào)用了階段 1 中提到的 retainNonConfigurationInstances() 方法
    if (getNonConfigInstance) {
        r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
    }
    // 執(zhí)行 onDestroy()
    mInstrumentation.callActivityOnDestroy(r.activity);
    return r;
}

public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
    final Activity a = performLaunchActivity(r, customIntent);
}

// 階段 2:在 Activity#attach() 中傳遞舊 Activity 的數(shù)據(jù)
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // 創(chuàng)建新的 Activity 實例
    Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    // 創(chuàng)建或獲取 Application 實例,在這個場景里是獲取
    Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    // 傳遞 lastNonConfigurationInstances 數(shù)據(jù)
    activity.attach(appContext, ..., r.lastNonConfigurationInstances,...);
    // 清空臨時變量
    r.lastNonConfigurationInstances = null;
    ...
}

2.5 ViewModel 的數(shù)據(jù)在什么時候才會清除

ViewModel 的數(shù)據(jù)會在 Activity 非配置變更觸發(fā)的銷毀時清除,具體分為 3 種情況:

  • 第 1 種: 直接調(diào)用 Activity#finish() 或返回鍵等間接方式;
  • 第 2 種: 異常退出 Activity,例如內(nèi)存不足;
  • 第 3 種: 強制退出應(yīng)用。

第 3 種沒有給予系統(tǒng)或應(yīng)用存儲數(shù)據(jù)的時機,內(nèi)存中的數(shù)據(jù)自然都會被清除。而前 2 種情況都屬于非配置變更觸發(fā)的,在 Activity 中存在 1 個 Lifecycle 監(jiān)聽:當 Activity 進入 DESTROYED 狀態(tài)時,如果 Activity 不處于配置變更重建的階段,將調(diào)用 ViewModelStore#clear() 清除 ViewModel 數(shù)據(jù)。

androidx.activity.ComponentActivity.java

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
    ContextAware,
    LifecycleOwner,
    ViewModelStoreOwner ... {
        
    private ViewModelStore mViewModelStore;
    private ViewModelProvider.Factory mDefaultFactory;

    public ComponentActivity() {
        // DESTROYED 狀態(tài)監(jiān)聽
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    mContextAwareHelper.clearAvailableContext();
                    // 是否處于配置變更引起的重建
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
    }
}

Activity.java

boolean mChangingConfigurations = false;

public boolean isChangingConfigurations() {
    return mChangingConfigurations;
}

3. ViewModel 的內(nèi)存泄漏問題

ViewModel 的內(nèi)存泄漏是指 Activity 已經(jīng)銷毀,但是 ViewModel 卻被其他組件引用。這往往是因為數(shù)據(jù)層是通過回調(diào)監(jiān)聽器的方式返回數(shù)據(jù),并且數(shù)據(jù)層是單例對象或者屬于全局生命周期,所以導(dǎo)致 Activity 銷毀了,但是數(shù)據(jù)層依然間接持有 ViewModel 的引用。

如果 ViewModel 是輕量級的或者可以保證數(shù)據(jù)層操作快速完成,這個泄漏影響不大可以忽略。但如果數(shù)據(jù)層操作并不能快速完成,或者 ViewModel 存儲了重量級數(shù)據(jù),就有必要采取措施。例如:

  • 方法 1: 在 ViewModel#onCleared() 中通知數(shù)據(jù)層丟棄對 ViewModel 回調(diào)監(jiān)聽器的引用;
  • 方法 2: 在數(shù)據(jù)層使用對 ViewModel 回調(diào)監(jiān)聽器的弱引用(這要求 ViewModel 必須持有回調(diào)監(jiān)聽器的強引用,而不能使用匿名內(nèi)部類,這會帶來編碼復(fù)雜性);
  • 方法 3: 使用 EventBus 代替回調(diào)監(jiān)聽器(這會帶來編碼復(fù)雜性);
  • 方法 4: 使用 LiveData 的 Transformations.switchMap() API 包裝數(shù)據(jù)層的請求方法,這相當于在 ViewModel 和數(shù)據(jù)層中間使用 LiveData 進行通信。例如:

MyViewModel.java

// 用戶 ID LiveData
MutableLiveData userIdLiveData = new MutableLiveData<String>(); 

// 用戶數(shù)據(jù) LiveData
LiveData userLiveData = Transformations.switchMap(userIdLiveData, id -> 
    // 調(diào)用數(shù)據(jù)層 API
    repository.getUserById(id));

// 設(shè)置用戶 ID
// 每次的 userIdLiveData 的值發(fā)生變化,repository.getUserById(id) 將被調(diào)用,并將結(jié)果設(shè)置到 userLiveData 上
public void setUserId(String userId) { 
    this.userIdLiveData.setValue(userId); 
}

4. ViewModel 和 onSaveInstanceState() 的對比

ViewModel 和 onSaveInstanceState() 都是對數(shù)據(jù)的恢復(fù)機制,但由于它們針對的場景不同,導(dǎo)致它們的實現(xiàn)原理不同,進而優(yōu)缺點也不同。

  • 1、ViewModel: 使用場景針對于配置變更重建中非配置數(shù)據(jù)的恢復(fù),由于內(nèi)存是可以滿足這種存儲需求的,因此可以選擇內(nèi)存存儲。又由于內(nèi)存空間相對較大,因此可以存儲大數(shù)據(jù),但會受到內(nèi)存空間限制;
  • 2、onSaveInstanceState() :使用場景針對于應(yīng)用被系統(tǒng)回收后重建時對數(shù)據(jù)的恢復(fù),由于應(yīng)用進程在這個過程中會消亡,因此不能選擇內(nèi)存存儲而只能選擇使用持久化存儲。又由于這部分數(shù)據(jù)需要通過 Bundle 機制在應(yīng)用進程和 AMS 服務(wù)之間傳遞,因此會受到 Binder 事務(wù)緩沖區(qū)大小限制,只可以存儲小規(guī)模數(shù)據(jù)。

如果是正常的 Activity 退出,例如返回鍵或者 finish(),都不屬于 ViewModel 和 onSaveInstanceState() 的應(yīng)用場景,因此都不會存儲和恢復(fù)數(shù)據(jù)。


5. 總結(jié)

到這里,Jetpack 中的 ViewModel 組件就講完了。下一篇文章,我們來討論 LiveData 的替代方案 Flow。關(guān)注我,帶你了解更多。


參考資料

最后編輯于
?著作權(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)容