1. 程式人生 > >Android Jetpack Architecture原理之ViewModel

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修改某些行為,這裡不討論)

,那麼儲存在當前UI中的臨時資料也會被清空,例如,登陸輸入框,輸入賬號或密碼後旋轉螢幕,檢視被重建,輸入過的資料也清空了,這無疑是一種不友好的使用者體驗。對於少量的可序列化資料可以使用onSaveInstanceState()方法儲存然後在onCreate()方法中重新恢復,正如所說onSaveInstanceState對於大量的資料快取有一定的侷限性,大量的資料快取則可以使用Fragment.setRetainInstance(true)來儲存資料。ViewModel也是提供了相同的功能,其實和“RetainInstance”也有關聯,用來儲存和管理與UI相關的資料,允許資料在系統配置變化後存活,我們一起看一下這個ViewModel的快取是怎麼實現的呢? 使用方式
首先先看下使用方式,先上效果圖 ViewMode Sample

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<