源碼解析Android Jetpack組件之ViewModel的使用

前言

在之前 LiveData 源碼淺析的博客中提到瞭 ViewModel 組件,當時對 ViewModel 的解釋是 “生命周期比Activity” 更長的對象。本文就來瞭解下其實現原理。

依賴版本

// 註意這裡的 appcompat、activity-ktx、fragment-ktx
// 高版本的自動引入瞭 viewmodel-savedstate 實戰中很少用到的功能
// 篇幅原因 就不再本文中分析 viewmodel-savedstate 擴展組件瞭
implementation 'androidx.appcompat:appcompat:1.0.0'

def fragment_version = "1.1.0"
def activity_version = "1.0.0"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"

def lifecycle_version = "2.5.1"

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"

基礎使用

定義

class MainViewModel: ViewModel(){ ... }
// or 
class MainViewModel(application: Application): AndroidViewModel(application){
    val data: String = ""
    
    fun requestData(){
        data = "xxx"
    }
}

在 MainVieModel 中可以定義 UI 界面中需要的數據(對象、LiveData、Flow 等等)和方法,在 Activity 真正銷毀前 ViewModel 中的數據不會丟失。

Activity 中獲取

val vm = ViewModelProvider(this).get(MainViewModel::class.java)
// or
// 引入 activity-ktx 庫可以這樣初始化 ViewModel
val vm by viewModels<MainViewModel>()

// 通過 vm 可以調用其中的方法、獲取其中的數據
vm.requestData()
Log.e(TAG, vm.data)

Fragment 中獲取

val vm = ViewModelProvider(this).get(MainViewModel::class.java)
// or 
// 獲取和 Activity 共享的 ViewModel 也就是同一個 ViewModel 對象
val vm = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)

引入 fragment-ktx 可以這樣初始化

val vm = viewModels<MainViewModel>()
// or 效果同上
val vm = activityViewModels<MainViewModel>()

前置知識

ViewModel 的使用非常簡單,也很容易理解,就是一個生命周期長於 Activity 的對象,區別在於不會造成內存泄漏。ViewModel 不是魔法,站在開發者的角度在 ViewModel 沒有問世之前橫豎屏切換需要保存狀態數據的需求通常都是通過 onSaveInstanceState、onRestoreInstanceState 來實現。

onSaveInstanceState、onRestoreInstanceState

關於這兩個方法這裡就簡單概述一下:onSaveInstanceState 用於在 Activity 橫豎屏切換(意外銷毀)前保存數據,而 onRestoreInstanceState 是用於 Activity 橫豎屏切換(重建)後獲取保存的數據;

onSaveInstanceState 調用流程

由於是在 Activity 銷毀前觸發,那麼直接來 ActivityThread 中找到 performPauseActivity 方法:

ActivityThread.java

private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, String reason, PendingTransactionActions pendingActions) {
    // ...
    if (shouldSaveState) {
        callActivityOnSaveInstanceState(r);
    }
    // ...
}

private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
    // ...
    // 這裡通過 ActivityClientRecord 獲取到 activity
    // state 是 Bundle 對象,後面要保存的數據就放在 state 中
    mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
    // ...
}

這裡有 ActivityThread 調用到瞭 Instrumentation 中,繼續看源碼:

Instrumentation.java

public void callActivityOnSaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
    activity.performSaveInstanceState(outState);
}

根據傳入的 activity 調用其 performSaveInstanceState 方法:

Activity.java

final void performSaveInstanceState(@NonNull Bundle outState) {
    onSaveInstanceState(outState);
}

總結一下,onSaveInstanceState 中我們將數據存儲在 Bundle 對象中,而這個 Bundle 對象是存儲在 ActivityClientRecord 中。

onRestoreInstanceState 調用流程

看完瞭 onSaveInstanceState 的調用流程,那麼 onRestoreInstanceState 的流程就來簡單說說,由於在 onStart 後發生回調,所以直接去看 ActivityThread 中的源碼:

ActivityThread.java

public void handleStartActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, ActivityOptions activityOptions) {
    // ...
    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
    // ...
}

可以看出這裡從 ActivityClientRecord 中取出瞭 activity 和 state 進行傳毒,後面就和 onSaveInstanceState 調用流程一樣瞭,源碼比較簡單就不貼瞭。

onRetainCustomNonConfigurationInstance、getLastCustomNonConfigurationInstance

除瞭 onSaveInstanceState 和 onRestoreInstanceState,在 Activity 中還有一組方法可以實現類似的功能,就是 onRetainCustomNonConfigurationInstance 和 getLastCustomNonConfigurationInstance,前者即保存數據,後者即獲取保存的數據;

簡單使用

override fun onRetainCustomNonConfigurationInstance(): Any? {
    val data = SaveStateData()
    return data
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // 獲取保存的數據
    val data = getLastCustomNonConfigurationInstance() as SaveStateData
}

和 onSaveInstanceState 使用的區別在於 onSaveInstanceState 隻能在其參數中的 Bundle 對象中寫入數據,而 onRetainCustomNonConfigurationInstance 返回的類型是 Any(Java Object)不限制數據類型。老樣子看一下這組方法的源碼調用流程。

onRetainCustomNonConfigurationInstance

onRetainCustomNonConfigurationInstance 是在 ComponentActivity 中定義的,默認實現返回 null,其在 onRetainNonConfigurationInstance 方法中被調用:

ComponentActivity.java

public Object onRetainCustomNonConfigurationInstance() {
    // ComponentActivity 中默認返回 null
    return null;
}

public final Object onRetainNonConfigurationInstance() {
    // 保存在瞭 custom 變量中
    Object custom = onRetainCustomNonConfigurationInstance();
    // 這裡已經出現 ViewModel 相關的源碼瞭,這裡先按下不表
    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        return null;
    }
    // 新建 NonConfigurationInstances 對象
    NonConfigurationInstances nci = new NonConfigurationInstances();
    // custom 賦值給瞭 NonConfigurationInstances 對象
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

從 ComponentActivity 的這部分源碼中可以看出保存的數據最終放在瞭 NonConfigurationInstances 對象的 custom 屬性中;接著找 onRetainNonConfigurationInstance 的定義,在 Activity 中:

Activity.java

public Object onRetainNonConfigurationInstance() {
    // 默認返回 null
    return null;
}

NonConfigurationInstances retainNonConfigurationInstances() {
    // ComponentActivity 中返回的 NonConfigurationInstances 對象
    Object activity = onRetainNonConfigurationInstance();
    // ...
    // 註意 這裡有新建另一個 NonConfigurationInstances 對象
    NonConfigurationInstances nci = new NonConfigurationInstances();
    // ComponentActivity 中返回的 NonConfigurationInstances 對象
    // 存儲到瞭新的 NonConfigurationInstances 中的 activity 屬性中
    nci.activity = activity;
    // ...
    return nci;
}

在 Activity 類中相當於做瞭一層套娃,又新建瞭一個 NonConfigurationInstances 對象,將 ComponentActivity 中返回的 NonConfigurationInstances 對象存瞭進去;

其實源碼看到這裡就可以瞭,不過本著刨根問底的原則,我們接著再看一下 NonConfigurationInstances 到底存在瞭哪裡?在 ActivityThread.java 中找到瞭調用 retainNonConfigurationInstances 的地方:

ActivityThread.java

void performDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
    // ...
    // 這個 r 是參數中的 ActivityClientRecord
    r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
}

和 onSaveInstanceState 一樣存儲在瞭 ActivityClientRecord 中,隻不過換瞭一個屬性罷瞭。

getLastCustomNonConfigurationInstance

看完瞭存儲的流程,簡單來看看取數據的流程。既然存的時候套娃瞭一下 NonConfigurationInstances,那取數據的時候肯定也需要套娃:

ComponentActivity.java

public Object getLastCustomNonConfigurationInstance() {
    // 通過 getLastNonConfigurationInstance 獲取 NonConfigurationInstances
    NonConfigurationInstances nc = (NonConfigurationInstances)
            getLastNonConfigurationInstance();
    // 返回 custom
    return nc != null ? nc.custom : null;
}

那麼在 Activity 中肯定還需要取一次 ActivityClientRecord 中的 NonConfigurationInstances:

Activity.java

NonConfigurationInstances mLastNonConfigurationInstances;

public Object getLastNonConfigurationInstance() {
    // 返回其 activity 字段
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

// mLastNonConfigurationInstances 賦值在 attach 方法中
final void attach(Context context, /*參數太多 省略瞭*/ NonConfigurationInstances lastNonConfigurationInstances) {
    // ...
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    // ...
}

可以看出在 Activity attach 方法中就已經拿到瞭套娃後的 NonConfigurationInstances 對象,我們都知道 Activity attach 方法是在 ActivityThread 的 performLaunchActivity 中調用,看一下源碼:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // ...
    // 參數太多 省略瞭
    // 可以看到是從 ActivityClientRecord 中取出傳入的
    activity.attach(appContext, r.lastNonConfigurationInstancesn);
    // ...
}

小節總結

兩種方式都是將數據保存到瞭 ActivityClientRecord 中,不同的是前者限制瞭 Bundle 類型,後者不限制類型(ViewModel 采用的就是後者這組方法實現),不過後者已經在源碼中被標記瞭刪除,並不影響使用,標記刪除是為瞭讓開發者們利用 ViewModel 來接管這種需求。下面我們就正式進入 ViewModel 源碼。

源碼分析

前置知識有點長,不過也幾乎把 ViewModel 的原理說透瞭,ViewModel 的保存、恢復是利用瞭系統提供的方法,不過還有些細節還需要在源碼中探索,比如:如何實現 Activity/Fragment 共享 ViewModel?接下來就來深入 ViewModel 源碼。

創建

先來以 Activity 中創建 ViewModel 的這段代碼入手:

val vm by viewModels<MainViewModel>()

查看 viewModels 源碼:

// 這是一個 ComponentActivity 的擴展方法
@MainThread // 在主線程中使用
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    // 從命名也可以看出是一個工廠模式,默認是 null
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    // 默認 factoryProducer 為 null
    // 返回的是 AndroidViewModelFactory
    val factoryPromise = factoryProducer ?: {
        val application = application ?: throw IllegalArgumentException(
            "ViewModel can be accessed only when Activity is attached"
        )
        AndroidViewModelFactory.getInstance(application)
    }
    
    // 返回瞭一個 ViewModelLazy 對象,將 viewModelStore、factoryProducer 傳入
    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

到這裡先暫停看一下 AndroidViewModelFactory 是如何初始化的,以及 viewModelStore 是什麼東東:

ViewModelProvider.kt

private var sInstance: AndroidViewModelFactory? = null

@JvmStatic
public fun getInstance(application: Application): AndroidViewModelFactory {
    if (sInstance == null) {
        sInstance = AndroidViewModelFactory(application)
    }
    return sInstance!!
}

是一個單例模式,直接對 AndroidViewModelFactory 進行實例化,再來看看 mViewModelStore

ComponentActivity.java

// 都是定義在 ComponentActivity 中的變量,默認 null
private ViewModelStore mViewModelStore;

public ViewModelStore getViewModelStore() {
    // ...
    if (mViewModelStore == null) { // 第一次啟動 activity 為 null
        // 獲取保存的數據
        NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
         // 優先從保存的數據中獲取
        if (nc != null) {
            mViewModelStore = nc.viewModelStore;
        }
        // 默認返回 ViewModelStore
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

ViewModelStore 內部僅僅是管理一個 Map<String, ViewModel>,用於緩存、清理創建的 ViewModel。

回過頭接著看擴展方法 viewModels 返回的 ViewModelLazy:

public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
    private val viewModelClass: KClass<VM>, // ViewModel 的 class
    private val storeProducer: () -> ViewModelStore, // 默認是 ViewModelStore
    private val factoryProducer: () -> ViewModelProvider.Factory, // 這裡就是 mDefaultFactory
    private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty } // 
) : Lazy<VM> { // 註意這裡返回的 Lazy,延遲初始化
    private var cached: VM? = null

    override val value: VM
        get() { // 由於返回的是 Lazy,也就是當使用 ViewModel 時才會調用 get
            val viewModel = cached
            return if (viewModel == null) { // 第一次調用是 null,進入 if
                val factory = factoryProducer() // mDefaultFactory
                val store = storeProducer() // ViewModelStore
                ViewModelProvider( // 生成 ViewModelProvider 對象
                    store,
                    factory,
                    extrasProducer()
                ).get(viewModelClass.java).also { // 調用其 get 方法獲取 ViewModel
                    cached = it  // 保存到 cached 變量
                } 
            } else {
                viewModel
            }
        }
    
    override fun isInitialized(): Boolean = cached != null
}

這裡又出現瞭一個陌生的對象 CreationExtras,其內部也是一個 map,可以理解為一個鍵值對存儲對象,隻不過他的 Key 是一個特殊類型。

接著查看 ViewModelProvider 的 get 方法是如何創建 ViewModel 的:

// 存儲ViewModel的key的前綴
internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"

public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
    val canonicalName = modelClass.canonicalName
        ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
    // 調用重載方法,拼接 key 傳入
    // 當前key即為:androidx.lifecycle.ViewModelProvider.DefaultKey$com.xxx.MainViewModel
    return get("$DEFAULT_KEY:$canonicalName", modelClass)
}

@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    val viewModel = store[key] // 優先從 ViewModelStroe 中獲取緩存
    if (modelClass.isInstance(viewModel)) { // 如果類型相同 直接返回
        // 這裡我們的 factory 是 AndroidViewModelFactory 所以不會走這行代碼
        (factory as? OnRequeryFactory)?.onRequery(viewModel)
        return viewModel as T
    }
    // ...
    // 這裡的 defaultCreationExtras 是上一步驟中的 CreationExtras,默認值為 CreationExtras.Empty
    // MutableCreationExtras 包裝一層就是將 defaultCreationExtras 中所有的鍵值對都copy一份
    val extras = MutableCreationExtras(defaultCreationExtras)
    // 將當前 ViewModel 的 key 存儲進去
    extras[VIEW_MODEL_KEY] = key
    
    return try {
        // 優先調用雙參數方法
        factory.create(modelClass, extras)
    } catch (e: AbstractMethodError) {
        // 調用雙參數方法發生異常再調用單參數方法
        factory.create(modelClass)
    }.also { 
        // 獲取到 ViewModel 後存儲到 viewModelStore 中
        // 再提一嘴 viewModelStore 是在 ComponentActivity 中定義 
        store.put(key, it) 
    }
}

終於到瞭創建 ViewModel 的部分瞭,直接去看 AndroidViewModelFactory 的 create 方法:

ViewModelProvider.kt

override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
    // application 不為 null 調用單參數方法
    // 在新建 AndroidViewModelFactory 已經傳入瞭 application,一般情況不為 null
    return if (application != null) { 
        create(modelClass)
    } else { 
        // application 如果為 null,則會從傳入的 extras 中嘗試獲取
        val application = extras[APPLICATION_KEY]
        if (application != null) {
            // 這個 create 也是雙參數,但不是遞歸,第二個參數是 application,源碼貼在下面
            create(modelClass, application)
        } else {
            // 如果 application 仍然為 null,且 ViewModel 類型為 AndroidViewModel 則拋異常
            if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
                throw IllegalArgumentException(...)
            }
            // 類型不是 AndroidViewModel 則根據 class 創建
            // 註意這裡調用的 super.create 是父類方法
            // 父類方法直接根據 modelClass.newInstance() 創建,就一行就不貼源碼瞭
            super.create(modelClass)
        }
    }
}

override fun <T : ViewModel> create(modelClass: Class<T>): T {
    return if (application == null) { // application 為 null 直接拋異常
        throw UnsupportedOperationException(...)
    } else {
        // 調用下面的雙參數方法
        create(modelClass, application)
    }
}

private fun <T : ViewModel> create(modelClass: Class<T>, app: Application): T {
    // 如果是 AndroidViewModel 類型則獲取帶 application 的構造參數創建
    return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
        modelClass.getConstructor(Application::class.java).newInstance(app)
    } else {
        // 直接調用父類 create 方法通過 modelClass.newInstance() 創建
        super.create(modelClass)
    }
}

至此 Activity 中的 ViewModel 創建過程源碼就全部分析完瞭,總結一下:Activity 中的 ViewModel 創建都是通過單例工廠 AndroidViewModelFactory 的 create 方法中反射創建,在調用 create 創建前會生成字符串 key,創建完成後會將 key 和 vm 對象存儲到 ViewModelStore 中,後續獲取將優先從 ViewModelStore 緩存中獲取。

ViewModelStore 是定義在 ComponentActivity 中的,ViewModel 生命周期 “長於” Activity 的原理跟這個 ViewModelStore 脫不瞭幹系。

恢復

前面小節提過,ViewModel 的恢復利用的是 onRetainNonConfigurationInstance 方法,ViewModelStore 又是定義在 ComponentActivity 中,那麼直接去看 ComponentActivity 這部分的源碼:

ComponentActivity.java

public final Object onRetainNonConfigurationInstance() {
    // 留給開發者使用的字段
    Object custom = onRetainCustomNonConfigurationInstance();
    
    // 獲取當前 Activity 的 mViewModelStore
    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) { 
        // 如果為 null 則嘗試獲取上一次保存的數據
        NonConfigurationInstances nc =  (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // 獲取上一次存儲的 viewModelStore
            viewModelStore = nc.viewModelStore;
        }
    }
    // ...
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom; // 開發者用的字段
    nci.viewModelStore = viewModelStore; // 保存 viewModelStore 的字段
    return nci;
}

再來看一看 ViewModelStore 的獲取方法:

public ViewModelStore getViewModelStore() {
    // ...
    if (mViewModelStore == null) {
        NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
         // 優先從保存的數據中獲取 viewModelStore
        if (nc != null) {
            mViewModelStore = nc.viewModelStore;
        }
        // 獲取不到才會新建
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

Activity 獲取 mViewModelStore 時優先從 getLastNonConfigurationInstance 獲取到 NonConfigurationInstances 對象,再從其中獲取 viewModelStore,這樣在當前 Activity 作用域中創建過的 ViewModel 都存儲在 ViewModelStore 中,當需要再次使用時走 ViewModel 創建流程會直接從 ViewModelStore 中返回。

最後

再瞭解瞭 onRetainNonConfigurationInstance 這組方法之後再來探究 ViewModel 的恢復原理就很簡單瞭,onRetainNonConfigurationInstance 也被標記為瞭刪除,google 也希望開發者盡可能的使用 ViewModel 來保存數據(臨時數據)。

onRetainNonConfigurationInstance 雖然被標記為刪除,但仍然可以正常使用,相比於 onSaveInstanceState 沒有瞭數據類型限制,但並不意味著我們可以隨意存儲,比較大的數據還是應該考慮持久化存儲。

以上就是源碼解析Android Jetpack組件之ViewModel的使用的詳細內容,更多關於Android Jetpack ViewModel的資料請關註WalkonNet其它相關文章!

推薦閱讀: