ViewModel

隨著 Android 架構(gòu)的演進(jìn),從 MVC 到 MVP 再到現(xiàn)在的 MVVM,項(xiàng)目的結(jié)構(gòu)越來(lái)越清晰,耦合度也越來(lái)越低,本質(zhì)上講就是對(duì) UI 和邏輯的分離,而在這一分離的過(guò)程中,MVP 的 presenter 和 MVVM 中的ViewModel 都起了很重要的作用,Presenter 不必多說(shuō),就是一個(gè)類(lèi)封裝了我們的邏輯代碼,并加了一些回調(diào)。我們要講的是 ViewModel 如何創(chuàng)建使用,如何和頁(yè)面生命周期綁定以及如何在配置更改時(shí)恢復(fù)數(shù)據(jù)。

1.what?


ViewModelLiveData 是組成 Jetpack 的一部分,在 MVVM 架構(gòu)中充當(dāng)著相當(dāng)重要的角色。

  • ViewModel 旨在以注重生命周期的方式存儲(chǔ)和管理界面相關(guān)的數(shù)據(jù),讓數(shù)據(jù)可在發(fā)生屏幕旋轉(zhuǎn)等配置更改后繼續(xù)留存,所以 ViewModel 在 MVVM 中擔(dān)當(dāng)?shù)氖且粋€(gè)數(shù)據(jù)持有者的角色,為 Activity 、Fragment 存儲(chǔ)數(shù)據(jù),在配置更改的時(shí)候恢復(fù)數(shù)據(jù),其次因?yàn)?ViewModel 存儲(chǔ)了數(shù)據(jù),所以 ViewModel 可以在當(dāng)前 Activity的 Fragment 中實(shí)現(xiàn)數(shù)據(jù)共享。

  • LiveData 作為ViewModel的好基友,是一個(gè)可以感知 Activity 、Fragment生命周期的數(shù)據(jù)容器。當(dāng) LiveData所持有的數(shù)據(jù)改變時(shí),它會(huì)通知相應(yīng)的組件進(jìn)行更新。同時(shí),LiveData 持有界面代碼 Lifecycle的引用,這意味著它會(huì)在界面代碼(LifecycleOwner)的生命周期處于 started 或 resumed 時(shí)作出相應(yīng)更新,而在 LifecycleOwner 被銷(xiāo)毀時(shí)停止更新。它的優(yōu)點(diǎn):不用手動(dòng)控制生命周期,不用擔(dān)心內(nèi)存泄露,數(shù)據(jù)變化時(shí)會(huì)收到通知.

2.How?


2.1 基本用法

我們先看看 ViewModel 是怎么使用的(雖然大家都比較熟悉)。首先,我們創(chuàng)建一個(gè)ViewModel子類(lèi),類(lèi)里面有一個(gè) LiveData 對(duì)象:

class MyViewModel : ViewModel() {
    val mNameLiveData = MutableLiveData()
}

然后我們?cè)?Activity 里面使用它:

class MainActivity : AppCompatActivity(R.layout.activity_main) {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val textView = findViewById<TextView>(R.id.textView)
        val viewModel by viewModel<MyViewModel>()
        viewModel.mNameLiveData.observe(this, Observer {
            textView.text = it
        })
    }
}

例子非常簡(jiǎn)單,這里就不過(guò)多的介紹。需要提一句的是,在最新的ViewModel中,以前通過(guò)ViewModelProviders.of 方法來(lái)獲取 ViewModel 已經(jīng)廢棄了,現(xiàn)在我們是通過(guò) ViewModelProvider Factory 創(chuàng)建 ViewModel 對(duì)象,因此需要往 ViewModelProider 構(gòu)造方法里面?zhèn)鬟f一個(gè)工廠類(lèi)對(duì)象,如下:

class MyViewModelFactory : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return modelClass.getConstructor().newInstance()
    }
}

當(dāng)然,我們可以不帶 Factory 對(duì)象。那么加入 Factory 對(duì)象之后,相比較于以前有什么好處呢?加了 Factory 之后,我們可以定義構(gòu)造方法帶參的 ViewModel。比如說(shuō),我們的一個(gè) ViewModel 構(gòu)造方法需要帶一個(gè) id 參數(shù),那么我們可以在 Factory 的 create 方法里面創(chuàng)建對(duì)象直接帶進(jìn)去。

我們還可以根據(jù)提供的參數(shù)使用 lazyMap 或類(lèi)似的 lazy init。當(dāng)參數(shù)是字符串或其他不可變類(lèi)時(shí),很容易將它們用作映射的鍵,以獲取與提供的參數(shù)相對(duì)應(yīng)的 LiveData。

class Books(val names: List<String>)

data class Parameters(val namePrefix: String = "")/*只為示范*/

class GetBooksCase {
   fun loadBooks(parameters: Parameters, onLoad: (Books) -> Unit) { /* Implementation detail */
   }
}
class BooksViewModel(val getBooksCase: GetBooksCase) : ViewModel() {
    private val booksLiveData: Map<Parameters, LiveData<Books>> = lazyMap { parameters ->
        val liveData = MutableLiveData<Books>()
        getBooksCase.loadBooks(parameters) { 
          liveData.value = it 
        }
        return@lazyMap liveData
    }
    fun books(parameters: Parameters): LiveData<Books> = booksLiveData.getValue(parameters)
}
fun <K, V> lazyMap(initializer: (K) -> V): Map<K, V> {
    val map = mutableMapOf<K, V>()
    return map.withDefault { key ->
        val newValue = initializer(key)
        map[key] = newValue
        return@withDefault newValue
    }
}

在上面使用 lazy map 的時(shí)候,我們只使用 map 來(lái)傳遞參數(shù),但在許多情況下,ViewModel 的一個(gè)實(shí)例將始終具有相同的參數(shù)。這時(shí)候最好將參數(shù)傳遞給構(gòu)造函數(shù),并在構(gòu)造函數(shù)中使用 lazy load 或 start load。

class BooksViewModel(val getBooksCase: GetBooksCase, parameters: Parameters) : ViewModel() {
    private val booksLiveData: LiveData<Books> by lazy {
        val liveData = MutableLiveData<Books>()
        getBooksCase.loadBooks(parameters) { 
          liveData.value = it 
        }
        return@lazy liveData
    }
    fun books(parameters: Parameters): LiveData<Books> = booksLiveData
}
class BooksViewModelFactory(val getBooksCase: GetBooksCase, val parameters: Parameters) :
    ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return BooksViewModel(getBooksCase, parameters) as T
    }
}

切記,我們不要自己創(chuàng)建 ViewModel 對(duì)象,因?yàn)樽约簞?chuàng)建的對(duì)象不能保存因?yàn)榕渲酶膶?dǎo)致 Activity 重建的數(shù)據(jù),從而完美避開(kāi)了 ViewModel 的優(yōu)點(diǎn)。

2.2 DataBinding 中使用 ViewModel 和 LiveData

ViewModel、LiveData 與 DataBinding 并不是什么新功能,但非常好用(但因?yàn)橐恍?DataBinding 出了問(wèn)題全局報(bào)錯(cuò)不好定位的原因,被眾大佬詬病甚至棄用)。ViewModel 通常都包含一些 LiveData,而 LiveData 意味著可以被監(jiān)聽(tīng)。在 XML 布局文件中使用ViewModel時(shí),調(diào)用 binding.setLifecycleOwner(this) 方法,然后將 ViewModel 傳遞給 binding 對(duì)象,就可以將 LiveData 與 Data Binding 結(jié)合起來(lái):

class MainActivity : AppCompatActivity() {

    private val myViewModel: MyViewModel by lazy {
        ViewModelProvider(
            this,
            MyViewModelFactory()
        )[MyViewModel::class.java]
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: MainActivityBinding = DataBindingUtil.setContentView(this, R.layout.main_activity)
        binding.lifecycleOwner = this
        // 將 ViewModel 傳遞給 binding
        binding.viewmodel = myViewModel
    }
}

XML 布局文件中使用 ViewModel:

<layout>
    <data>
        <variable
            name="viewModel"
            type="com.gxj.test.MyViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.text}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

注意,這里的 viewModel.text 可以是 String 類(lèi)型,也可以是 LiveData。如果它是 LiveData,那么 UI 將根據(jù) LiveData 值的改變自動(dòng)刷新。

2.3 ViewMode 與 Kotlin 協(xié)程: viewModelScope

通常情況下,我們使用回調(diào) (Callback) 處理異步調(diào)用,這種方式在邏輯比較復(fù)雜時(shí),會(huì)導(dǎo)致回調(diào)嵌套地獄,代碼也變得難以理解。而協(xié)程同樣適用于處理異步調(diào)用,它能夠讓邏輯變得簡(jiǎn)單的同時(shí),也確保了操作不會(huì)阻塞主線程。一段簡(jiǎn)單的協(xié)程代碼,真實(shí)情景下不要使用:

GlobalScope.launch {   
    longRunningFunction()    
    longRunningFunction1()
}

這段代碼只啟動(dòng)了一個(gè)協(xié)程,但我們?cè)谡鎸?shí)的使用環(huán)境下很容易創(chuàng)建出許多協(xié)程,這就難免會(huì)導(dǎo)致有些協(xié)程的狀態(tài)無(wú)法被跟蹤。如果這些協(xié)程中剛好有您想要停止的任務(wù)時(shí),就會(huì)導(dǎo)致任務(wù)泄漏。而為了防止任務(wù)泄漏,需要將協(xié)程加入到一個(gè) CoroutineScope 中,它可以持續(xù)跟蹤協(xié)程的執(zhí)行,也可以被取消。當(dāng) CoroutineScope 被取消時(shí),它所跟蹤的所有協(xié)程都會(huì)被取消。上面的代碼中,我使用了GlobalScope,正如我們不推薦隨意使用全局變量一樣,這種方式通常不推薦使用。所以,如果想要使用協(xié)程,要么限定一個(gè)作用域 (scope),要么獲得一個(gè)作用域的訪問(wèn)權(quán)限。而在 ViewModel 中,我們可以使用 viewModelScope 來(lái)管理協(xié)程的作用域,它是一個(gè)ViewModel 的 kotlin 擴(kuò)展屬性,當(dāng) ViewModel 被銷(xiāo)毀時(shí),通常都會(huì)有一些與其相關(guān)的操作也應(yīng)當(dāng)被停止。

舉個(gè)栗子,當(dāng)我們要加載一個(gè)文件的時(shí)候: 既要做到不能在執(zhí)行時(shí)阻塞主線程,又要求在退出相關(guān)界面時(shí)停止加載。當(dāng)使用協(xié)程進(jìn)行耗時(shí)操作時(shí),就應(yīng)當(dāng)使用 viewModelScope, ,它能在 ViewModel 銷(xiāo)毀時(shí) (onCleared()方法調(diào)用時(shí)) 退出。這樣我們就可以在 ViewModel 的 viewModelScope 中啟動(dòng)各種協(xié)程,而不用擔(dān)心任務(wù)泄漏。

示例如下:

class MyViewModel() : ViewModel() {

    fun initialize() {
        viewModelScope.launch {
            processLoadFile()
        }
    }

    suspend fun processLoadFile() = withContext(Dispatchers.Default) {
        // 在這里做耗時(shí)操作
    }
}

2.4 ViewModel 的 Saved State

  1. onSaveInstanceState 帶來(lái)的挑戰(zhàn)

我們知道 Activity 和 Fragment 通常會(huì)在下面三種情況下被銷(xiāo)毀:

  1. 從當(dāng)前界面永久離開(kāi): 用戶(hù)導(dǎo)航至其他界面或直接關(guān)閉 Activity (通過(guò)點(diǎn)擊返回按鈕或執(zhí)行的操作調(diào)用了 finish() 方法)。對(duì)應(yīng) Activity 實(shí)例被永久關(guān)閉;
  2. Activity 配置被改變: 例如,旋轉(zhuǎn)屏幕等操作,會(huì)使 Activity 需要立即重建;
  3. 應(yīng)用在后臺(tái)時(shí),其進(jìn)程被系統(tǒng)殺死: 這種情況發(fā)生在設(shè)備剩余運(yùn)行內(nèi)存不足,系統(tǒng)又亟須釋放一些內(nèi)存的時(shí)候。當(dāng)進(jìn)程在后臺(tái)被殺死后,用戶(hù)又返回該應(yīng)用時(shí),Activity 也需要被重建。

在后兩種情況中,我們通常都希望重建 Activity。ViewModel 會(huì)處理第二種情況,因?yàn)樵谶@種情況下 ViewModel 沒(méi)有被銷(xiāo)毀;而在第三種情況下, ViewModel 被銷(xiāo)毀了。所以一旦出現(xiàn)了第三種情況,便需要在 Activity 的 onSaveInstanceState 相關(guān)回調(diào)中保存和恢復(fù) ViewModel 中的數(shù)據(jù)。

  1. Saved State 模塊

ViewModel 保存和恢復(fù)的數(shù)據(jù)范圍僅限于配置更改導(dǎo)致的重建,并不支持因?yàn)橘Y源限制導(dǎo)致 Activity 重建的情況。但是,大家對(duì)此的呼聲卻從來(lái)沒(méi)有停歇,Google 因此新增了一個(gè) SavedStateHandle 類(lèi),用來(lái)滿(mǎn)足我們的要求。該模塊會(huì)在應(yīng)用進(jìn)程被殺死時(shí)恢復(fù) ViewModel 的數(shù)據(jù)。在免除了與 Activity 繁瑣的數(shù)據(jù)交換后,ViewModel 也真正意義上的做到了管理和持有所有自己的數(shù)據(jù)。

SavedStateHandle 和 Bundle 一樣,以鍵值對(duì)形式存儲(chǔ)數(shù)據(jù),它包含在 ViewModel 中,并且可以在應(yīng)用處于后臺(tái)時(shí)進(jìn)程被殺死的情況下幸存下來(lái)。諸如用戶(hù) id 等需要在 onSaveInstanceState 時(shí)得到保存下來(lái)的數(shù)據(jù),現(xiàn)在都可以存在 SavedStateHandle 中。

  1. 使用Save State模塊
  • 添加依賴(lài)

    SaveStateHandle 目前在一個(gè)獨(dú)立的模塊中,所以需要在依賴(lài)中添加:

implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0"

  • 修改調(diào)用 ViewModelProvider 的方式

創(chuàng)建一個(gè) SaveStateHandle 的ViewModel,在 onCreate() 方法中將 ViewModelProvider 的調(diào)用修改為:

class MainActivity : AppCompatActivity(R.layout.activity_main) {

    val viewModel = ViewModelProvider(
        this,
        SavedStateViewModelFactory(application, this)
    ).get(MyViewModel::class.java)
}

創(chuàng)建 ViewModel 的類(lèi)是 ViewModelFactory,而創(chuàng)建包含 SaveStateHandle 的 ViewModel 的工廠類(lèi)是 SavedStateViewModelFactory。通過(guò)此工廠創(chuàng)建的 ViewModel 將持有一個(gè)基于傳入 Activity 或 Fragment 的 SaveStateHandle。如果我們的 ViewModel 構(gòu)造方法只帶一個(gè) SavedStateHandle 參數(shù)或者帶有一個(gè)Application 參數(shù)和 SavedStateHandle 參數(shù),可以直接使用 SavedStateViewModelFactory。如果構(gòu)造方法還帶有其他的參數(shù),此時(shí)需要繼承 AbstractSavedStateViewModelFactory 實(shí)現(xiàn)我們自己的工廠類(lèi)。在使用AbstractSavedStateViewModelFactory 時(shí),我們需要注意一點(diǎn):create 方法帶的 SavedStateHandle 參數(shù)一定傳遞到 ViewModel 里面去。

  • 調(diào)用SaveStateHandle

舉一個(gè)保存用戶(hù) ID 的例:

class MyViewModel(state :SavedStateHandle) :ViewModel() {
    // 將Key聲明為常量
    companion object {
        private val USER_KEY = "userId"
    }

    private val savedStateHandle = state

    fun saveCurrentUser(userId: String) {
        // 存儲(chǔ) userId 對(duì)應(yīng)的數(shù)據(jù)
        savedStateHandle.set(USER_KEY, userId)
    }

    fun getCurrentUser(): String {
        // 從 saveStateHandle 中取出當(dāng)前 userId
        return savedStateHandle.get(USER_KEY)?: ""
    }
}

保存: saveNewUser 方法展示了使用鍵值對(duì)的形式保存 USER_KEY 和 userId 到 SaveStateHandle 的例子。每當(dāng)數(shù)據(jù)更新時(shí),要保存新的數(shù)據(jù)到 SavedStateHandle;

獲取: 調(diào)用 savedStateHandle.get(USER_KEY) 方法獲取被保存的 userId。

現(xiàn)在,無(wú)論是第二還是第三種情況下,SavedStateHandle 都可以恢復(fù)界面數(shù)據(jù)。

3.why?


3.1 ViewModel 是如何創(chuàng)建的?

ViewModelProivder 有很多構(gòu)造方法,不過(guò)最終都調(diào)到同一個(gè)地方:

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}

這個(gè)方法中,mFactory 就是我們預(yù)期的工廠類(lèi),用來(lái)創(chuàng)建 ViewModel 對(duì)象;mViewModelStore 是一個(gè)什么東西呢?這個(gè)很好理解,mViewModelStore 就是用來(lái)存儲(chǔ)的 ViewModel 對(duì)象的,比如同一個(gè) Activity 的onCreate() 方法可能會(huì)多次回調(diào),我們?cè)?onCreate()方法初始化ViewModel,但是不可能每次 onCreate() 回調(diào)都會(huì)創(chuàng)建新的 ViewModel 對(duì)象,所以需要有一個(gè)東西用來(lái)存儲(chǔ)的我們之前創(chuàng)建過(guò)的 ViewModel,這個(gè)就是ViewModelStore 的作用。而 ViewModel 生命周期比 Activity 的生命周期長(zhǎng)也是因?yàn)檫@個(gè)類(lèi)。

那么 mViewModelStore 對(duì)象是從哪里傳過(guò)來(lái),我們清楚的記得構(gòu)造方法里面我們并沒(méi)有傳這個(gè)變量。

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

我們可以看到從 ViewModelStoreOwner 獲取的,代碼如下

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

ViewModelStoreOwner是一個(gè)接口,那么哪些類(lèi)是這個(gè)借口的實(shí)現(xiàn)類(lèi)呢?如你所料,我們熟悉的ComponentActivity 和 Fragment 都實(shí)現(xiàn)了這個(gè)接口。

我們?cè)賮?lái)看一下 get 方法,因?yàn)檎嬲@取 ViewModel 對(duì)象就是通過(guò)這個(gè)方法的。

 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");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

這個(gè)get方法沒(méi)有做什么事情,構(gòu)造了一個(gè)默認(rèn)的 key,然后調(diào)用另一個(gè) get 方法。代碼如下:

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
}

這個(gè) get 方法總的來(lái)說(shuō),主要分為以下2個(gè)過(guò)程:

  1. 先通過(guò) key 從 ViewModelStore (緩存)獲取 ViewModel 對(duì)象,如果緩存中存在,直接返回。Activity 經(jīng)過(guò)橫屏重建之后,返回 ViewMode 的對(duì)象就是這里返回。
  2. 如果緩存不存在,那么通過(guò) Factory 創(chuàng)建一個(gè)對(duì)象,然后放在緩存中,最后返回。

3.2.ViewModel 如何做到配置更改時(shí)依然可以恢復(fù)數(shù)據(jù)?

在上面講SaveState的時(shí)候,提到了Activity 和 Fragment 被銷(xiāo)毀的三種情況,在這三種情況下的 ViewModel 的生命周期可以看下圖:

image.png

從這張圖里面,我們可以看出,ViewModel 的生命周期要比Activity長(zhǎng)一點(diǎn)。ViewModel 存在的時(shí)間范圍是獲取 ViewModel 時(shí)傳遞給 ViewModelProvider 的 Lifecycle。在此期間ViewModel 將一直留在內(nèi)存中,直到限定其存在時(shí)間范圍的 Lifecycle 永久消失:對(duì)于 Activity,是在 Activity 完成時(shí);而對(duì)于 Fragment,是在 Fragment 分離時(shí)。

在前面的概述中,我們已經(jīng)知道 ViewModel 的生命周期要比 Activity 長(zhǎng)一點(diǎn)。那 ViewModel 是怎么做到的呢?對(duì)于這個(gè)問(wèn)題,我猜大家首先想到的是緩存,并且這個(gè)緩存是被 static 關(guān)鍵字修飾的。正常來(lái)說(shuō),這個(gè)實(shí)現(xiàn)方案是沒(méi)有問(wèn)題的,我們也能找到具體的例子,比如 Eventbus 就是這么實(shí)現(xiàn)的。

那么在 ViewModel 中,這個(gè)是怎么實(shí)現(xiàn)的呢?我們都知道 ViewModel 是從一個(gè) ViewModelStore 緩存里面的獲取,我們看了 ViewModelStore 的源碼,發(fā)現(xiàn)它的內(nèi)部并沒(méi)有通過(guò)靜態(tài)緩存實(shí)現(xiàn)。那么它是怎么實(shí)現(xiàn)Activity 在 onDestroy 之后(重建),還繼續(xù)保留已有的對(duì)象呢?

這個(gè)我們可以從 ComponentActivity 的 getViewModelStore 方法去尋找答案:

public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
}

getViewModeStrore 方法的目的很簡(jiǎn)單,就是獲取一個(gè) ViewModelStrore 對(duì)象。那么這個(gè) ViewModelStore 可以從哪里獲取呢?我們從上面的代碼中可以找到兩個(gè)地方:

  1. 從 NonConfigurationInstances 獲取。
  2. 創(chuàng)建一個(gè)新的 ViewModelStore 對(duì)象。

第二點(diǎn)我們不用看,關(guān)鍵是 NonConfigurationInstances。NonConfigurationInstances 這是什么東西?

NonConfigurationInstances 其實(shí)就是一個(gè) Wrapper,用來(lái)包裝一下因?yàn)椴皇芘渲酶挠绊懙臄?shù)據(jù),包括我們非常熟悉的 Fragment,比如說(shuō),一個(gè) Activity 上面有一個(gè) Fragment,旋轉(zhuǎn)了屏幕導(dǎo)致 Activity 重新創(chuàng)建,此時(shí)Activity 跟之前的不是同一個(gè)對(duì)象,但是 Fragment 卻是同一個(gè),這就是通過(guò) NonConfigurationInstances 實(shí)現(xiàn)的。也就是說(shuō)在 getViewModelStore 方法里面,從 NonConfigurationInstances 獲取的 ViewModelStore 對(duì)象其實(shí)就是上一個(gè) Activity 的。同時(shí),我們還可以在 ComponentActivity 里面看到一段代碼:

getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {
               if (event == Lifecycle.Event.ON_STOP) {
                   Window window = getWindow();
                   final View decor = window != null ? window.peekDecorView() : null;
                   if (decor != null) {
                      decor.cancelPendingInputEvents();
                   }
             }
        }
});

從上面的代碼中,我們可以到如果 Activity 是因?yàn)榕渲酶膶?dǎo)致 onDestroy 方法的回調(diào),并不會(huì)清空ViewModelStore 里面的內(nèi)容,這就能保證當(dāng) Activity 因?yàn)榕渲酶膶?dǎo)致重建重新創(chuàng)建的 ViewModel 對(duì)象跟之前創(chuàng)建的對(duì)象是同一個(gè)。反之,如果 Activity 是正常銷(xiāo)毀的話,則不會(huì)保存之前創(chuàng)建的 ViewModel 對(duì)象,對(duì)應(yīng)的是 ViewModelStore 的 clear 方法調(diào)用。其實(shí)這個(gè) clear 方法還跟 kotlin 里面的協(xié)程有關(guān),這里就不過(guò)多解釋了,有興趣的同學(xué)可以看看 ViewModel.viewModelScope。

現(xiàn)在我們來(lái)看一下 NonConfigurationInstances 為啥能保證 Activity 重建前后,ViewModeStore 是同一個(gè)對(duì)象呢?我們直接從ActivityThread的performDestroyActivity方法去尋找答案。我們知道,performDestroyActivity 方法最后會(huì)回調(diào)到 Activity 的 onDestroy 方法,我們可以通過(guò)這個(gè)方法可以找到ActivtyThread 在 Activity onDestroy 之前做了保存操作。

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
    // ······
    performPauseActivityIfNeeded(r, "destroy");
    // Activity的onStop方法回調(diào)
    if (!r.stopped) {
        callActivityOnStop(r, false /* saveState */, "destroy");
    }
    if (getNonConfigInstance) {
        // ······
        // retainNonConfigurationInstances方法的作用就是創(chuàng)建一個(gè)對(duì)象
        r.lastNonConfigurationInstances= r.activity.retainNonConfigurationInstances();
        // ······
    }
    // ······
    // Activity的onDestroy方法回調(diào)
    mInstrumentation.callActivityOnDestroy(r.activity);
    // ······
    return r;
}

從上面的代碼中看出,在 Activity 的 onStop 和 onDestroy之間,會(huì)回調(diào) retainNonConfigurationInstances方法,同時(shí)記錄到ActivityClientRecord中去。這里retainNonConfigurationInstances 方法返回的對(duì)象就是我們之前看到的 NonConfigurationInstances 對(duì)象。

那么又在哪里恢復(fù)已保存的 NonConfigurationInstances 對(duì)象呢?這個(gè)可以從 performLaunchActivity 方法找到答案。performLaunchActivity 方法的作用就是啟動(dòng)一個(gè) Activity,Activity 重建肯定會(huì)調(diào)用這個(gè)方法。在performLaunchActivity方法里面,調(diào)用了Activity的attach方法,在這個(gè)方法,Google將已有的NonConfigurationInstances 賦值給了新的 Activity 對(duì)象。

到這里,我們就知道為啥 NonConfigurationInstances 能保證 ViewModelStore 在 Activity 重建前后是同一個(gè)對(duì)象,同時(shí)也知道為啥 ViewModel 的生命周期比 Activity 的生命周期要長(zhǎng)一點(diǎn)。

總結(jié)

在本篇文章中我講述了什么是 ViewModel,如何傳遞參數(shù)到 ViewModel 中去,以及 ViewModel一些使用場(chǎng)景,也相信大家對(duì) ViewModel 都能立馬上手了。接著我們又從源碼的角度分析了 ViewModel 是如何創(chuàng)建的,是如何和 Activity 的生命周期綁定在一起的,這讓我們能夠更深入的理解 ViewModel,最后講述了 ViewModel 在配置更改以及銷(xiāo)毀重建時(shí)是如何保存和恢復(fù)數(shù)據(jù)的。ViewModel 作為數(shù)據(jù)的處理和分發(fā)者,在 MVVM 盛行的當(dāng)下承扮演著越來(lái)越重要的角色,讓我們把ViewModel深入提煉并應(yīng)用到實(shí)際項(xiàng)目中吧!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容