Android 架構之美 - ViewModel
ViewModel
是資料與 UI 分離的中間層,提供了一個將資料轉換為 UI 友好型資料的場所。其次,它也提供了多Fragment
複用相同ViewModel
的機制。
簡單使用
class UserViewModel(): ViewModel() { val userLiveData = LiveData<User>() override fun onCleared(){ // clear 工作,例如 Rxjava 裡取消訂閱 } } class UserFragment(): Fragment() { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) val viewModel = ViewModelProviders.of(this).get(UserViewModel::class.java) viewModel.userLiveData.observe(viewLifecycleOwner, Observer { // update ui }) } }
如果你想在多個Fragment
(同一個Activity
)裡複用這個UserViewModel
, 那麼只需要將ViewModelProviders.of(this)
更換為ViewModelProviders.of(activity)
即可, 這樣UserViewModel
的生存時間就會和activity
一致。
注意:這裡我使用了viewLifecycleOwner
而沒用使用lifecycleOwner
。 而viewLifecycleOwner
是在 androidx 中才存在,這主要是解決Fragment
中View
的生命週期與Fragment
的生命週期不同步的問題。 假設從 FragmentA 跳轉到 FragmentB 時, FragmentA 會經歷下列生命週期:
onPause -> onStop -> onDestroyView
從 FragmentB 返回到 FragmentA 時, FragmentA 又會重新經歷下列生命週期:
onCreateView -> onActivityCreated -> onStart -> onResume
如果用Fragment
的生命週期lifecycleOwner
, 那麼將會在FragmentB 返回到 FragmentA 時產生新的 observe 訂閱,而舊的訂閱並沒有被銷燬。在之前的版本,可能需要開發者自己去處理重複訂閱的問題,而新版 androidx 則提供了View
的生命週期viewLifecycleOwner
, 開發者可以視需求而採用不同的生命週期。
原始碼解析
ViewModel
並不是由使用者直接通過構造器生成的,而是通過ViewModelProvider
來獲取,這使得使用者不用關心ViewModel
的生命週期,全部交給框架內部解決。 框架提供了ViewModelProviders
這個工具方法來提供各種場景下的ViewModelProvider
// 提供一個將 viewModel 儲存 在 Fragment 的 ViewModelProvider public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) { Application application = checkApplication(checkActivity(fragment)); if (factory == null) { factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application); } return new ViewModelProvider(fragment.getViewModelStore(), factory); } // 提供一個將 viewModel 儲存 在 Activity 的 ViewModelProvider public static ViewModelProvider of(@NonNull FragmentActivity activity, @Nullable Factory factory) { Application application = checkApplication(activity); if (factory == null) { factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application); } return new ViewModelProvider(activity.getViewModelStore(), factory); }
我們可以看出,其關鍵根據傳參是Fragment
還是FragmentActivity
而提供不同的ViewModelStore
:
-
如果是
Fragment
, 則ViewModelStore
依附在Fragment
上,它提供的ViewModel
可以存活至Fragment
銷燬。 -
如果是
FragmentActivity
,則ViewModelStore
依附在FragmentActivity
上,它提供的ViewModel
可以存活至FragmentActivity
銷燬。 因此這些ViewModel
可以在多個Fragment
中共享。
另外值得一提的是:只有 androidx 中的Fragment
和FragmentActivity
才實現了ViewModelStoreOwner
介面。 所以 androidx 和這之前 support-v4 裡的實現並不一樣。 舊版本的實現也值得一提,這個我稍後再詳細介紹。
public interface ViewModelStoreOwner { /** * Returns owned {@link ViewModelStore} * * @return a {@code ViewModelStore} */ @NonNull ViewModelStore getViewModelStore(); } // Fragment 關於 ViewModelStoreOwner 的實現 public class Fragment implements ViewModelStoreOwner, ... { // 省略其它原始碼... // ViewModelStore for storing ViewModels associated with this Fragment ViewModelStore mViewModelStore; public ViewModelStore getViewModelStore() { if (getContext() == null) { throw new IllegalStateException("Can't access ViewModels from detached fragment"); } if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); } return mViewModelStore; } public void onDestroy() { mCalled = true; FragmentActivity activity = getActivity(); boolean isChangingConfigurations = activity != null && activity.isChangingConfigurations(); if (mViewModelStore != null && !isChangingConfigurations) { mViewModelStore.clear(); } } } // FragmentActivity 關於 ViewModelStoreOwner 的實現 public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner, ... { private ViewModelStore mViewModelStore; 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; } protected void onCreate(@Nullable Bundle savedInstanceState) { //... NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null && nc.viewModelStore != null && mViewModelStore == null) { mViewModelStore = nc.viewModelStore; } // ... } protected void onDestroy() { super.onDestroy(); if (mViewModelStore != null && !isChangingConfigurations()) { mViewModelStore.clear(); } mFragments.dispatchDestroy(); } }
而ViewModelStore
本身的實現比較容易,基本就是一個HashMap
來儲存ViewModel
例項:
public class ViewModelStore { private final HashMap<String, ViewModel> mMap = new HashMap<>(); final void put(String key, ViewModel viewModel) { ViewModel oldViewModel = mMap.put(key, viewModel); if (oldViewModel != null) { oldViewModel.onCleared(); } } final ViewModel get(String key) { return mMap.get(key); } /** *Clears internal storage and notifies ViewModels that they are no longer used. */ public final void clear() { for (ViewModel vm : mMap.values()) { vm.onCleared(); } mMap.clear(); } }
ViewModelStore
的問題解決了,我們就可以看看ViewModelProvider
是如何構造ViewModel
例項並存入ViewModelFactory
的了。首先看看ViewModelProvider
裡提供的Factory
介面:
public interface Factory { /** * Creates a new instance of the given {@code Class}. * <p> * * @param modelClass a {@code Class} whose instance is requested * @param <T>The type parameter for the ViewModel. * @return a newly created ViewModel */ @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass); }
通過名字我們就可以看出,這是一個工廠模式的應用,我們可以通過例項出不同的 Factory 來以不同的方式構造ViewModel
,當然,如果我們沒有特殊需求,可以直接使用框架提供的預設 Factory 來構造無引數的ViewModel
。 如果我們需要傳參,則我們需要自己實現 Factory 介面了。
public class ViewModelProvider { private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"; private final Factory mFactory; private final ViewModelStore mViewModelStore; public <T extends ViewModel> T get(@NonNull Class<T> modelClass) { String canonicalName = modelClass.getCanonicalName(); //... return 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. } } // 如果不存在,則通過 Factory 建立,並放入 mViewModelStore 中快取 viewModel = mFactory.create(modelClass); mViewModelStore.put(key, viewModel); //noinspection unchecked return (T) viewModel; } }
至此,整個ViewModel
就說得差不多了,使用簡單,原理也不是很難,但是功能確實強大。接下來會詳細介紹下 ViewModel 1.x 版本時ViewModelStore
的獲取。那個時候,Fragment
和FragmentActivity
還沒有實現ViewModelStoreOwner
。那我們又如何無感知的讓生命週期融入框架中呢?
其實這裡用了一個技巧,一個新增到FragmentManager
的Fragment
生命週期與Activity
或者parent fragment
的生命週期是一致的, 因此可以通過向Activity
或者Fragment
新增一個無View
的Fragment
來獲取其生命週期,再將ViewModelStore
依附在 這個Fragment
上。 如果有看過glide
原始碼的話,就可以發現其實它也是這麼做的,非常成熟的套路,可以學習學習。
ViewModel 1.x 中 ViewModelStore 的獲取
public static ViewModelProvider of(@NonNull Fragment fragment) { //... return new ViewModelProvider(ViewModelStores.of(fragment), sDefaultFactory); } public static ViewModelProvider of(@NonNull FragmentActivity activity) { //... return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory); }
我們可以看到,其主要其將核心邏輯抽取在了ViewModelStores
中,接下來我以FragmentActivity
為例繼續追蹤原始碼,Fragment
的邏輯大體相同,只是依賴於childFragment
而已。
public static ViewModelStore of(@NonNull FragmentActivity activity) { return holderFragmentFor(activity).getViewModelStore(); } public static HolderFragment holderFragmentFor(FragmentActivity activity) { // 移交給 HolderFragmentManager 處理 return sHolderFragmentManager.holderFragmentFor(activity); } HolderFragment holderFragmentFor(FragmentActivity activity) { FragmentManager fm = activity.getSupportFragmentManager(); // 查詢 FragmentManager 是否已經存在 HolderFragment HolderFragment holder = findHolderFragment(fm); if (holder != null) { // 已經存在,直接返回 return holder; } // 因為 fragment commit 是個非同步的過程,所以 FragmentManager 不存在可能是因為 commit 還沒執行完成,因此需要在 mNotCommittedActivityHolders 裡也存放一份。 holder = mNotCommittedActivityHolders.get(activity); if (holder != null) { return holder; } if (!mActivityCallbacksIsAdded) { mActivityCallbacksIsAdded = true; // 新增 callback, 在 activity 銷燬時移除 mNotCommittedActivityHolders 裡的快取, 一個應用只需新增一個 callback 足矣。 activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks); } // 不存在,則建立,並存入 mNotCommittedActivityHolders 中 holder = createHolderFragment(fm); mNotCommittedActivityHolders.put(activity, holder); return holder; } private static HolderFragment createHolderFragment(FragmentManager fragmentManager) { HolderFragment holder = new HolderFragment(); // 新增 HOLDER_TAG, 便於尋找 fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss(); return holder; } private static HolderFragment findHolderFragment(FragmentManager manager) { //... // 通過 HOLDER_TAG 來尋找 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; }
這就是之前版本的做法,不過其實也可以通過 lifecycle 去做。有了 lifecycle 之後,這種做法就顯得有些浪費了。但不管如何,這種對 Fragment 的應用也是值得稱讚的。