Jetpack已經出了很久很久了,近幾年的GDD幾乎每次都會介紹新的元件,說來慚愧,一直沒有好好學習,看近年的Google 的很多Demo中其實都有所體現,之前都是大概的瞭解了一遍。最近決定,好好梳理一遍,既學習其用法,也嘗試學習下其設計思想。也是時候該補充一下了。進入正題--ViewModel
首先都是看官方的例子,ViewModel官方的的例子是會和另一個架構庫LiveData寫在一起,很多的部落格也是照官方的例子來說明,開始接觸時甚至給了我一種假象:ViewModel都是和LiveData一起使用的。後來閱讀才瞭解,ViewModel和LiveData職責分工還是很明顯的,使用LiveData Demo主要使用其observe功能,LiveDate的使用及原理之後再分析,甚至在appcompat-v7:27.1.1中直接單獨整合了ViewModel.所以,故為排除干擾,今天不會使用官方的主流Demo用法,先來看ViewModel。
Android的UI控制器(Activity和Fragment)從建立到銷燬擁有自己完整的生命週期,當系統配置發生改變時((Configuration changes)),系統就會銷燬Activity和與之關聯的Fragment然後再次重建(可通過在AndroidManifast.xml中配置android:configChanges修改某些行為,這裡不討論),那麼儲存在當前UI中的臨時資料也會被清空,例如,登陸輸入框,輸入賬號或密碼後旋轉螢幕,檢視被重建,輸入過的資料也清空了,這無疑是一種不友好的使用者體驗。對於少量的可序列化資料可以使用onSaveInstanceState()方法儲存然後在onCreate()方法中重新恢復,正如所說onSaveInstanceState對於大量的資料快取有一定的侷限性,大量的資料快取則可以使用Fragment.setRetainInstance(true)來儲存資料。ViewModel也是提供了相同的功能,其實和“RetainInstance”也有關聯,用來儲存和管理與UI相關的資料,允許資料在系統配置變化後存活,我們一起看一下這個ViewModel的快取是怎麼實現的呢?
使用方式
首先先看下使用方式,先上效果圖
public class MyViewModel extends ViewModel {
String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
@Override
protected void onCleared() {
super.onCleared();
name = null;
}
}
複製程式碼
public class ViewModelActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "ViewModelActivity";
TextView textView;
private MyViewModel myViewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewmodel);
textView = findViewById(R.id.textView);
textView.setOnClickListener(this);
ViewModelProvider.Factory factory = ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication());
/*
*這裡的this是ViewModelStoreOwner介面在appcompat-v7:27.1.1支援庫中AppCompatActivity已經實現了,
*如果是較低版本,需要更新支援包或者參考其實現對本來繼承的Activity做對應實現。
*/
ViewModelProvider provider = new ViewModelProvider(this, factory);//
myViewModel = provider.get(MyViewModel.class);
Log.e(TAG, "onCreate: " + myViewModel.getName() );
if (myViewModel.getName() != null) {
textView.setText(myViewModel.getName());
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.textView:
myViewModel.setName("MyViewModel Test");
textView.setText(myViewModel.getName());
break;
}
}
}
複製程式碼
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="default"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
複製程式碼
非常簡單的一個例子,這就是ViewModel最簡單的使用了,就是TextView中顯示ViewModel的資料。ViewModel需要由ViewModelProvider.get(Class)來取得,旋轉螢幕銷燬後,之前改變的資料還在。
發現的一些疑問
接下來就是進入主題分析下ViewModel到底是怎麼實現的呢?
帶著問題看原始碼:
- ViewModelProvider是幹啥的?
- AndroidViewModelFactory 這命名一看就是應該是工廠模式,工廠建立了什麼?
- provider.get(MyViewModel.class) 這裡直接使用的get命名就得到了需要的唯一資料
- 註釋中ViewModelStoreOwner又是什麼角色?
原始碼分析 先看ViewModel類,沒什麼說的,就是一個麼有任何真正實現的抽象類,只有一個抽象方法onCleared()
public abstract class ViewModel {
/**
* This method will be called when this ViewModel is no longer used and will be destroyed.
* <p>
* It is useful when ViewModel observes some data and you need to clear this subscription to
* prevent a leak of this ViewModel.
*/
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}
}
複製程式碼
接著看下ViewModelFactory,顧名思義就是製造ViewModel的。
AndroidViewModelFactory的繼承關係如下:
android.arch.lifecycle.ViewModelProvider.Factory
android.arch.lifecycle.ViewModelProvider.NewInstanceFactory
android.arch.lifecycle.ViewModelProvider.AndroidViewModelFactory
Factory是一個只包含一個create的interface,NewInstanceFactory實現了該方法傳入Class會利用ViewModel的預設無參構造器建立一個對應ViewModel的例項,而AndroidViewModelFactory增加了一個屬性就是應用的Applicaion,同時重寫create方法,檢視ViewModel是否有包含Applicaion引數的構造方法從而使用,對應的其實還有一個AndroidViewModel是ViewModel的子類,預設已經實現了帶有Application引數的構造方法,需要使用在ViewModel中使用application的直接繼承AndroidViewModel就可以,看到這裡其實最上面的例子有個不是問題的問題,其實上面的Factory直接使用NewInstanceFactory就可以建立出對應的ViewModel例項了。
/**
* Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
*/
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);
}
複製程式碼
/**
* Simple factory, which calls empty constructor on the give class.
*/
public static class NewInstanceFactory implements Factory {
@SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
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);
}
}
}
複製程式碼
/**
* {@link Factory} which may create {@link AndroidViewModel} and
* {@link ViewModel}, which have an empty constructor.
*/
public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
private static AndroidViewModelFactory sInstance;
/**
* Retrieve a singleton instance of AndroidViewModelFactory.
*
* @param application an application to pass in {@link AndroidViewModel}
* @return A valid {@link AndroidViewModelFactory}
*/
@NonNull
public static AndroidViewModelFactory getInstance(@NonNull Application application) {
if (sInstance == null) {
sInstance = new AndroidViewModelFactory(application);
}
return sInstance;
}
private Application mApplication;
/**
* Creates a {@code AndroidViewModelFactory}
*
* @param application an application to pass in {@link AndroidViewModel}
*/
public AndroidViewModelFactory(@NonNull Application application) {
mApplication = application;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.getConstructor(Application.class).newInstance(mApplication);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
return super.create(modelClass);
}
}
複製程式碼
之後通過ViewModelStoreOwner和剛剛建立的Factory建立出ViewModelPrivider例項
/**
* Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
* {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}.
*
* @param owner a {@code ViewModelStoreOwner} whose {@link ViewModelStore} will be used to
* retain {@code ViewModels}
* @param factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
/**
* Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
* {@code Factory} and retain them in the given {@code store}.
*
* @param store {@code ViewModelStore} where ViewModels will be stored.
* @param factory factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
this.mViewModelStore = store;
}
複製程式碼
/**
* A scope that owns {@link ViewModelStore}.
* <p>
* A responsibility of an implementation of this interface is to retain owned ViewModelStore
* during the configuration changes and call {@link ViewModelStore#clear()}, when this scope is
* going to be destroyed.
*/
@SuppressWarnings("WeakerAccess")
public interface ViewModelStoreOwner {
/**
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
*/
@NonNull
ViewModelStore getViewModelStore();
}
複製程式碼
ViewModelStoreOwner 也是一個介面是FragmentActivity實現了該介面並實現了其中的getViewModelStore()方法
public class FragmentActivity extends BaseFragmentActivityApi16 implements
ViewModelStoreOwner...{
private ViewModelStore mViewModelStore;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);
super.onCreate(savedInstanceState);
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
/**
* Returns the {@link ViewModelStore} associated with this activity
*
* @return a {@code ViewModelStore}
*/
@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) {
mViewModelStore = new ViewModelStore();
}
return mViewModelStore;
}
}
複製程式碼
這個ViewModelStore又是什麼呢,其實就是真正利用HashMap儲存ViewModel的地方了,看下程式碼在儲存和clear同時會呼叫ViewModel需要實現的抽象方法onClear()
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();
}
}
複製程式碼
這樣ViewModelProvider就是有了一個ViewModel的容器,這時去呼叫ViewModelProvider的get(Class)方法就是去呼叫mViewModelStore 的get()方法取出對應的ViewModel所以這裡只要持有的ViewModelStore是有快取的,那麼取出的ViewModel就是相同的快取了。
/**
* Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
* an activity), associated with this {@code ViewModelProvider}.
* <p>
* The created ViewModel is associated with the given scope and will be retained
* as long as the scope is alive (e.g. if it is an activity, until it is
* finished or process is killed).
*
* @param modelClass The class of the ViewModel to create an instance of it if it is not
* present.
* @param <T> The type parameter for the ViewModel.
* @return A ViewModel that is an instance of the given type {@code T}.
*/
@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);
}
@NonNull
@MainThread
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;
}
複製程式碼
看到這裡就會發現ViewModelStore的快取其實是通過NonConfigurationInstances的快取來實現的,這樣就完成了Activity銷燬重建後ViewModel還儲存原來的資料的過程,那麼NonConfigurationInstances 是什麼呢?如果有了解過使用在Activity中使用onRetainNonConfigurationInstance()儲存快取資料,在onCreate()中通過getLastNonConfigurationInstance()恢復之前的資料狀態的同學可能會很熟悉這裡的寫法,是的,這裡FragmentActivity就是使用的這種方式來儲存之前的ViewModelStore,看下FragmentActivity的onRetainNonConfigurationInstance()方法。
/**
* Retain all appropriate fragment state. You can NOT
* override this yourself! Use {@link #onRetainCustomNonConfigurationInstance()}
* if you want to retain your own state.
*/
@Override
public final Object onRetainNonConfigurationInstance() {
if (mStopped) {
doReallyStop(true);
}
Object custom = onRetainCustomNonConfigurationInstance();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
if (fragments == null && mViewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = mViewModelStore;//就是這裡了,會把之前的VeiwmodelStroe儲存到NonConfigurationInstances中以供後續恢復使用
nci.fragments = fragments;
return nci;
}
複製程式碼
這裡其實再次出現了一個問題 onRetainNonConfigurationInstance()和getLastNonConfigurationInstance()又是怎麼恢復資料呢?...這個其實和Activity的啟動流程相關,這裡也介紹一下吧,之後的內容其實是Activity的內容了,趁這次看ViwModel也跟著看了一遍,有了解過Activity啟動流程的同學更容易理解的多,大家酌情觀看。
也不能從頭開始說起,再從頭就要越扯越遠了,就從ActivityThread.java中的scheduleLaunchActivity開始
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
r.ident = ident;
r.intent = intent;
r.referrer = referrer;
r.voiceInteractor = voiceInteractor;
r.activityInfo = info;
r.compatInfo = compatInfo;
r.state = state;
r.persistentState = persistentState;
r.pendingResults = pendingResults;
r.pendingIntents = pendingNewIntents;
r.startsNotResumed = notResumed;
r.isForward = isForward;
r.profilerInfo = profilerInfo;
r.overrideConfig = overrideConfig;
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r);
}
複製程式碼
從ActivityThread.java中H(extents Handler)接收到LAUNCH_ACTIVITY,並且會接收ActivityClientRecord,其中會呼叫ActivityThread的handleLaunchActivity方法:
//ActivityThread.java
//沒有前後文的H中的handleMessage~~~
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
//ActivityClientRecord 是apk程式中一個Activity的代表,這個物件的activity成員引用真正的Activity元件,後面的都和它有關係
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");///這裡~這裡~
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
複製程式碼
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
Activity a = performLaunchActivity(r, customIntent);
...
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
if (activity != null) {
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (r.overrideConfig != null) {
config.updateFrom(r.overrideConfig);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config, //看到這個r.lastNonConfigurationInstances 就是在Activity方法中呼叫getLastNonConfigurationInstance()獲取到的Object了。
r.referrer, r.voiceInteractor, window, r.configCallback);
...
}
複製程式碼
註釋中的地方就是lastNonConfigurationInstances的賦值的地方,可能會發現在scheduleLaunchActivity並沒有對lastNonConfigurationInstances賦值,因為第一次啟動Activity時,這裡其實就是null的,那麼賦值的地方在哪裡呢,既然是銷燬後會恢復資料,追蹤發現在performDestroyActivity()也就是在呼叫onDestroy生命週期之前有這樣一段程式碼
private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance) {
ActivityClientRecord r = mActivities.get(token);
...無關程式碼省略
if (getNonConfigInstance) {
try {
r.lastNonConfigurationInstances
= r.activity.retainNonConfigurationInstances();///就是這裡出現了想要找的NonConfigurationInstances
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to retain activity "
+ r.intent.getComponent().toShortString()
+ ": " + e.toString(), e);
}
}
}
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnDestroy(r.activity);
...無關程式碼省略
return r;
}
複製程式碼
在performDestroyActivity()呼叫了Activity.retainNonConfigurationInstances()方法了,所以邏輯切換回Activity中...
/**
* This method is similar to {@link #onRetainNonConfigurationInstance()} except that
* it should return either a mapping from child activity id strings to arbitrary objects,
* or null. This method is intended to be used by Activity framework subclasses that control a
* set of child activities, such as ActivityGroup. The same guarantees and restrictions apply
* as for {@link #onRetainNonConfigurationInstance()}. The default implementation returns null.
*/
@Nullable
HashMap<String,Object> onRetainNonConfigurationChildInstances() {
return null;
}
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();///熟悉的程式碼,原來的配方,和分析ActivityThread之前聯絡起來了,在Activity中是空實現,這裡就是獲取子類的NonConfigurationInstance(),之前的例子就是的得FragmentActivity中的具體實現,上文中已經在分析ActivityThread.java已經指出。
HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
// We're already stopped but we've been asked to retain.
// Our fragments are taken care of but we need to mark the loaders for retention.
// In order to do this correctly we need to restart the loaders first before
// handing them off to the next activity.
mFragments.doLoaderStart();
mFragments.doLoaderStop(true);
ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
if (activity == null && children == null && fragments == null && loaders == null
&& mVoiceInteractor == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
if (mVoiceInteractor != null) {
mVoiceInteractor.retainInstance();
nci.voiceInteractor = mVoiceInteractor;
}
return nci;//這裡返回的是Activity中的NonConfigurationInstances就儲存在了ActivityClientRecord中了
}
複製程式碼
至此,ActivityClientRecord就不再深入了,可以看到在Activity中是以一個ArrayMap來儲存Activity的記錄,記錄的就是Activity的狀態,所以這裡就實現了對NonConfigurationInstances的儲存。
結語:至此就基本看完了ViewModel在Activity中的使用和原理,在Fragment中的實現主要是使用setRetainInstance(true)的方式去儲存,跟今天的分析也有關聯,分析原始碼的過程總是看著就有新的問題,再次帶著問題去解決會再次有不同的收穫,本文的理解也可能有偏差,如有錯誤和想要交流的也歡迎指正溝通。