首先希望这篇文章如果对大家有帮助的话,可以点一个善意的赞或者收藏,这对我创作来说非常重要!先感谢大家啦~
1.ViewModel是什么?
官方解释: ViewModel是Jetpack组件里面一个具备宿主生命感知能力的数据存储组件;
从官方的解释可以看出有两点:
- 数据存储组件
- 具备宿主感知能力
从第一点来看,我首先有第一个疑问,数据存储组件?这有什么特别的,我为何不新建一个类保存数据,非要放到ViewModel呢?然后第二点乍一眼看,我盲猜ViewModel只是集成了LifeCycle的能力,然后才拥有的宿主感知能力吧?
接下来我们简单的解释一下以上的疑问吧~!
首先ViewModel他的作用确实是用于保存页面(甚至是应用维度)的数据,其次就是ViewModel拥有数据还原的能力,我们都知道一旦配置发生变化(页面旋转、分辨率调整、系统字体变更),Activity就会进行重建,在重建后如果我们的数据是定义在Activity上,那这个时候数据就会丢失,但是如果我们是放在ViewModel上,页面重建后我们拿到的ViewModel实体依旧是重建前的实体,那就意味着原本我们保存在ViewModel里面的数据也会得以保存。
但是这个时候就会有人有疑问,这只是重建啊,那么如果发生了因内存不足或者电量不足下的Activity回收,那回显时ViewModel也会恢复保存数据吗?
答案是会的,但是依赖SavedState这个组件,我们使用ViewModel + SaveState之后我们就能应对到这种情况!
而且使用ViewModel还有一个好处,就是充当MVVM里面VM的角色,让Activity的代码更加清爽,只专注View层相关的事务即可!如果有小伙伴不了解MVVM的话,可以去搜索一下相关资料,在这里就不展开说了!
至于宿主感知能力,我们就在原理章节顺路解开!话不多说我们先看看ViewModel是怎么使用的!
2.ViewModel怎么用?
既然是数据存储组件,我们关注的有以下几点:
- 怎么声明ViewModel?
- 怎么在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原理探索
在了解一件事物的时候,我们应该对他抛出疑问,然后逐一寻找答案,这个是快速了解一个事物最有方向的做法~ 我们此时应该产生以下疑问:
- ViewModelProvider是怎么生产与缓存ViewModel的?
- 如果我创建好ViewModel并且初始化了数据,但是在这个时候页面配置发生改变,ViewModel是怎么被保存与被还原的?
- 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,接下来无非我们就是解释两个问题
- Activity在重建时怎么存,存到哪里去?
- 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的第一阶段探索已经到此结束,通过本篇文章我们知道了:
- ViewMode是什么?
- ViewModel的使用方式
- ViewModel是怎么创建与保存的
- 在Activity因配置变动重建后,ViewModel实体为何还是重建前的那个
但是以上都只是ViewModel的第一阶段的探索,接下来下一篇文章则对ViewModel进行完整探索,我们先保留一些疑问:
- 为什么ViewModel能够声明带有Application与SavedStateHandle参数的构造函数?这两类的ViewModel是怎么初始化的
- 因为内存不足等原因时回收Activity后再重建,是怎么通过SavedState进行恢复数据的?
以上就是文章:ViewModel探索(1)的全部内容,如果想了解更多Android、Flutter技术,请关注我博客