Android Jetpack之ViewModel

奇舞移動發表於2018-11-13

概述

ViewModel仍然是Model的範疇,是資料物件的載體,但是多了與檢視(View)生命週期的繫結關係。可以簡單理解為帶有生命週期的資料物件。可在Activity, Fragment中使用,保證其在生命週期內的唯一性和一致性,不受配置的更改(例如螢幕旋轉)。

使用場景

  • 某頁面螢幕旋轉導致Activity/ Fragment銷燬重建資料不能儲存。
    你可能會說我用onSaveInstanceState儲存下來,然後某onCreate, onRestoreInstanceState還原啊。onSaveInstanceState設計用來儲存那些小的與 UI 相關的並且序列化或者反序列化不復雜的資料。如果被序列化的物件複雜的話,序列化會消耗大量的記憶體。由於這一過程發生在主執行緒的配置更改期間,它需要快速處理才不會丟幀和引起視覺上的卡頓。
  • 仍然是螢幕旋轉問題,一般頁面重建後需要重新請求資料,如果能快取下來是不是就能減少一次請求?又或者靜置後臺一小會回來用onSaveInstance實現就略麻煩了。
  • Activity+ Fragments時單個Fragment希望主動拿到Activity的資料完成渲染。此場景換成Activity/ Fragment+自定義View也是一樣的。
    你可能又說我讓Activity實現個介面,Fragment中將context物件轉成介面不就能拿到Activity的資料了嘛。
    我想說還得定義介面,有沒有不那麼麻煩的方法;另外如果不恰當地呼叫,Activity被銷燬了還會出現空指標啊。
  • 想象某Activity+ Fragments需要Fragment之間通訊,甚至某Fragment的更改影響到其他Fragment。
    估計大家的第一反應是讓Activity當中介或者用Eventbus這種去通知吧

    總之,學習了ViewModel你會發現一種新思路去解決這些問題。既能保持資料在生命週期內的唯一性又不需要考慮因為非同步操作導致的空指標。

ViewModel生命週期

下圖可以看出,ViewModel的生命週期和Activity/ Fragment一樣甚至更長,因為如果發生螢幕旋轉,ViewModel並沒有跟著銷燬,直到最終的onDestroy。

圖1.ViewModel生命週期

ViewModel的使用

1.gradle依賴

dependencies {
    ...
    implementation 'android.arch.lifecycle:extensions:1.1.1'
}
複製程式碼

2.ViewModel的宣告 千萬不能持有Context的引用,否則會引起記憶體洩漏,如果實在需要Context,可以繼承AndroidViewModel,通過getApplication()獲取Application。

public class CustomModel extends ViewModel {
      public CustomObj obj;//業務內需要使用的物件
}
複製程式碼

3.ViewModel的使用

public class MyActivity extends Activity {
      public void myFunction() {
              //也可以ViewModelProviders.of(MyActivity.this).get("key", CustomModel.class);
              CustomModel customModel = ViewModelProviders.of(MyActivity.this).get(CustomModel.class);
              customModel.obj...//自己操作該物件
      }
}
複製程式碼

4.ViewModel與LiveData結合

public class CustomModel extends ViewModel {
      //業務內需要使用的變數
      public MutableLiveData<string> value = new MutableLiveData&lt;&gt;();
}
複製程式碼
public class MyFragment1 extends Fragment {
      public void myFunction() {
              CustomModel customModel = ViewModelProviders.of(getActivity()).get(CustomModel.class);
              customModel.value.observe(MyFragment.this, new Observer<string>() {
                    @Override
                    public void onChanged(String value) {
                        //自己操作該引數,如textView.setText(value)
                    }
              });
      }
}
複製程式碼
public class MyFragment2 extends Fragment {
      public void myFunction() {
              CustomModel customModel = ViewModelProviders.of(getActivity()).get(CustomModel.class);
              customModel.value.setValue("fragment2 changed the value");//注意postValue
      }
}
複製程式碼

這樣兩個Fragment就可以通訊了,LiveData的引入也能實時監聽資料的變化反應在介面上。

好了,ViewModel的基本用法掌握了,可以開心地在簡歷上寫熟練掌握Android Jetpack之ViewModel。但是如果在面試官問“你知道它底層是怎麼實現的嗎”時,你不想一臉懵逼的說我猜。。。可能。。。大概。。。是這樣吧,反正作為一個熱愛鑽研的攻城師的我是看了一遍原始碼的。

ViewModel實現原理

猜猜怎麼實現

1.既然可以儲存多個不同的ViewModel,那麼它們是怎麼儲存的?
2.ViewModel資料儲存在哪裡?Application級別還是Activity/ Fragment級別?
3.怎麼保證ViewModel在配置更改(如螢幕旋轉)時不被銷燬?
4.怎麼保證Activity/ Fragment onDestroy時回收?
5.傳遞的是ViewModel.class,怎麼獲取例項?
6.AndroidViewModel持有的Application是在哪裡設定進去的?

原理分析

下面會一步步分析實現原理,著急的同學請跳到最後直接看答案。 說明:androidx轉換前後對於ViewModel儲存方式有變化,這裡我們分別聊下這兩種實現:

androidx轉換前

圖2.ViewModel的實現類圖
(圖片來源:blog.csdn.net/zhuzp_blog/…)

  • ViewModel, AndroidViewModel是我們可以繼承的類,後者持有Application物件。
  • ViewModelStore是儲存ViewModel的類,用HashMap儲存。
  • ViewModelProviders提供獲取ViewModelProvider的方法,支援activity, Fragment兩種傳值
  • ViewModelProvider組合VIewModelStore和Factory,可根據ViewModel的名稱獲取其例項。Factory有預設實現,也可自定義,例項的首次建立就是通過Factory反射得到的。
  • HolderFragment是關鍵邏輯,實現了ViewModelStore的儲存功能。本質上是Activity/ Fragment中插入的空Fragment,能夠在配置發生更改時不被銷燬。
ViewModelProviders.of(activity/ fragment)

圖3.獲取ViewModelProvider
圖4.檢查並得到Application

checkXXX方法校驗合法性同時獲取上層Context,最終獲得Application物件;不自定義Factory的情況下會使用系統的AndroidViewModelFactory,getInstance物件得到static物件,它的作用是在ViewModel例項不存在時建立一個新的。
圖5.利用AndroidViewModelFactory建立ViewModel例項

回頭看圖3ViewModelProvider構造方法(具體見下面圖12)的第一個引數是ViewModelStores.of(fragment),原來是為了得到ViewModelStore,我們先看下ViewModelStore的定義。簡單來說它是ViewModel的儲存工廠,底層用HashMap實現,還對外提供了clear方法。
圖6.ViewModelStore類

明白了ViewModelStore是個關鍵的儲存類,那麼它怎麼獲得的?到底存在哪裡呢?
圖7.ViewModelStores類

先來看下ViewModelStoreOwner是什麼,顧名思義是儲存ViewModelStore的類,正常Activity/ Fragment一般不儲存,那麼往下看holderFragmentFor(fragment)就是關鍵實現了。圖8顯示該方法是static型別,目的是得到HolderFragment。看來ViewModelStore是儲存在HolderFragment中了,那麼HolderFragment又是什麼東東??
圖9.獲得HolderFragment

圖9.HolderFragment類

原來HolderFragment是一個Fragment,儲存著ViewModelStore,在onDestroy的時候呼叫ViewModelStore.clear方法。注意構造方法中setRetainInstance(true), 這個方法能讓Activity在配置更改重建時Fragment不被銷燬(具體功能自行查閱),也就是這個功能成為螢幕旋轉ViewModel仍然保留的功臣。
圖10.HolderFragmentManager類

HolderFragmentManager有倆HashMap分別存Activity與插入的HolderFragment,Fragment和插入的子HolderFragment的鍵值對,並能監聽Activity/ Fragment的生命週期,在destroy時去掉快取HolderFragment。下圖11仍然是HolderFragmentManager的方法,描述瞭如何建立HolderFragment,如果查詢HolderFragment。
圖11.HolderFragmentManager類2

回過頭和圖9中建立HolderFragment聯絡在一起看,原來是先在當前Activity/ Fragment中查詢是否存在HolderFragment,不存在則在HolderFragmentManager的HashMap中找,實在沒有就建立一個放在Activity/ Fragment中。得到HolderFragment就能得到ViewModelStore,接著就有了ViewModelProvider,那麼接下來怎麼得到ViewModel例項呢?

ViewModelProvider.get(ViewModel.class)

圖12.獲取ViewModel例項

獲取例項的方法很簡單,從ViewModelStore中查詢,有則直接返回,沒有則通過反射建立一個(mFactory的建立過程見AndroidViewModelFactory)。至此,我們就找到了我們的主角ViewModel。
總結思想就是在Activity/ Fragment中插入一個唯一的HolderFragment來儲存ViewModelStore,HolderFragment沒有檢視資訊,不加入到view container中,通過setRetainInstance(true)保證配置更改不被銷燬,但是在onDestroy時手動呼叫ViewModelStore.clear方法釋放掉Map中所有的ViewModel物件。由於沒有主動從載體中清除,HolderFragment本身就能和外層載體生命週期一樣長,同時setRetainInstance(true)保證了即使螢幕旋轉後載體重建也能不被銷燬,也可以說它的生命週期比載體還長,從而保證了HolderFragment中的ViewModelStore在整個載體存續期間的唯一性和一致性,間接保證了ViewModel的唯一。
Amazing, 想明白了嗎?這麼看來onRetainInstance挺好用的,但是總覺得承載體都沒有了,真的不會記憶體洩漏嘛?首先官方的東西一般不會洩漏的,另外我簡單分析了一下:
1.沒有View試圖(其實有也沒關係,配置更改時View會被釋放重建的)
2.ViewModel中不持有Context物件
3.在配置更改時承載體消失只是臨時的,很快又重建並attach,如果是長時間地承載體消失,那麼它也會慢慢被垃圾回收的。

androidx轉換後

1.不變:ViewModel, AndroidViewModel, ViewModelStore, ViewModelProvider, Factory
2.變化:ViewModelStores廢棄,HolderFragment刪除。Activity/ Fragment實現了ViewModelStoreOwner類, 也就是ViewModelStore存在於Activity/ Fragment載體自身。
原始碼的差異點是獲取ViewModelStore的方式如圖13。不同於HolderFragment統一處理,這種實現中Activity/ Fragment儲存ViewModelStore的方式不相同,下面我們分開說明。

圖13.獲取ViewModelStore

FragmentActivity中ViewModelStore

關鍵點是onRetainNonConfigurationInstance(), getLastNonConfigurationInstance(), 至於實現原理自行查閱。

圖14.FragmentActivity之getViewModelStore()

如果載體真的發生配置更改,ViewModelStore則先儲存到NonConfigurationInstances中,然後在OnCreate時再進行還原如圖15,圖14中再獲取一遍也沒什麼。留意下面fragments.restoreAllState則是對Fragment中ViewModelStore的還原,我們後面再說。這裡的NonConfigurationInstances在哪裡設定進去的呢?
圖15.onCreate中還原ViewModelStore

圖16.onRetainNonConfigurationInstance

上圖16展示了NonConfigurationInstances的儲存,不同於onSaveInstance資料格式是Bundle,這個方法允許儲存物件。儲存內容分三塊使用者自定義物件,ViewModelStore物件和FragmentManagerNonConfig物件。對於使用者自定義部分看註釋發現通過重寫onRetainCustomNonConfigurationInstance()實現,ViewModelStore是我們今天的主角,最後一個FragmentManagerNonConfig則是Fragment中資料的儲存,包含了Fragment中ViewModelStore邏輯。留意下mFragments.retainNestedNonConfig()是儲存Fragment的資料,我們後面說。
那麼ViewModelStore是怎麼在Activity.onDestroy()時銷燬呢,原始碼中說只有因為配置更改導致Activity destroy時ViewModelStore是不回撥clear方法的。
圖17.Activity之onDestroy

onRetainNonConfigurationInstance的呼叫時機是介於onStop和onDestroy之間,在onSaveInstance之後。
總結,ViewModelStore存在於FragmentActivity中,只是在配置更改導致Activity銷燬前時通過onRetainNonConfigurationInstance將資料物件儲存,並在重建時通過getLastNonConfigurationInstance將資料物件還原回來,從而保證了Activity內ViewModelStore的唯一性和一致性。

Fragment中ViewModelStore

Fragment中實現保留機制大體相同,只是資料儲存在FragmentManagerNonConfig中,下圖18說明了當配置更改時Activity內需要儲存的資料,包括三部分:不被銷燬的Fragment集合(通過setRetainInstance(true)),所有子Fragment中需要儲存的資料以及所有的Fragment中的ViewModelStore

圖18.FragmentManagerNonConfig類

圖19.Fragment之getViewModelStore

上圖中直接返回mViewModelStore物件,比FragmentActivity少了一步還原過程,由於它沒有getLastNonConfigurationInstance,所以只能在FragmentActivity的onCreate中主動呼叫FragmentController.restoreAllState來還原Fragment相關資料(入口見圖16),下圖20簡單得展示了FragmentManager.restoreAllState方法還原ViewModelStore的過程
圖20.FragmentManager中還原ViewModelStore

Fragment中ViewModelStore的還原過程瞭解了,但是儲存又是什麼時候完成的呢? 還記得圖16中FragmentActivity的OnRetainNonConfigurationInstance()時呼叫FragmentController.retainNestedNonConfig()嗎,這個函式的作用就是重建Activity時重建或者還原Fragment。圖21描述了將fragment.mRetaining設定為true的過程(注意mRetaining和mRetainInstance不一樣哦,前者只是在當前配置更改時不銷燬),根據註釋可以看出mSavedNonConfig是在這個之前通過saveNonConfig設定的,其實際呼叫順序是 FragmentActivity.onSaveInstanceState()-> FragmentManager.saveAllState()-> FragmentManager.saveNonConfig()
圖21.FragmentManager.retainNonConfig

圖22.FragmentManager.saveNonConfig

上圖儲存的資料包括三部分:通過setRetainInstance(true)需要被儲存的Fragment例項,子Fragment中的儲存資料以及所有Fragment中不為空的ViewModelStore物件。它們最終會在FragmentActivity.OnRetainNonConfigurationInstance時作為和FragmentActivity中的ViewModelStore同級別的物件一起被快取起來,直到Activity重建onCreate中被還原。
最後onDestroy時ViewModelStore.clear邏輯與FragmentActivity實現大同小異如圖23.
圖23.Fragment之onDestroy

說到這裡,Activity/ Fragment中ViewModelStore的儲存和還原就介紹完了。暈乎乎的,怎麼就冒出來一個onRetainNonConfigurationInstance,其實這個方法很早就存在,可以快取任何你想快取的物件,哪怕是activity例項,AnsycTask都行,這個方法用於配置更改導致Activity重建後的物件的還原。

原理總結

在android和androidx下ViewModel的儲存方式完全不同,但是暴露給我們使用的方法卻是不變的,這也說明架構的用心。當前恰逢android和androidx的交接,具體以哪個為準我不確定。 上面的問題1, 5, 6的實現方式一致,在此先給出答案:
1.多個ViewModel儲存在ViewModelStore中,本質上是一個Map儲存的鍵值對。key為ViewModel的名字或者get方法時自己設定的key。
5.先查詢ViewModelStore中是否含有該例項,有則直接返回,沒有則通過反射的方式得到例項,並加入ViewModelStore中。
6.ViewModelProviders.of(activity/fragment),通過activity/ fragment拿到Application,當然AndroidViewModel也是通過反射拿到初始例項的,和ViewModel不同的是它需要將application傳過去。

  • android的support包:
    2.ViewModel本質上屬於Activity/ Fragment級別,通過FragmentManager/ ChildFragmentManager插入一個沒有View的Fragment-HolderFragment,ViewModelStore即儲存在HolderFragment中。
    3.在onCreate時setRetainInstance(true), 保證螢幕旋轉時fragment不被銷燬。
    4.HolderFragment在onDestroy時回撥ViewModelStore.clear,迴圈呼叫viewModel.onCleared.
  • androidx包:
    2.ViewModel仍然屬於Activity/ Fragment級別,只是ViewModelStore是分別儲存在FragmentActivity和Fragment中
    3.在Activity銷燬重建過程是通過onRetainNonConfigurationInstance(), getLastNonConfigurationInstance()儲存和還原的ViewModelStore物件的;
    Fragment略微複雜,在呼叫FragmentActivity onStop和onDestroy之間onSaveInstanceState之後saveAllState的saveNonConfig時快取FragmentManagerNonConfig物件,裡面儲存的資料包括不被銷燬的fragment集合,迴圈巢狀的子Fragment的FragmentManagerNonConfig集合以及所有Fragment的ViewModelStore集合,它們在FragmentActivity重建onCreate時還原每個Fragment的ViewModelStore物件,可以說ViewModelStore的儲存介質是正常被銷燬的,只是ViewModelStore資料被儲存並在特定場景下還原回來,儲存和還原都是由FragmentActivity主動觸發的;
    3.如果是由配置更改導致Activity/ Fragment onDestroy則不清除ViewModelStore的資料,反之則清除。
    總之,不管採用不銷燬ViewModelStore的儲存介質,還是採用儲存再還原資料的方式,都能保證在當前作用域下資料的唯一性和完整性。程式碼說起來複雜,但是設計思想值得我們去消化學習。

關注微信公眾號,最新技術乾貨實時推送

image

相關文章