為什么要使用ViewModel胧砰?
在日常開發(fā)中,橫豎屏切換是非常常見的功能旦委,由于橫豎屏切換會使得
Activity
重建奇徒,導(dǎo)致界面相關(guān)數(shù)據(jù)都會丟失。為了避免這種情況缨硝,一般會有兩種做法:第一種是在AndroidManifest.xml
文件中摩钙,將Activity
的一個(gè)屬性設(shè)置為android:configChanges="orientation|keyboardHidden|screenSize"
;第二種方案是使用onSaveInstanceState
方法保存數(shù)據(jù)查辩,但此方法僅適合可以序列化和反序列化的少量數(shù)據(jù)胖笛,而不適合數(shù)量可能較大的數(shù)據(jù),如用戶列表或位圖宜岛。在使用MVP架構(gòu)時(shí)长踊,當(dāng)View層使用Presenter層去異步加載一個(gè)耗時(shí)任務(wù),若在任務(wù)結(jié)果返回回來之前萍倡,界面已經(jīng)被銷毀身弊,我們都需要手動判斷View層的狀態(tài);若Presenter層還有持有了View層的
Context
列敲,若不清理掉的話可能會造成內(nèi)存泄露阱佛。
ViewModel的出現(xiàn)能夠很優(yōu)雅地解決上述兩個(gè)問題。
簡單用法
// 1. 創(chuàng)建一個(gè)繼承ViewModel的類
public class MyVideoModel extends ViewModel {}
// 2. 創(chuàng)建一個(gè)ViewModelProvider實(shí)例
ViewModelProvider provider = new ViewModelProvider(this);
// 3. 通過provider獲取MyVideoModel實(shí)例
MyVideoModel videoModel = provider.get(MyVideoModel.class);
源碼分析
先來看看第2步創(chuàng)建ViewModelProvider
時(shí)做了些什么事
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
第一個(gè)參數(shù)調(diào)用了owner.getViewModelStore()
戴而,這個(gè)ViewModelStoreOwner
是一個(gè)接口凑术,我們繼承的父類Activity
--ComponentActivity
實(shí)現(xiàn)了這個(gè)接口。
@NonNull
@Override
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;
}
當(dāng)我們在自己的Activity#onCreate
方法內(nèi)new ViewModelProvider(this);
時(shí)所意,這個(gè)
mViewModelStore
就已經(jīng)不為空了淮逊,所以直接返回。ViewModelStore
就是用來保存我們自己創(chuàng)建的
ViewModel
對象扁眯。那么它是什么時(shí)候被創(chuàng)建賦值的呢?在FragmentActivity#onCreate
方法內(nèi)調(diào)用了
mFragments.attachHost(null /*parent*/);
// FragmentActivity.java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);
...
super.onCreate(savedInstanceState);
...
}
// FragmentController.java
public void attachHost(@Nullable Fragment parent) {
mHost.mFragmentManager.attachController(
mHost, mHost /*container*/, parent);
}
// FragmentManagerImpl.java
public void attachController(@NonNull FragmentHostCallback host,
@NonNull FragmentContainer container, @Nullable final Fragment parent) {
...
// Get the FragmentManagerViewModel
if (parent != null) {
mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
} else if (host instanceof ViewModelStoreOwner) {
ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
} else {
mNonConfig = new FragmentManagerViewModel(false);
}
}
attachController
方法中的parent
為空翅帜,所以不會走第一個(gè)if
條件姻檀;第一個(gè)參數(shù)host
,它的實(shí)現(xiàn)類是
HostCallbacks
涝滴。
class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements
ViewModelStoreOwner,
OnBackPressedDispatcherOwner {
...
@NonNull
@Override
public ViewModelStore getViewModelStore() {
return FragmentActivity.this.getViewModelStore();
}
...
}
再回到ComponentActivity#getViewModelStore方法
@NonNull
@Override
public ViewModelStore getViewModelStore() {
...
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
// Activity第一次啟動時(shí)绣版,得到nc為null胶台。但是在橫豎屏切換時(shí),這個(gè)nc不為空了
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
// 所以這里就拿到了之前保存的ViewModelStore杂抽,也就意味著拿到之前我們的ViewModel實(shí)例诈唬。
// 后面會繼續(xù)分析到
mViewModelStore = nc.viewModelStore;
}
// 執(zhí)行這里
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
此時(shí)再回到上面創(chuàng)建ViewModelProvider
時(shí)的第二個(gè)參數(shù),因?yàn)?code>ComponentActivity并沒有實(shí)現(xiàn)
HasDefaultViewModelProviderFactory
缩麸,所以第二個(gè)參數(shù)的類型是NewInstanceFactory
铸磅。第2步源碼分析完成。
接下來看第3步源碼實(shí)現(xiàn)杭朱。
@NonNull
@MainThread
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);
}
繼續(xù)追蹤get的重載函數(shù)
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
// 注釋1
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
// 注釋2
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
// 注釋3
viewModel = mFactory.create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
方法中的key為androidx.lifecycle.ViewModelProvider.DefaultKey:xxx.xxx.MyViewModel
阅仔,
modelClass
就是我們自己傳的MyViewModel.class
。注釋1處代碼第一次執(zhí)行時(shí)肯定為空弧械,從第2步源碼分析可知mFactory
的類型為NewInstanceFactory
八酒,所以會執(zhí)行到第注釋3。通過反射獲取到我們自己的
ViewModel
對象刃唐,然后將其保存到ViewModelStore
中羞迷。
當(dāng)因配置更新導(dǎo)致界面重建時(shí)如何做到保存數(shù)據(jù)的?
當(dāng)界面發(fā)生橫豎屏切換時(shí)画饥,Activity
會被銷毀再重建衔瓮,在調(diào)用onDestroy
方法之前系統(tǒng)會調(diào)用一個(gè)方法
// Activity.java
/**
* Called by the system, as part of destroying an
* activity due to a configuration change, when it is known that a new
* instance will immediately be created for the new configuration. You
* can return any object you like here, including the activity instance
* itself, which can later be retrieved by calling
* {@link #getLastNonConfigurationInstance()} in the new activity
* instance.
*/
public Object onRetainNonConfigurationInstance() {
return null;
}
來看看ComponentActivity中是如何將其重寫的
@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
// 這里直接返回的是null
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
// 正常情況下,此時(shí)的mViewModelStore不可能為空
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
根據(jù)代碼中的注釋荒澡,最終ViewModelStore
對象被保存在ComponentActivity
的內(nèi)部類
NonConfigurationInstances
對象中报辱。這個(gè)方法是在Activity
中被調(diào)用的。
// Activity.java
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();
...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
...
return nci;
}
將ComponentActivity#NonConfigurationInstances
對象賦值給了
Activity#NonConfigurationInstances
對象的activity
屬性单山。而retainNonConfigurationInstances
方法是在Activity執(zhí)行onDestroy
生命周期方法之前執(zhí)行的碍现。
// ActivityThread.java
/** Core implementation of activity destroy call. */
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = mActivities.get(token);
...
if (getNonConfigInstance) {
try {
// 將其返回給了ActivityClientRecord屬性
r.lastNonConfigurationInstances
= r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
...
}
}
try {
r.activity.mCalled = false;
// 執(zhí)行onDestroy方法
mInstrumentation.callActivityOnDestroy(r.activity);
...
} catch (Exception e) {
...
}
return r;
}
最終mViewModelStore
對象被保存在ActivityClientRecord
對象的
lastNonConfigurationInstances
屬性上。很顯然米奸,mActivities
屬性的生命周期與ActivityThread
保持一致昼接,那么lastNonConfigurationInstances
也一樣,因此不會受Activity
的銷毀再重建的影響悴晰。
當(dāng)Activity
重建時(shí)慢睡,又會執(zhí)行onCreate
方法,此時(shí)铡溪,我們再回到getViewModelStore
方法
@NonNull
@Override
public ViewModelStore getViewModelStore() {
...
if (mViewModelStore == null) {
// 會執(zhí)行到這里
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
@Nullable
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
mLastNonConfigurationInstances
不為空漂辐,所以直接返回它的屬性activity
的值。還記得這個(gè)屬性保存的是什么值嗎棕硫?上文已經(jīng)提到過了髓涯,它就是ComponentActivity
的NonConfigurationInstances
對象,而它的viewModelStore
就保存著橫豎屏銷毀前的那個(gè)ViewModelStore
對象哈扮,那也就意味著拿到了之前的ViewModel
對象纬纪。
那mLastNonConfigurationInstances
這個(gè)屬性是在哪里被賦值的呢蚓再?它是Activity
對象在ActivityThread
中被創(chuàng)建后,調(diào)用其attach
方法時(shí)進(jìn)行賦值的包各。上文也提到過摘仅,我們的ViewModel
被保存在ViewModelStore
,而ViewModelStore
又被保存在CompponentActivity#NonConfigurationInstances
對象的viewModelStore
屬性上问畅,而這個(gè)對象又被保存到了Activity#NonConfigurationInstances
對象的activity
屬性上娃属,它又被保存到了ActivityThread#ActivityClientRecord
對象的lastNonConfigurationInstances
屬性上。