Android Jetpack Architecture原理之ViewModel

1,739 阅读11分钟


  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);
                } 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)的方式去保存,跟今天的分析也有关联,分析源码的过程总是看着就有新的问题,再次带着问题去解决会再次有不同的收获,本文的理解也可能有偏差,如有错误和想要交流的也欢迎指正沟通。