
請點贊,你的點贊對我意義重大,滿足下我的虛榮心。
?? Hi,我是小彭。本文已收錄到 GitHub · Android-NoteBook 中。這里有 Android 進階成長知識體系,有志同道合的朋友,歡迎跟我一起成長。
前言
ViewModel 是 Jetpack 組件中較常用的組件之一,也是實現(xiàn) MVVM 模式或 MVI 模式的標準組件之一。在這篇文章里,我將與你討論 ViewModel 實用和面試常見的知識點。如果能幫上忙請務(wù)必點贊加關(guān)注,這對我非常重要。
這篇文章是 Jetpack 系列文章第 3 篇,專欄文章列表:
一、架構(gòu)組件:
- 1、Lifecycle:生命周期感知型組件的基礎(chǔ)
- 2、LiveData:生命周期感知型數(shù)據(jù)容器
- 3、ViewModel:數(shù)據(jù)驅(qū)動型界面控制器(本文)
- 4、Flow:LiveData 的替代方案
- 5、從 MVC 到 MVP、MVVM、MVI:Android UI 架構(gòu)演進
- 6、ViewBinding:新一代視圖綁定方案
- 7、Fragment:模塊化的微型 Activity
- 8、RecyclerView:可復(fù)用型列表視圖
- 9、Navigation:單 Activity 多 Fragment 的導(dǎo)航方案
- 10、Dagger2:從 Dagger2 到 Hilt 玩轉(zhuǎn)依賴注入(一)
- 11、Hilt:從 Dagger2 到 Hilt 玩轉(zhuǎn)依賴注入(二)
- 12、OnBackPressedDispatcher:處理回退事件的新姿勢
二、其他:
- 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)注我,帶你了解更多。
參考資料
- ViewModel 概覽 —— 官方文檔
- 保存界面狀態(tài) —— 官方文檔
- ViewModel 的 SavedState 模塊 —— 官方文檔
- ViewModel 和 LiveData:為設(shè)計模式打 Call 還是唱反調(diào)? —— 官方博文