ViewModel探索(1)

626 阅读11分钟

首先希望这篇文章如果对大家有帮助的话,可以点一个善意的赞或者收藏,这对我创作来说非常重要!先感谢大家啦~

1.ViewModel是什么?

官方解释: ViewModel是Jetpack组件里面一个具备宿主生命感知能力的数据存储组件;

从官方的解释可以看出有两点:

  1. 数据存储组件
  2. 具备宿主感知能力

从第一点来看,我首先有第一个疑问,数据存储组件?这有什么特别的,我为何不新建一个类保存数据,非要放到ViewModel呢?然后第二点乍一眼看,我盲猜ViewModel只是集成了LifeCycle的能力,然后才拥有的宿主感知能力吧?

接下来我们简单的解释一下以上的疑问吧~!

首先ViewModel他的作用确实是用于保存页面(甚至是应用维度)的数据,其次就是ViewModel拥有数据还原的能力,我们都知道一旦配置发生变化(页面旋转、分辨率调整、系统字体变更),Activity就会进行重建,在重建后如果我们的数据是定义在Activity上,那这个时候数据就会丢失,但是如果我们是放在ViewModel上,页面重建后我们拿到的ViewModel实体依旧是重建前的实体,那就意味着原本我们保存在ViewModel里面的数据也会得以保存。

但是这个时候就会有人有疑问,这只是重建啊,那么如果发生了因内存不足或者电量不足下的Activity回收,那回显时ViewModel也会恢复保存数据吗?

答案是会的,但是依赖SavedState这个组件,我们使用ViewModel + SaveState之后我们就能应对到这种情况!

而且使用ViewModel还有一个好处,就是充当MVVM里面VM的角色,让Activity的代码更加清爽,只专注View层相关的事务即可!如果有小伙伴不了解MVVM的话,可以去搜索一下相关资料,在这里就不展开说了!

至于宿主感知能力,我们就在原理章节顺路解开!话不多说我们先看看ViewModel是怎么使用的!

2.ViewModel怎么用?

既然是数据存储组件,我们关注的有以下几点:

  1. 怎么声明ViewModel?
  2. 怎么在Activity或Fragment中获取ViewModel?

2.1 声明ViewModel

//继承ViewModel
class MyViewModel: ViewModel(){
    //在ViewModel下声明自己的数据
    val data=MutableLiveData<String>()
    
    //声明处理数据逻辑等函数
    fun loadData():MutableLiveData<String> {
        if (data.value == null) {
            data.setValue(fetchDataFormRemote())
        }
        return data
    }

    fun fetchDataFormRemote():String{
        return  "从服务器获取的数据";
    }
}

2.2 获得声明的ViewModel对象

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //通过ViewModelProvider获得我们上面声明的MyViewModel对象
        val myViewModel = ViewModelProvider(this).get(MyViewModel::class.java)
        //调用加载数据方法,并打印
        print(myViewModel.loadData())
  
    }
}

以上就是ViewModel + Activity最常用的使用方式了,可以说是很简单了~

2.3 其他使用场景

2.3.1 跨页面数据共享

比如我们有时候想保存一些全局数据,这时候很多页面都要获取该数据,那么除了声明静态数据之外,我们也可以通过以下方式保存全局数据:

//Application继承ViewModelStoreOwner接口,实现ViewModelStore能力
class MyApp : Application(), ViewModelStoreOwner {

    private val viewModelStore: ViewModelStore by lazy {
        ViewModelStore()
    }

    override fun getViewModelStore(): ViewModelStore {
        return viewModelStore
    }
    
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //与页面不同的是,我们通过application获取MyViewModel,此时ViewModel是保存在Application中
        val myViewModel = ViewModelProvider(application).get(MyViewModel::class.java)
        myViewModel.loadData()
        print(myViewModel.data.value)
    }
}

2.3.2 配合SavedState使用

//在构造函数中声明SavedStateHandle
class MyViewModel(val savedStateHandle: SavedStateHandle) : ViewModel() {
    private val DATA_SAVE_KEY = "save_key";

    val data = MutableLiveData<String>()

    fun loadData(): MutableLiveData<String> {
        if (data.value == null) {
            //1.首先先从内存中尝试还原数据
            val memoryData = savedStateHandle.get<String>(DATA_SAVE_KEY)
            if (memoryData != null) {
                data.postValue(memoryData)
            }
            //2.再从网络中拉取最新数据
            val remoteData=fetchDataFormRemote()
            //3.塞到savedState中
            savedStateHandle[DATA_SAVE_KEY]=remoteData;
            data.postValue(remoteData)
        }
        return data
    }

    fun fetchDataFormRemote(): String {
        return "从服务器获取的数据";
    }
}

配合SavedState使用的时候,即使是页面回收后重建,数据亦会保存不会丢失。

2.3.3 构造函数中声明Application

class MyViewModel(val app:Application) : AndroidViewModel(app) {
    val data = MutableLiveData<String>()

    fun loadData(): MutableLiveData<String> {
        //...
    }
}

通过这种方式去声明ViewModel时,Factory也会将Application作为构造函数的参数传达给我们~

3.ViewModel原理探索

在了解一件事物的时候,我们应该对他抛出疑问,然后逐一寻找答案,这个是快速了解一个事物最有方向的做法~ 我们此时应该产生以下疑问:

  1. ViewModelProvider是怎么生产与缓存ViewModel的?
  2. 如果我创建好ViewModel并且初始化了数据,但是在这个时候页面配置发生改变,ViewModel是怎么被保存与被还原的?
  3. ViewModel是怎么通过SaveState是怎么保存恢复数据的?(这个问题会留在ViewModel探索第二篇中解答)

3.1 了解ViewModel的创建过程

首先我们回顾一下ViewModel是怎么创建的~

    ViewModelProvider(activity).get(MyViewModel::class.java)

ViewModel是通过ViewModelProvider创建的,以上的代码我们分别分析其构造函数与get方法,就能了解ViewModel创建的流程,首先我们看看有哪些构造函数。

3.1.1 了解ViewModelProvider构造函数

class ViewModelProvider{
    //主构造函数
    //ViewModelStore: 具体保存ViewModel的容器
    //factory: 生产ViewModel的工厂对象
    //defaultCreationExtras: 工厂生产ViewModel对象时额外输入的参数(不关注这个)
    constructor(
        private val store: ViewModelStore,
        private val factory: Factory,
        private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
    )
    
    //次构造函数,ViewModelStore与Factory都会赋值一个默认对象
    public constructor(
        owner: ViewModelStoreOwner
    ) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))
    
    //次构造函数,指定一个生产ViewModel用的Factory
    public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
        owner.viewModelStore,
        factory,
        defaultCreationExtras(owner)
    )
    
}
    

可以看出来来,ViewModelProvider既不负责生产,也不负责保存数据,具体的生产会交由Factory去做,而保存ViewModel则交由ViewModelStore去保存,接下来我们看看get方法里面的逻辑

3.1.2 了解ViewModelProvider的get方法

    //Key前缀
    internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"

    //该方法会继续调用get方法,传入经过拼接的Key与ViewModel Class对象获得一个ViewModel对象
    public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
        val canonicalName = modelClass.canonicalName
            ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
        return get("$DEFAULT_KEY:$canonicalName", modelClass)
    }
    
    //实际上ViewModel是由该方法生产的~
     public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
        //1.首先会从store(该store就是我们通过构造参数中传入的store)中获取之前初始化过的ViewModel
        //(后面会介绍该store,目前我们只需知道他是一个Map就可以了)
        val viewModel = store[key]
        //2.对从缓存获取出来的ViewModel进行对比,是否同一类型,如果是的话,则直接返回
        if (modelClass.isInstance(viewModel)) {
            return viewModel as T
        } else {
            @Suppress("ControlFlowWithEmptyBody")
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        //3.如果缓存没有,则委托Factory进行生产,完成后保存到Store中
        return try {
            factory.create(modelClass, extras)
        } catch (e: AbstractMethodError) {
            factory.create(modelClass)
        }.also { store.put(key, it) }
    }
    

ViewModelProvider的核心源码其实到这里就结束了,总结一下就是ViewModelProvider生产依赖Factory,存储依赖Store,但是他们具体是怎么样的还不知道,所以我们还要继续揭开他们的面纱,再就是我们通过构造函数可以看出,ViewModelStore除了可以直接传进来,还能通过ViewModelStoreOwner中的getViewModelStore方法获得,接下来我们就逐一解密,Activity为什么是ViewModelStoreOwner,而Activity又是怎么持有Store的。

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

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }
    //...
}

ok,ViewModelStore只是内部声明了一个Map,然后封装了get和put方法而已,那ViewModelStore没什么好说的,接着就看看ViewModelStoreOwner这个接口

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

Fine,这个接口更加朴素了,甚至到现在我们已经猜得出,Activity无非就是实现了ViewModelStoreOwner接口,然后内部再声明了一个ViewModelStore,通过重写getViewModelStore方法返回Store而已,我们来看看是不是这样

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner, {
        
        //Activity中声明了一个Store
        private ViewModelStore mViewModelStore;

        public ViewModelStore getViewModelStore() {
            //初始化ViewModelStore方法
            ensureViewModelStore();
            return mViewModelStore;
        }
    
        void ensureViewModelStore() {
            //如果ViewModel未初始化 则进行初始化
            if (mViewModelStore == null) {
                /**通过名字,好像隐约猜到这是啥? 没错,这就是一旦发生配置变化,重建后的Activity数据缓存,重建前如果我们已经初始化了ViewModelStore,则会缓存
                NonConfigurationInstances中,这里先mark住,说完Activity这事后我们马上说配置变化的缓存过程。
                */
                NonConfigurationInstances nc =
                        (NonConfigurationInstances) getLastNonConfigurationInstance();
                if (nc != null) {
                    // 从缓存中获取重建前的ViewModelStore
                    mViewModelStore = nc.viewModelStore;
                }
                //如果缓存中没有,则直接New一个出来
                if (mViewModelStore == null) {
                    mViewModelStore = new ViewModelStore();
                }
            }
        }
    }
    
  //再稍微看一下NonConfigurationInstances长什么样子
  static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
  }

果不其然,就是Activity实现了ViewModelStoreOwner接口,并且内部声明了一个Store对ViewModel进行了存储,只是我们意外的发现了Activity好像也实现了Factory相关接口,还有就是找到了ViewModelStore会从缓存中恢复的代码片段,目前ViewModelProvider怎么生产ViewModel的好像已经明白了!

等等! Factory还没讲呢。

3.1.4 了解生产ViewModel的Factory

  //调用defaultFactory并且传入Activity
  public constructor(
        owner: ViewModelStoreOwner
    ) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))
    
    //判断如果Activity实现了HasDefaultViewModelProviderFactory接口则直接获取Activity中的工厂,否则则取默认工厂来生产ViewModel
    fun defaultFactory(owner: ViewModelStoreOwner): Factory =
                if (owner is HasDefaultViewModelProviderFactory)
                    owner.defaultViewModelProviderFactory else instance
                    
    
    //默认工厂                
    public val instance: NewInstanceFactory
            @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
            get() {
                if (sInstance == null) {
                    sInstance = NewInstanceFactory()
                }
                return sInstance!!
            }
            
    //默认工厂声明类        
    public open class NewInstanceFactory : Factory {
        
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
          //默认工厂只是通过反射直接New出对象 
            return modelClass.newInstance()
        }
    }

我们可以知道,如果我们声明的是无构造函数的ViewModel,如下:

class MyViewModel: ViewModel(){
    val data=MutableLiveData<String>()
}

那就对应上了默认工厂的无参构造器新建对象的逻辑,但是这个只是无参构造函数,我们还可以声明

//1.声明带有Application的ViewModel
class MyViewModel(val app:Application): ViewModel(){
    val data=MutableLiveData<String>()
}

//2.或者带有SavedState组件的ViewModel
class  MyViewModel(val savedState:SavedStateHandle): ViewModel(){
    val data=MutableLiveData<String>()
}

以上额外两个带参ViewModel会在下一篇文章中进行解释,因为篇幅问题我们先解释最简单的无参构造函数的ViewModel是怎么生产的~

那目前来说ViewModel整个生产、存储流程我们都已经很清楚了,在本节认识到了ViewModelProvider是通过Factory生产和通过Activity中的ViewModelStore去存储的。

但是我们还不清楚配置变化引起的重建,ViewModel是如何缓存与恢复的?

3.2 了解ViewModel因配置变化而重建数据恢复过程

在讲到Activity创建ViewModelStore的时候,留了一个悬念,再拉出源码看看


    public ViewModelStore getViewModelStore() {
        ensureViewModelStore();
        return mViewModelStore;
    }

    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            //从NonConfigurationInstances中恢复重建前的ViewModelStore对象
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }
    
    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }

在ensureViewModelStore方法中,Activity会尝试从getLastNonConfigurationInstance()中获取NonConfigurationInstances对象中的ViewModelStore,接下来无非我们就是解释两个问题

  1. Activity在重建时怎么存,存到哪里去?
  2. Activity在重建后怎么取?

3.2.1 Activity在重建时怎么存,存到哪里去?

当Activity因配置变更重建前,会触发以下回调:

class ComponentActivity {

    //FrameWork层会调用此方法触发保存
    NonConfigurationInstances retainNonConfigurationInstances() {
        //在这里再调用下面的回调函数进行保存
        Object activity = onRetainNonConfigurationInstance();
        //...
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.activity = activity;
        //...
        return nci;
    }
    
    
    public final Object onRetainNonConfigurationInstance() {
       //...其他保存操作
       //首先获得我们之前初始化好的ViewModelStore
        ViewModelStore viewModelStore = mViewModelStore;
        //判断ViewModelStore是否为空
        if (viewModelStore == null) {
            return null;
        }
        //不为空则塞进NonConfigurationInstances对象中保存
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.viewModelStore = viewModelStore;
        return nci;
    }
    
}

以上只是弄明白了一半,只是知道了实际上Framework会调用Activity的函数进行保存,接下来我们进入Framework层继续探索,不过因为代码庞大,我只会以伪代码的形式去展示关键逻辑,而且只对Framework的相关类做简单解释,后面我会开一个Framework专栏,有兴趣的小伙伴请关注我的博客吧~

class ActivityThread{
    //在Activity配置重建时,Framework层会调用此方法开始对Activity进行重建
    public void handleRelaunchActivity(ActivityClientRecord record){
        //...其他操作
        //调用即将重建的activity中的retainNonConfigurationInstances方法,并将数据保存到ActivityClientRecord中,而ActivityClientRecord暂时不会被销毁
        record.lastNonConfigurationInstance = record.activity.retainNonConfigurationInstances();
    }
}

//ActivityClientRecord相当于存储在Framework层的一个Activity句柄,保存着Activity的相关信息
class ActivityClientRecord{
    //...其他字段
    //Activity实体
    Activity activity;
    //存放着ViewModelStore的那个缓存对象
    Activity.NonConfigurationInstances lastNonConfigurationInstance;
}

可以看出来在Framework中,我们会通过ActivityClientRecord获具体取Activity调用其保存数据的回调,并且将数据保存在ActivityClientRecord中,ActivityClientRecord暂时不会被销毁,所以ViewModelStore就一直保存在其中,接着我们看一下Framework重建时,怎么将ActivityRecord的数据塞给重建后的Activity。

3.2.2 Activity在重建后怎么取?

class ActivityThread{
    //在Activity配置重建时,Framework层会调用此方法开始对Activity进行重建
    public void preformLaunchActivity(ActivityClientRecord record){
        //...其他操作
        //通过反射重建新的Activity对象,这里不作解释
        Activity activity = mInstrumentation.newInstance(classLoader,componet.getClassName(),r.intent)
        //恢复数据,在attch的时候将之前record保存的数据传进去
        activity.attch(...,record.lastNonConfigurationInstance);
    }
}

//回到Activity中
class Activity {
    final void attach(...,  NonConfigurationInstances lastNonConfigurationInstances) {
        //将Framework帮忙缓存的数据,设置到mLastNonConfigurationInstances中
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
    }
}

class ComponentActivity{

    //返回在attach时保存到本地的缓存数据
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }
    
    //最后回顾一下那个起源函数
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                //获取缓存中的ViewModelStore,而ViewModelStore保存着我们一开始创建的ViewModel
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }
    
}   
    

最后在Frameword重建Activity时,会将重建前保存的数据,通过新创建的activity的attch方法重新设置进Activity,然后Activity就可以通过getLastNonConfigurationInstance方法获取缓存在Framework中ViewModeStore,既然获取到了ViewModelStore,那就肯定能获取到我们之前存在其中的ViewModel!

可喜可贺,ViewModel的第一阶段探索已经到此结束,通过本篇文章我们知道了:

  1. ViewMode是什么?
  2. ViewModel的使用方式
  3. ViewModel是怎么创建与保存的
  4. 在Activity因配置变动重建后,ViewModel实体为何还是重建前的那个

但是以上都只是ViewModel的第一阶段的探索,接下来下一篇文章则对ViewModel进行完整探索,我们先保留一些疑问:

  1. 为什么ViewModel能够声明带有Application与SavedStateHandle参数的构造函数?这两类的ViewModel是怎么初始化的
  2. 因为内存不足等原因时回收Activity后再重建,是怎么通过SavedState进行恢复数据的?

以上就是文章:ViewModel探索(1)的全部内容,如果想了解更多Android、Flutter技术,请关注我博客

下一篇: ViewModel探索(2)之SavedState