【Android Jetpack教程】ViewModel原理分析
ViewModel的定義: ViewModel旨在以注重生命週期的方式儲存和管理介面相關的資料。ViewModel本質上是檢視(View)與資料(Model)之間的橋樑,想想以前的MVC模式,檢視和資料都會寫在Activity/Fragment中,導致Activity/Fragment過重,後續難以維護,而ViewModel將檢視和資料進行了分離解耦,為檢視層提供資料。
ViewModel的特點:
ViewModel生命週期比Activity長
資料可在螢幕發生旋轉等配置更改後繼續留存。下面是ViewModel生命週期圖:
可以看到即使是發生螢幕旋轉,旋轉之後拿到的ViewModel跟之前的是同一個例項,即發生螢幕旋轉時,ViewModel並不會消失重建;而如果Activity是正常finish(),ViewModel則會呼叫onClear()銷燬。
ViewModel中不能持有Activity引用
不要將Activity傳入ViewModel中,因為ViewModel的生命週期比Activity長,所以如果ViewModel持有了Activity引用,很容易造成記憶體洩漏。如果想在ViewModel中使用Application,可以使用ViewModel的子類AndroidViewModel,其在構造方法中需要傳入了Application例項:
public class AndroidViewModel extends ViewModel { private Application mApplication; public AndroidViewModel(@NonNull Application application) { mApplication = application; } public <T extends Application> T getApplication() { return (T) mApplication; } } 複製程式碼
ViewModel使用舉例
引入ViewModel,在介紹Jetpack系列文章Lifecycle時已經提過一次,這裡再寫一下:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"複製程式碼
先看執行效果圖:
頁面中有兩個Fragment,左側為列表,右側為詳情,當點選左側某一個Item時,右側會展示相應的資料,即兩個Fragment可以透過ViewModel進行通訊;同時可以看到,當螢幕發生旋轉的時候,右側詳情頁的資料並沒有丟失,而是直接進行了展示。
//ViewModelActivity.ktclass ViewModelActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.e(JConsts.VIEW_MODEL, "onCreate") setContentView(R.layout.activity_view_model) } } 複製程式碼
其中的XML檔案:
//activity_view_model.xml<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android=" xmlns:app=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".viewmodel.ViewModelActivity"> <fragment android:id="@+id/fragment_item" android:name="com.example.jetpackstudy.viewmodel.ItemFragment" android:layout_width="150dp" android:layout_height="match_parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/fragment_detail" app:layout_constraintTop_toTopOf="parent" /> <fragment android:id="@+id/fragment_detail" android:name="com.example.jetpackstudy.viewmodel.DetailFragment" android:layout_width="0dp" android:layout_height="match_parent" app:layout_constraintLeft_toRightOf="@+id/fragment_item" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>複製程式碼
直接將Fragment以佈局方式寫入我們的Activity中,繼續看兩個Fragment:
//左側列表Fragmentclass ItemFragment : Fragment() { lateinit var mRvView: RecyclerView //Fragment之間透過傳入同一個Activity來共享ViewModel private val mShareModel by lazy { ViewModelProvider(activity!!).get(ShareViewModel::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = LayoutInflater.from(activity) .inflate(R.layout.layout_fragment_item, container, false) mRvView = view.findViewById(R.id.rv_view) mRvView.layoutManager = LinearLayoutManager(activity) mRvView.adapter = MyAdapter(mShareModel) return view } //構造RecyclerView的Adapter class MyAdapter(private val sViewModel: ShareViewModel) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() { class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val mTvText: TextView = itemView.findViewById(R.id.tv_text) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val itemView = LayoutInflater.from(parent.context) .inflate(R.layout.item_fragment_left, parent, false) return MyViewHolder(itemView) } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val itemStr = "item pos:$position" holder.mTvText.text = itemStr //點選傳送資料 holder.itemView.setOnClickListener { sViewModel.clickItem(itemStr) } } override fun getItemCount(): Int { return 50 } } } 複製程式碼
//右側詳情頁Fragmentclass DetailFragment : Fragment() { lateinit var mTvDetail: TextView //Fragment之間透過傳入同一個Activity來共享ViewModel private val mShareModel by lazy { ViewModelProvider(activity!!).get(ShareViewModel::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return LayoutInflater.from(context) .inflate(R.layout.layout_fragment_detail, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { mTvDetail = view.findViewById(R.id.tv_detail) //註冊Observer並監聽資料變化 mShareModel.itemLiveData.observe(activity!!, { itemStr -> mTvDetail.text = itemStr }) } } 複製程式碼
最後貼一下我們的ViewModel:
//ShareViewModel.ktclass ShareViewModel : ViewModel() { val itemLiveData: MutableLiveData<String> by lazy { MutableLiveData<String>() } //點選左側Fragment中的Item傳送資料 fun clickItem(infoStr: String) { itemLiveData.value = infoStr } } 複製程式碼
這裡再強調一下,兩個Fragment中ViewModelProvider(activity).get()傳入的是同一個Activity,從而得到的ViewModel是同一個例項,進而可以進行互相通訊。
上面使用ViewModel的寫法有兩個好處:
螢幕切換時儲存資料
- 螢幕發生變化時,不需要重新請求資料,直接從ViewModel中再次拿資料即可。
Fragment之間共享資料:
- Activity 不需要執行任何操作,也不需要對此通訊有任何瞭解。
- 除了 SharedViewModel 約定之外,Fragment 不需要相互瞭解。如果其中一個 Fragment 消失,另一個 Fragment 將繼續照常工作。
- 每個 Fragment 都有自己的生命週期,而不受另一個 Fragment 的生命週期的影響。如果一個 Fragment 替換另一個 Fragment,介面將繼續工作而沒有任何問題。
ViewModel與onSaveInstance(Bundle)對比
- ViewModel是將資料存到記憶體中,而onSaveInstance()是透過Bundle將序列化資料存在磁碟中
- ViewModel可以儲存任何形式的資料,且大小不限制(不超過App分配的記憶體即可),onSaveInstance()中只能儲存可序列化的資料,且大小一般不超過1M(IPC通訊資料限制)
原始碼解析
ViewModel的存取
我們在獲取ViewModel例項時,並不是直接new出來的,而是透過ViewModelProvider.get()獲取的,顧名思義,ViewModelProvider意為ViewModel提供者,那麼先來看它的構造方法:
//ViewModelProvider.javapublic ViewModelProvider(@NonNull ViewModelStoreOwner owner) { this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance()); }public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) { this(owner.getViewModelStore(), factory); }public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) { mFactory = factory; mViewModelStore = store; } 複製程式碼
ViewModelProvider建構函式中的幾個引數:
- ViewModelStoreOwner:ViewModel儲存器擁有者,用來提供ViewModelStore
- ViewModelStore:ViewModel儲存器,用來儲存ViewModel
- Factory:建立ViewModel的工廠
private final ViewModelStore mViewModelStore;private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey";@MainThreadpublic <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"); } //首先構造了一個key,直接呼叫下面的get(key,modelClass) return get(DEFAULT_KEY + ":" + canonicalName, modelClass); }@MainThreadpublic <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { //嘗試從ViewModelStore中獲取ViewModel ViewModel viewModel = mViewModelStore.get(key); if (modelClass.isInstance(viewModel)) { if (mFactory instanceof OnRequeryFactory) { ((OnRequeryFactory) mFactory).onRequery(viewModel); } //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為空,透過Factory建立 viewModel = mFactory.create(modelClass); } //將ViewModel儲存到ViewModelStore中 mViewModelStore.put(key, viewModel); return (T) viewModel; } 複製程式碼
邏輯很簡單,首先嚐試透過ViewModelStore.get(key)獲取ViewModel,如果不為空直接返回該例項;如果為空,透過Factory.create建立ViewModel並儲存到ViewModelStore中。先來看Factory是如何建立ViewModel的,ViewModelProvider建構函式中,如果沒有傳入Factory,那麼會使用NewInstanceFactory:
//介面Factorypublic interface Factory { <T extends ViewModel> T create(@NonNull Class<T> modelClass); }//預設Factory的實現類NewInstanceFactorypublic static class NewInstanceFactory implements Factory { private static NewInstanceFactory sInstance; //獲取單例 static NewInstanceFactory getInstance() { if (sInstance == null) { sInstance = new NewInstanceFactory(); } return sInstance; } @Override public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { try { //反射建立 return modelClass.newInstance(); } catch (InstantiationException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); } catch (IllegalAccessException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); } } } 複製程式碼
可以看到NewInstanceFactory的實現很簡單,直接透過傳入的class反射建立例項,泛型中限制必須是ViewModel的子類,所以最終建立的是ViewModel例項。
看完Factory,接著來看ViewModelStore:
public class ViewModelStore { private final HashMap<String, ViewModel> mMap = new HashMap<>(); final void put(String key, ViewModel viewModel) { ViewModel oldViewModel = mMap.put(key, viewModel); //如果oldViewModel不為空,呼叫oldViewModel的onCleared釋放資源 if (oldViewModel != null) { oldViewModel.onCleared(); } } final ViewModel get(String key) { return mMap.get(key); } Set<String> keys() { return new HashSet<>(mMap.keySet()); } //遍歷儲存的ViewModel並呼叫其clear()方法,然後清除所有ViewModel public final void clear() { for (ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); } } 複製程式碼
ViewModelStore類也很簡單,內部就是透過Map進行儲存ViewModel的。到這裡,我們基本看完了ViewModel的存取,流程如下:
ViewModelStore的存取
上面聊了ViewModel的存取,有一個重要的點沒有說到,既然ViewModel的生命週期比Activity長,而ViewModel又是透過ViewModelStore存取的,那麼ViewModelStore又是如何存取的呢?在上面的流程圖中我們知道ViewModelStore是透過ViewModelStoreOwner提供的:
//介面ViewModelStoreOwner.javapublic interface ViewModelStoreOwner { ViewModelStore getViewModelStore(); } 複製程式碼
ViewModelStoreOwner中介面方法getViewModelStore()返回的既是ViewModelStore。我們上面例子獲取ViewModel時是
ViewModelProvider(activity).get(ShareViewModel::class.java)
,其中的activity其實就是ViewModelStoreOwner,也就是Activity中實現了這個介面:
//ComponentActivity.javapublic class ComponentActivity extends androidx.core.app.ComponentActivity implements LifecycleOwner, ViewModelStoreOwner,.... { @Override public ViewModelStore getViewModelStore() { //Activity還未關聯到Application時,會拋異常,此時不能使用ViewModel if (getApplication() == null) { throw new IllegalStateException("Your activity is not yet attached to the " + "Application instance. You can't request ViewModel before onCreate call."); } ensureViewModelStore(); return mViewModelStore; } void ensureViewModelStore() { if (mViewModelStore == null) { NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null) { //從NonConfigurationInstances中恢復ViewModelStore mViewModelStore = nc.viewModelStore; } if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); } } } @Override //覆寫了Activity的onRetainNonConfigurationInstance方法,在Activity#retainNonConfigurationInstances()方法中被呼叫,即配置發生變化時呼叫。 public final Object onRetainNonConfigurationInstance() { Object custom = onRetainCustomNonConfigurationInstance(); ViewModelStore viewModelStore = mViewModelStore; if (viewModelStore == null) { //嘗試從之前的儲存中獲取NonConfigurationInstance NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null) { //從NonConfigurationInstances中恢復ViewModelStore viewModelStore = nc.viewModelStore; } } if (viewModelStore == null && custom == null) { return null; } //如果viewModelStore不為空,當配置變化時將ViewModelStore儲存到NonConfigurationInstances中 NonConfigurationInstances nci = new NonConfigurationInstances(); nci.custom = custom; nci.viewModelStore = viewModelStore; return nci; } //內部類NonConfigurationInstances static final class NonConfigurationInstances { Object custom; ViewModelStore viewModelStore; } } 複製程式碼
NonConfigurationInstances是ComponentActivity的靜態內部類,裡面包含了ViewModelStore。getViewModelStore()內部首先嚐試透過getLastNonConfigurationInstance()來獲取NonConfigurationInstances,不為空直接能拿到對應的ViewModelStore;否則直接new一個新的ViewModelStore
跟進去getLastNonConfigurationInstance()這個方法:
//Activity.javapublic class Activity extends ContextThemeWrapper { NonConfigurationInstances mLastNonConfigurationInstances; public Object getLastNonConfigurationInstance() { return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity : null; } NonConfigurationInstances retainNonConfigurationInstances() { //onRetainNonConfigurationInstance實現在子類ComponentActivity中實現 Object activity = onRetainNonConfigurationInstance(); ....... if (activity == null && children == null && fragments == null && loaders == null && mVoiceInteractor == null) { return null; } NonConfigurationInstances nci = new NonConfigurationInstances(); nci.activity = activity; ...... return nci; } final void attach(Context context, ......., NonConfigurationInstances lastNonConfigurationInstances) { mLastNonConfigurationInstances = lastNonConfigurationInstances; } static final class NonConfigurationInstances { Object activity; ...... } } 複製程式碼
可以看到getLastNonConfigurationInstance()中是透過mLastNonConfigurationInstances是否為空來判斷的,搜尋一下該變數賦值的地方,就找到了Activity#attach()方法。我們知道Activity#attach()是在建立Activity的時候呼叫的,順藤摸瓜就可以找到了ActivityThread:
//ActivityThread.javafinal ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); Activity.NonConfigurationInstances lastNonConfigurationInstances;//1、將NonConfigurationInstances儲存到ActivityClientRecordActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = mActivities.get(token); Class<? extends Activity> activityClass = null; if (r != null) { ...... if (getNonConfigInstance) { try { //retainNonConfigurationInstances r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances(); } catch (Exception e) { ...... } } } synchronized (mResourcesManager) { mActivities.remove(token); } return r; }//2、從ActivityClientRecord中獲取NonConfigurationInstancesprivate Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ...... activity.attach(...., r.lastNonConfigurationInstances,....); } 複製程式碼
- 在執行performDestroyActivity()的時候,會呼叫Activity#retainNonConfigurationInstances()方法將生成的NonConfigurationInstances賦值給lastNonConfigurationInstances。
- 在performLaunchActivity()中又會透過Activity#attach()將lastNonConfigurationInstances賦值給Activity.mLastNonConfigurationInstances,進而取到ViewModelStore。
ViewModelStore
的存取都是間接在
ActivityThread
中進行並儲存在
ActivityClientRecord
中。在
Activity
配置變化時,
ViewModelStore
可以在
Activity
銷燬時得以儲存並在重建時重新從
lastNonConfigurationInstances
中獲取,又因為
ViewModelStore
提供了
ViewModel
,所以
ViewModel
也可以在
Activity
配置變化時得以儲存,這也是為什麼
ViewModel
的生命週期比
Activity
生命週期長的原因了。
作者:
小馬快跑
連結:
來源:稀土掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
更多Android技術分享可以關注@我,也可以加入QQ群號:Android進階學習群:345659112,一起學習交流。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69983917/viewspace-2795781/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android Jetpack Architecture原理之ViewModelAndroidJetpackView
- Android Jetpack系列——ViewModel原始碼分析AndroidJetpackView原始碼
- Android Jetpack之ViewModelAndroidJetpackView
- Android Jetpack 之 ViewModelAndroidJetpackView
- Android Jetpack元件 - ViewModel,LiveData使用以及原理AndroidJetpack元件ViewLiveData
- Android JetPack~ ViewModel (一) 介紹與使用AndroidJetpackView
- Jetpack-ViewModelJetpackView
- Jetpack的ViewModel與LiveDataJetpackViewLiveData
- Android Jetpack - Android TV 應用開發教程AndroidJetpack
- 一點點入坑JetPack:ViewModel篇JetpackView
- Jetpack架構元件學習(2)——ViewModel和Livedata使用Jetpack架構元件ViewLiveData
- Android Jetpack - DataBindingAndroidJetpack
- Android-ViewModel 使用指北AndroidView
- Android ViewModel 引入協程AndroidView
- ViewModel 原始碼分析View原始碼
- Android JNI原理分析Android
- Android Jetpack 之 LiveDataAndroidJetpackLiveData
- Android Jetpack 之 LifecycleAndroidJetpack
- Android官方架構元件之LiveData + ViewModel + Room 原始碼分析一Android架構元件LiveDataViewOOM原始碼
- Android Jetpack Navigation基本使用AndroidJetpackNavigation
- 剖析 Android 架構元件之 ViewModelAndroid架構元件View
- Android 架構元件 - Lifycycle, LiveData, ViewModelAndroid架構元件LiveDataView
- Android 官方架構元件(三)——ViewModelAndroid架構元件View
- Android Jetpack - Fragment官方說明AndroidJetpackFragment
- Android Jetpack Compose 引入示例工程AndroidJetpack
- 初學 Android 架構元件之 ViewModelAndroid架構元件View
- Android深色模式適配原理分析Android模式
- Android Jetpack 之Navigation Architecture Component使用AndroidJetpackNavigation
- Android Sunflower 帶您玩轉 JetpackAndroidJetpack
- Android Jetpack(2):DataBinding的基本使用AndroidJetpack
- 玩轉Android Jetpack系列之ViewModeAndroidJetpackView
- Android Jetpack - Emoji表情符號初探AndroidJetpack符號
- Android Jetpack 架構元件之 NavigationAndroidJetpack架構元件Navigation
- 【譯】Android Architecture - ViewModel 與 View 的通訊AndroidView
- Android模組開發框架 LiveData+ViewModelAndroid框架LiveDataView
- Android生成ViewModel例項的幾種方式AndroidView
- ViewModel 基礎使用和原始碼分析View原始碼
- Android技術棧(四)Android Jetpack MVVM 完全實踐AndroidJetpackMVVM