Android Jetpack之ViewModel源码分析

Android Jetpack之ViewModel源码分析

ViewModel 简介

在Android开发的时候,使用Activity、Fragment的生命周期的变动有时候是不受开发人员控制的(比如横竖屏切换,导致Activity销毁并重新创建),各种因素导致Android界面或被系统重新创建。当Activity需要重新创建的时候,之前与之绑定的数据也会丢失(比如EditText上输入的数据或者网络请求而没有本地化存储的数据等),导致用户体验极差。
但是在实际开发中,我们可以使用Android中提供的onSaveInstanceState()方法来保存临时数据从而可以恢复用户现场,然后重新执行OnCreate的时候,通过Bundle参数来再次获取保存的数据。这种实现方式有较大的局限性:只能保存数据量较小的情况(比如较大的图片就不适合),并且数据需要被序列化(Parcelable)。
ViewMode的出现解决了onSaveInstanceState的不足,ViewMode将UI控制器和数据业务进行分离,UI控制器只负责UI展示相关的工作,ViewMode用来管理和存储与UI绑定的数据,同时ViewMode还与UI的生命周期相关联。比如在横竖屏切换时,与ViewMode相关联的UI界面需要重新绘制创建时即执行onCreate方法时,就可以使用ViewMode存储并恢复之前的用户现场的数据,而不会因为Activity的销毁而丢失,从而恢复用户现场。ViewModel的还有一个好处就是可以实现Activity和Fragment之间的数据共享。

官方网址介绍:ViewModel介绍

ViewMode的使用

2.1 Gradle引入

implementation "android.arch.lifecycle:extensions:2.0.0"

2.2 创建自定义ViewModel

创建自定义ViewModel有两种方式代码如下。其中最大的区别是ViewMode中不能引用Activity、Fragment的实例。主要原因是:为了防止内存泄漏。如果在某种场景下需要用到上下文Context,在继承ViewMode的时候,可以选择继承AndroidViewMode。由于AndroidViewMode持有的是Application,Application在单进程的应用中是单例模式,而且生命周期是最长的所以不在内存泄漏的问题。

open class MyCustomViewModel : ViewModel(){
    var name: String = "demo "
}

class MyCustomAndroidViewModel(application: Application): AndroidViewModel(application){
    var lastName = "demo"
}

2.3 在Activity中的使用

(1)代码块1 :通过ViewModelProviders的of方法绑定activity的实例进行初始化,然后通过get方法来获取到MyViewMode的实例。
(2)代码块2:使用了lambda表达式,主要是对ViewModel里面的lastName变量进行重新赋值。
(3)代码块3:使用了Kotlin的拓展包可以不用findViewById。如果旋转屏幕,重新执行onCreate方法,获取到的还是之前的ViewModel实例。所以这个时候屏幕显示的是“configChanged”文字。

class ViewModelActivity : AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_model)
        //代码块1
        val viewModel = ViewModelProviders.of(this).get(MyCustomViewModel::class.java)
        val androidViewModel = ViewModelProviders.of(this).get(MyCustomAndroidViewModel::class.java)
        //代码块3
        txt_view_model.text = androidViewModel.lastName
        //代码快2
        txt_view_model.setOnClickListener { androidViewModel.lastName = "configChanged" }
    }
}

open class MyCustomViewModel : ViewModel(){
    var name: String = "demo "
}

class MyCustomAndroidViewModel(application: Application): AndroidViewModel(application){
    var lastName = "demo"
}

2.4 ViewMode的生命周期

ViewMode在Activity被finish后才会销毁,否则在Activity的生命周期范围内会一直保存在内存中,或者当依附的Fragment detached后进行销毁。
使用官网的一张图来标注Activity的生命周期与ViewMode的生命周期关联,如下图所示:


在这里插入图片描述

源码分析

3.1 Activity是如何实现状态保留

在分析源码之前我们先来学习Activity是如何实现状态保留。Activity中的retainNonConfigurationInstances和getLastNonConfigurationInstance方法。在Android横竖屏切换时会触发onSaveInstanceState,而还原时会产生onRestoreInstanceState,但是Android的Activity类还有一个方法名为onRetainNonConfigurationInstance和getLastNonConfigurationInstance这两个方法。 当发生屏幕切换时,将伴随Destroying被系统调用。通过这个方法可以像onSaveInstanceState()的方法一样保留变化前的Activity数据和状态,最大的不同在于这个方法可以返回一个包含有状态信息的Object对象,其中甚至可以包含Activity Instance本身。用这个方法保存Activity State后,通过getLastNonConfigurationInstance()在新的Activity Instance中恢复原有状态。比如: 在恢复窗口时,我们可以不使用onRestoreInstanceState,而代替的是 getLastNonConfigurationInstance 方法。下面的例子效果和ViewModel实现的一致的,都可以实现用户现场的数据恢复。
(1)看一下Activity中NonConfigurationInstances的定义 。子类通过复写onRetainNonConfigurationInstance返回的对象,其实被赋值给了NonConfigurationInstances.activity属性。

static final class NonConfigurationInstances {
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
    }

(2)getLastNonConfigurationInstance方法返回的数据就是NonConfigurationInstances.activity属性。

@Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

(3)下面来看一个具体的例子。继承自Activity

  1. 代码块1复写Activity的onRetainNonConfigurationInstance方法,给MyCustomViewModel的name赋值,并返回该对象,其实该对象被赋值给了NonConfigurationInstances.activity属性
  2. 代码块2通过getLastNonConfigurationInstance方法获取NonConfigurationInstances.activity属性即MyCustomViewModel对象,然后取出name属性赋值给TextView。
class ViewModelActivity : Activity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_model)
        //代码块2
        lastNonConfigurationInstance?.let {
            var data =  it as MyCustomViewModel
            txt_view_model.text = data.name
        }
    }
    //代码块1
    override fun onRetainNonConfigurationInstance(): Any {
        val model = MyCustomViewModel()
        model.name = "configChanged"
        return model
    }
}

(4)下面来看另一种具体的实例。但是是继承自AppCompatActivity
代码的效果和上诉一致,只不过复写的是onRetainCustomNonConfigurationInstance方法,获取用的是getLastCustomNonConfigurationInstance方法。

class ViewModelActivity : AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_model)
        lastCustomNonConfigurationInstance?.let {
            var data =  it as MyCustomViewModel
            txt_view_model.text = data.name
        }
        Log.e("activity","class :"+this.toString())

    }

    override fun onRetainCustomNonConfigurationInstance(): Any {
        val model = MyCustomViewModel()
        model.name = "configChanged"
        return model
    }
}

为什么会有上面的区别呢?我们来看一下原因:
在FragmentActivity中把onRetainNonConfigurationInstance写成final了,不允许子类复写。但是我们在其中看到了onRetainCustomNonConfigurationInstance方法,并把onRetainCustomNonConfigurationInstance的返回值赋值给NonConfigurationInstances.custom属性,然后返回FragmentActivity中的NonConfigurationInstances赋值给Activity中的NonConfigurationInstances.activity属性。注意这两个NonConfigurationInstances要区分开来。

public final Object onRetainNonConfigurationInstance() {
        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;
        nci.fragments = fragments;
        return nci;
    }

NonConfigurationInstances类定义。这里的NonConfigurationInstances是定义在FragmentActivity里面的一个类。

  static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
    }

在来看一下getLastCustomNonConfigurationInstance方法:
(1)调用Activity中getLastNonConfigurationInstance获取FragmentActivity中的NonConfigurationInstances对象。
(2)获取到FragmentActivity中的NonConfigurationInstances对象后在获取custom属性,custom属性就是我们复写的onRetainCustomNonConfigurationInstance返回对象。

 /**
     * Return the value previously returned from
     * {@link #onRetainCustomNonConfigurationInstance()}.
     */
    @SuppressWarnings("deprecation")
    public Object getLastCustomNonConfigurationInstance() {
        NonConfigurationInstances nc = (NonConfigurationInstances)
                getLastNonConfigurationInstance();
        return nc != null ? nc.custom : null;
    }

特别注意的点
1. onRetainNonConfigurationInstance()在onSaveInstanceState()之后被调用。
2. 调用顺序同样在onStop() 和 onDestroy()之间。

3.2 ViewModelProviders类

  1. ViewModelProviders.of方法
    在Activity中我们会调用如下代码来获取ViewModel实例
ViewModelProviders.of(this).get(MyCustomViewModel::class.java)

那么我们就看看在ViewModelProviders.of(this)都做了些什么?
在ViewModelProviders中有四个构造方法,都是创建ViewModelProvider对象,只不过参数不同而已。
(1)ViewModelProviders里面的of()函数为我们构建一个ViewModelProvider对象。而ViewModelProvider构造方法接两个变量ViewModelStore和Factory
(2)ViewModelStore顾名思义就是存储ViewMoel的容器
(3)在代码块2:如果没有传入Factory,则内部创建了默认的Factory,Factory是ViewModelProvider的一个内部接口,里面有一个create方法。Factory实现类是用来构建ViewModel实例的。

public static ViewModelProvider of(@NonNull Fragment fragment) {
        return of(fragment, null);
    }
    
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return of(activity, null);
    }
    
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
        Application application = checkApplication(checkActivity(fragment));
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(fragment.getViewModelStore(), factory);
    }
 public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        //代码块1
        Application application = checkApplication(activity);
        if (factory == null) {
        //代码块2
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(activity.getViewModelStore(), factory);
    }

在构建ViewModelProvider的时候需要用到ViewModelStore和Factory,下面我们来分别介绍一下他们。

3.3 ViewModelStore类

ViewModelStore顾名思义就是存储ViewMoel的容器
(1)在ViewModelStore中通过HashMap存储ViewModel
(2)在Activity和Fragment的onDestroy方法中,会执行clear方法,清空Map集合里面的ViewModel。并且会执行ViewModel的onCleared方法。

 protected void onDestroy() {
        super.onDestroy();
        if (mViewModelStore != null && !isChangingConfigurations()) {
            mViewModelStore.clear();
        }
    }

在new ViewModelProvider(activity.getViewModelStore(), factory)的时候,会通过调用androidx.fragment.app.FragmentActivity的getViewModelStore()方法获取ViewModelStore对象。这里就用到了我们在3.1说的知识点了。
(1)如果mViewModelStore不是null,直接返回该对象
(2)如果是null,通过getLastNonConfigurationInstance获屏幕旋转等操作是保存的对象。注意返回的NonConfigurationInstances是FragmentActivity的。
(3)如果NonConfigurationInstances不为空,则获取NonConfigurationInstances中的viewModelStore属性。
(3)如果NonConfigurationInstances中的viewModelStore属性为null,则创建一个ViewModelStore对象。

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) {
           //代码块1
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

我们再来看看在Activity在横竖屏切换时是如何保存viewModelStore对象的,在onRetainNonConfigurationInstance方法中将mViewModelStore赋值给了NonConfigurationInstances.viewModelStore属性。这样在横竖屏切换时相当于ViewModel实例被恢复了。

public final Object onRetainNonConfigurationInstance() {
    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;
    nci.fragments = fragments;
    return nci;
}

通过上面的分析,我们知道在横竖屏旋转的时候,其实恢复的是ViewModelStore对象,而ViewModelStore里面通过键值对的形式存储着ViewModel,所以相当于ViewModel被恢复了。
下面附上ViewModelStore的代码

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();
    }
}

3.4 Factory类

在new ViewModelProvider(activity.getViewModelStore(), factory)的时候第二个参数是Factory的子类,Factory有2个实现类:是NewInstanceFactory, AndroidViewModelFactory 。通过ViewModelProvider.AndroidViewModelFactory.getInstance获取单例的Factory对象。
NewInstanceFactory源代码:

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);
            }
        }
    }

AndroidViewModelFactory 源代码

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);
        }
    }

(1)AndroidViewModelFactory实例化构造方法里面有参数的class,可以引用Context,其实是Appplication实例。
(2)如果是有application参数,则通过newInstance(application)实例化。否则调用父类的create方法然后通过newInstance()实例化。
(3)如果通过newInstance(application)实例化。就可以在ViewModel里面拿到Context,由于Application是APP全局的生命周期最长,所以就不存在内存泄露的问题了。
Factory类的定义非常简单,只有一个create方法,返回ViewModel实例。

    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);
    }

3.5 ViewModelProvider类

通过上面的步骤,我们已经构建出了ViewModelProvider对象,那么接下来就是使用它了。还记得在Activity里面的ViewModelProviders.of(this).get(MyCustomViewModel::class.java)代码吗?通过ViewModelProvider的get方法,就可以获取ViewModel的实例了。而这个实例是通过上面的Factory创建的。
下面我们来看一下get方法,里面生成了一个KEY,这个KEY就是ViewModelStore的HashMap的Key了。然后调用重载的get方法 get(@NonNull String key, @NonNull Class<T> modelClass)

  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);
    }

(1)根据之前的从FragmentActivity中获取的ViewModelStore对象中获取key对应的ViewModel。如果获取的ViewModel是传入的Class 的对象,则直接返回该ViewModel。
(2)通过of函数创建的Factory生产一个ViewModel实例,可能是带有Application实例的。
(3)将Factory实例put进ViewModelStore的HashMap里面。

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;
    }

3.6 ViewModel类

ViewModel是一个抽象类,里面有一个onCleared方法,有一个实现类AndroidViewModel,唯一的不同点是AndroidViewModel里面有一个Application的属性。
onCleared在Fragment和Activity的onDestory方法中通过ViewModelStore的clear调用。

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() {
    }
}
public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @SuppressWarnings("TypeParameterUnusedInFormals")
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容