Android Jetpack Architecture原理之ViewModel
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修改某些行為,這裡不討論)
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<