Android Architecture Component之ViewModel原始碼分析
前言
知識準備
- 重要知識介紹(後面用到)
/**
* Control whether a fragment instance is retained across Activity
* re-creation (such as from a configuration change). This can only
* be used with fragments not in the back stack. If set, the fragment
* lifecycle will be slightly different when an activity is recreated:
* <ul>
* <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still
* will be, because the fragment is being detached from its current activity).
* <li> {@link #onCreate(Bundle)} will not be called since the fragment
* is not being re-created.
* <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b>
* still be called.
* </ul>
*/
public void setRetainInstance(boolean retain) {
mRetainInstance = retain;
}
這個方法是fragment的方法,根據官方註釋,我們知道一旦我們設定setRetainInstance(true),意味著當我們旋轉螢幕的時候,activity重繪,fragment不會重繪,它將會保留。也就意味著Fragment的onCreate和onDestory方法都不會呼叫,這樣的特性很多被用來用狀態儲存,資料恢復。具體是怎麼樣的過程,請看fragment原始碼分析
ViewModel是什麼?
- 定義
ViewModel類旨在以一種有生命週期的方式儲存和管理與UI相關的資料。 ViewModel類允許資料在螢幕旋轉等配置變化後存活
解決的問題
Android框架控制著UI控制器(Activity或者Fragment)的生命週期,它就有可能決定銷燬或者重建我們的UI控制,以響應完全不受控制的某些使用者操作或裝置事件。那麼就會出現幾個問題。
如果被銷燬那麼儲存在其中的所有瞬態UI相關資料都將丟失.
例如:您的應用可能會在其中一個活動中包含使用者列表。 當針對配置更改重新建立活動時,新活動必須重新獲取使用者列表。 對於簡單的資料,活動可以使用onSaveInstanceState()方法並從onCreate()的包中恢復其資料,但是這種方法僅適用於可以序列化然後反序列化的少量資料,而不適用於潛在的大量資料像使用者或點陣圖的列表。
UI控制獲取資料的時候經常進行非同步呼叫,可能需要一些時間來返回。 UI控制器需要管理這些呼叫,並確保系統在銷燬後清理它們以避免潛在的記憶體洩漏。
這種管理需要大量的維護,並且在為配置更改而重新建立物件的情況下,由於物件可能不得不重新發出已經做出的呼叫(可能會重新請求資料),所以浪費資源.
UI控制器(如活動和片段)主要用於顯示UI資料,對使用者操作做出反應,或處理作業系統通訊(如許可權請求)。
如果要求UI控制器也負責從資料庫或網路載入資料,從而增加了該類的膨脹。 為UI控制器分配過多的責任可能會導致一個類嘗試單獨處理應用程式的所有工作,而不是將工作委託給其他類。 通過這種方式給UI控制器分配過多的責任也使測試變得更加困難。
用法簡介
- 依賴
compile "android.arch.lifecycle:runtime:1.0.3"
compile "android.arch.lifecycle:extensions:1.0.0-rc1"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0-rc1"
api用法
建立ViewModel
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 asyncronous operation to fetch users. } }
ViewModel物件在配置更改期間自動保留,以便它們儲存的資料立即可用於下一個activity或fragment
- acitivity或fragment獲取資料
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 }); } }
如果Activity重新繪製,但是ViewModel例項並不會被銷燬,而是重新從ViewModel中獲取資料,等到Activity銷燬(退出或者被系統殺死),框架會呼叫onCleared()方法,以便清理資源;
警告:viewModel絕不能引用檢視, Lifecycle或任何可能持有對活動上下文的引用的類。
- acitivity或fragment獲取資料
ViewModel可以包含lifeCycleObservers.例如liveData。但是ViewModel絕不能觀察生命週期感知的可觀察物件(LiveData物件的更改),如果ViewModel需要上下文引用,請用AndroidModelView,它含有Application的物件.
ViewModel的生命週期
從圖中可以看出我們發現,當activity因螢幕旋轉而銷燬,但是ViewModel一直存在,也就是這個物件一直都在(框架核心,怎麼實現原始碼分析會講到),直到finished才呼叫clear清除記憶體。
Fragment之間共享資料
一個Activity中兩個Fragment進行通訊是很常見的,想象一下,主 - 從Fragmetn的一種常見情況,其中有一個片段,使用者從列表中選擇一個專案,另一個片段顯示所選專案的內容。這種情況從來都不是微不足道的,因為這兩個片段都需要定義一些介面描述,而所有者活動必須將兩者聯絡在一起。 此外,這兩個片段必須處理其他片段尚未建立或可見的場景。
對於這個常見的痛點,可以用ViewModel來解決,這些Fragment可以使用Activity的Scope來共享一個ViewModel,來處理通訊。
public class SharedViewModel extends ViewModel { private final MutableLiveData<Item> selected = new MutableLiveData<Item>(); public void select(Item item) { selected.setValue(item); } public LiveData<Item> getSelected() { return selected; } } public class MasterFragment extends Fragment { private SharedViewModel model; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class); itemSelector.setOnClickListener(item -> { model.select(item); }); } } public class DetailFragment extends Fragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class); model.getSelected().observe(this, { item -> // Update the UI. }); } }
注意:我們在獲取ViewModelProvider時,這兩個是使用的是getActivity(),而不是當前的Fragment(後面會分析原始碼)。所以兩個Fragment用的是同一個物件。
好處如下:
- 對於Activity來說,他不知道這個交流,也不用管理
- 對於Framgent,除了共同擁有這個SharedViewModel,他們之間不需要了解彼此。如果一個Framgnet消失不會影響另一個Framgent.
- 每個Framgent都有自己獨立的生命週期,不互相影響。
原始碼分析
我們先找到程式的入口
ViewModelProviders.of(this).get(MyViewModel.class)
- 先看of方法
return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory)
返回一個ViewModelProvider,需要兩個引數ViewModelStore(存放ViewModel的倉庫),Factory(用工廠模式建立我們的ViewModel物件)。假如說我們的Activity重新繪製了,那麼這裡的ViewModelProvider就是不同的例項。這個類主要的作用就是獲取我們ViewModel物件。 - 看一下of()方法
ViewModelStores.of(activity)
ViewModelStores是幹什麼的呢?提供一些靜態方法獲取所傳入activity的ViewModeStore.
@MainThread
public static ViewModelStore of(@NonNull FragmentActivity activity) {
return holderFragmentFor(activity).getViewModelStore();
}
holderFragmentFor
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public static HolderFragment holderFragmentFor(FragmentActivity activity) { return sHolderFragmentManager.holderFragmentFor(activity); }
- 我們看到是靜態方法,這個方法不是ViewModelStore,而是
HolderFragment的一個靜態方法,返回一個HolderFragment物件. - 然後回到3處呼叫getViewModelStore,返回ViewModelStore。所以說HolderFragment中有一個ViewModelStore。對應關係是一個HolderFrament含有一個ViewModelStore(存放ViewModel的倉庫),一個ViewModelStore存放著多個ViewModel。
- HolderFragmentManager是屬於HolderFramgent的靜態內部類
- 我們看到是靜態方法,這個方法不是ViewModelStore,而是
在(iii)處呼叫了sHolderFragmentManager.holderFragmentFor
HolderFragment holderFragmentFor(FragmentActivity activity) { FragmentManager fm = activity.getSupportFragmentManager(); HolderFragment holder = findHolderFragment(fm); if (holder != null) { return holder; } holder = mNotCommittedActivityHolders.get(activity); if (holder != null) { return holder; } holder = createHolderFragment(fm); mNotCommittedActivityHolders.put(activity, holder); return holder; }
首先在在Activity的supportFragmentManager中的查詢,有的話就會返回
private static HolderFragment findHolderFragment(FragmentManager manager) { if (manager.isDestroyed()) { throw new IllegalStateException("Can't access ViewModels from onDestroy"); } Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG); if (fragmentByTag != null && !(fragmentByTag instanceof HolderFragment)) { throw new IllegalStateException("Unexpected " + "fragment instance was returned by HOLDER_TAG"); } return (HolderFragment) fragmentByTag; }
- 沒有的話在我們存放的集合中查詢有的話就返回,在HolderFragmentManager集合如下
private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
可以看出多個Activity可能對應一個HolderFrament
- 沒有的話在我們存放的集合中查詢有的話就返回,在HolderFragmentManager集合如下
沒有的話就建立並放入我們的mNotCommittedActivityHolders。
private static HolderFragment createHolderFragment(FragmentManager fragmentManager) { HolderFragment holder = new HolderFragment(); fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss(); return holder; }
建立Fragment物件,這是一個無ui的Fragment,==主要作用就是用ViewModelStore儲存我們的ViewModel,並在Fragement的OnDestory呼叫ViewModelStore的clear釋放所有記憶體==。
有人要說,fragment物件還是要重建啊,那麼ViewModel也要重建啊,因為它屬於Fragment,這就用到了文章開篇講到了setRetainInstance方法。看下原始碼
public HolderFragment() { setRetainInstance(true); }
在我們初始化的時候會呼叫setRetainInstance,這就解決這個問題了。
- 拿到HolderFragment物件,回到(ii)處呼叫getViewModelStore,拿到ViewModelStore物件。
- 先看of方法
我們拿到ViweModelStore回到(1)處,我們就去拿我們的ViewModel。看原始碼
@NonNull 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); }
看get(DEFAULT_KEY + “:” + canonicalName, modelClass)
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { ViewModel viewModel = mViewModelStore.get(key); if (modelClass.isInstance(viewModel)) { //noinspection unchecked return (T) viewModel; } else { //noinspection StatementWithEmptyBody if (viewModel != null) { // TODO: log a warning. } } viewModel = mFactory.create(modelClass); mViewModelStore.put(key, viewModel); //noinspection unchecked return (T) viewModel; }
首先是我們從mViewModelStore取出我們想要的ViewModel.
有的話就返回- 沒有的話就利用工廠模式反射生產我們所要的ViewModel物件,同時把我們的ViewModel物件放入mViewModelStore。同時返回我們的ViewModel.
總結:
核心思想就是HolderFragment呼叫setsetRetainInstance(true),使得HolderFragment在FragmentMannager呼叫FindFragmentBytag,找到的是同一個HolderFragment物件(無論Activity是否重繪),這也保證了HolderFragment中的ViewModelStore(存放我們ViewModel的地方)不被銷燬,然後我們取出我們所要的ViewModel,進行資料讀取。