探祕Android黑科技-ViewModel如何無視ConfigurationChange

背景介紹

ViewModel是Android Architecture Components中提供的一個組件,其做用是爲UI組件提供須要展示的數據內容,幫助開發者更優雅簡單高效的實現多個組件間的數據共享。其中值得注意的是ViewModel的生命週期,以往咱們將UI展現的數據直接緩存在對應的UI組件中,遇到ConfigurationChange等事件UI組件從新建立,咱們緩存的數據也隨之銷燬。但ViewModel能夠在內存中長期被持有而不受ConfigurationChange的影響,直到相關聯的UI組件真正銷燬的時候ViewModel才隨之釋放。下面是來自官方的對ViewModel生命週期的描述圖:html

本文主要探討文章標題提到的問題,關於ViewModel的細節介紹能夠參閱官方文檔java

開門見山

若是堆代碼不如本身去閱讀源碼,那樣反而來的更清晰直觀。因此後面的部分不會打源碼戰術,可是也有一些我的分析的過程描述,若是隻關心黑科技是啥,能夠直接跳到最後結論部分。android

由官方的demo咱們能夠看到使用ViewModel的簡單方法以下:git

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}
複製代碼

咱們須要實現一個派生自ViewModel的類。接着在須要的UI組件中經過ViewModelProviders.of().get()來獲取咱們定義的ViewModel實例。demo中看到真正使用時每每在ViewModel中以LiveData來封裝須要UI組件展示的數據,因而在獲取咱們定義的ViewModel實例後即可以經過其內部的LiveData來添加觀察者,進而實現數據變動時能及時的通知到UI組件。關於LiveData的細節介紹能夠參閱官方文檔。 咱們仍是回到ViewModel。就以demo中看到的ViewModelProviders爲切入點。github

我畫了一張類圖,打算嘗試按照本身的理解將他們的關係講清楚,那也就基本瞭解爲何ViewModel對ConfigurationChange免疫了。也爲你們查閱源碼或者提出異議提供一個思路。緩存

查看原圖

圖中虛線箭頭表示依賴關係,箭頭尾部對應的類依賴箭頭頭部對應的類。 中間的實線箭頭表示類的組合關係,箭頭尾部對應的類中包含箭頭頭部對應的類的實例。app

類圖簡述

  • ViewModelProvider依賴ViewModelProviders類來建立ViewModelProvider實例。
  • ViewModelProvider中有一個ViewModelStore類型的成員變量mViewModelStore。ViewModelProviders在建立ViewModelProvider的過程當中須要依賴ViewModelStores來建立ViewModelStore實例並賦值給ViewModelProvider的成員變量mViewModelStore。
  • ViewModelStore依賴ViewModelStores類來建立ViewModelStore實例。
  • ViewModelStores建立ViewModelStore的過程當中須要依賴HolderFragment的支持,建立好的ViewModelStore保存在HolderFragment的成員變量mViewModelStore中,經過getViewModelStore()方法返回其內部保存的ViewModelStore類型對象給ViewModelProvider。
  • 最終經過ViewModelProvider類的get()方法獲取ViewModel實例。

ViewModelProvider實例

ViewModel對ConfigurationChange免疫,換個角度能夠理解爲在不一樣的UI組件實例中獲取的ViewModel實例相同。 根據demo得知,咱們經過ViewModelProviders.of().get()這樣一個鏈式調用來獲取ViewModel實例。 因此要得到一個ViewModel分爲兩個步驟:async

第一步,調用ViewModelProviders.of()方法獲取一個ViewModelProvider實例。這正是圖中左上部分表達的信息:ide

ViewModelProvider依賴於ViewModelProviders,ViewModelProvider是經過ViewModelProviders提供的一系列of方法構造出來的。經過ViewModelProvider的構造方法的定義能夠看到在構造時須要傳入ViewModelStore和Factory。ViewModelStore是真正緩存ViewModel的地方,Factory則用於在緩存未命中時建立ViewModel。fetch

第二步,經過ViewModelProvider.get()方法獲取ViewModel實例,這從圖中的ViewModelProvider和ViewModelStore的關係能有所體現:

ViewModelProvider內部有一個ViewModelStore類型的成員變量mViewModelStore和一個用於建立ViewModel的Factory成員變量mFactory。他們是在建立對象時經過構造方法傳入的。調用ViewModelProvider.get()方法實際上是經過內部的ViewModelStore.get()方法來獲取ViewModel,而若是獲取爲空,則經過mFactory構建一個新的ViewModel。

ViewModelStore中保存一個Map鍵值對,這裏是真正緩存ViewModel的地方,因此ViewModelProvider.get()最終會調到ViewModelStore的這個Map中來找緩存的ViewModel。 由於最終是從ViewModelStore的Map鍵值對中查找緩存的ViewModel,因此要保證ViewModel的惟一性其實就是保證ViewModelStore的惟一性。 前面提到ViewModelProvider中的mViewModelStore是先經過ViewModelStores構造好,而後利用其構造方法傳遞到ViewModelProvider中的。因此接下來的重點就是ViewModelStores如何構造一個ViewModelStore。

ViewModelStore實例

ViewModelStore經過ViewModelStores構造,其構造過程須要圖中的ViewModelStores和HolderFragment類一塊兒完成。 從圖中能夠看到ViewModelStore是經過ViewModelStores.of()方法來構造的,ViewModelStores.of()方法內部會直接調用HolderFragment的靜態方法holderFragmentFor(),該靜態方法返回一個HolderFragment對象,這個HolderFragment對象就是一個派生自Fragment的類,只不過它不包含任何View樹結構。接着調用它的getViewModelStore()方法返回HolderFragment的成員變量mViewModelStore,這個mViewModelStore是做爲成員變量在HolderFragment類加載時直接new出來的。因此一個HolderFragment對象必定對應惟一一個ViewModelStore對象實例。同時這個mViewModelStore實例會做爲前面構造ViewModelProvider時的參數傳進去。也就是說ViewModelProvider和HolderFragment中的mViewModelStore對象指向的是同一個實例,這個實例是先在HolderFragment中構造初始化以後傳遞到ViewModelProvider中去的。 既然ViewModelStore是經過HolderFragment建立出來的,一個HolderFragment實例內部惟一對應一個ViewModelStore實例,那如今的問題就變成如何保證HolderFragment的惟一性了。 到這裏,黑科技終於登場。其實就是利用Fragment的setRetainInstance()方法。經過在實例化HolderFragment的時候調用該方法,並傳入true做爲參數。HolderFragment就能夠在其宿主的Fragment或Activity重走生命週期時在內存中持久化其自身實例,即此時Fragment對應的View會從View樹中移除,可是Fragment實例自己不會銷燬,在下一次宿主Fragment或Activity重走生命週期時會複用內存中持久化的Fragment實例。以此來達到HolderFragment實例的惟一性。關於setRetainInstance()方法的使用詳情能夠參閱官方文檔

總結

  • ViewModel要無視ConfigurationChange事件,就得保證在ConfigurationChange事件發生時UI組件生命週期重走過程當中獲取到的ViewModel實例與以前的是同一個實例。
  • ViewModel最終會緩存在ViewModelStore的Map中,而一個HolderFragment實例與ViewModelStore實例一一對應。因此問題最終回到如何保證HolderFragment的惟一性。
  • HolderFragment派生自Fragment。利用Fragment的setRetainInstance()方法保證HolderFragment在ConfigurationChange事件發生時能夠複用內存中同一個HolderFragment實例,進而保證HolderFragment的惟一性,達到無視ConfigurationChange的目的。
相關文章
相關標籤/搜索