Jetpack明星组件-—ViewModel-这些知识点你必须知道!

本文详细介绍了Android中的ViewModelProvider构造函数,Factory接口,以及ViewModel在Activity和Fragment中的创建、获取和生命周期管理。重点探讨了配置变化时数据恢复的方法,包括onSaveInstanceState、Fragment的setRetainInstance和ViewModelStore的使用。
摘要由CSDN通过智能技术生成

public static ViewModelProvider of(@NonNull FragmentActivity activity) {
return new ViewModelProvider(activity);
}

ViewModelProvider 类需要我们传递 ViewModelStoreFactory 对象。其构造函数声明如下:

//使用ViewModelStoreOwner对象构造函数
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}

//使用ViewModelStoreOwner与Factory对象的构造函数
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}

//使用ViewModelStore与Factory对象的构造函数
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}

在 ViewModelProvider 内部,拥有三种类型构造函数:

  • (ViewModelStoreOwner owner):
  • 该构造函数使用 owner 对象的 getViewModelStore() 方法来获取 ViewModelStore 对象,如果传入的 owner 对象也实现了 HasDefaultViewModelProviderFactory 接口时,那么会调用 getDefaultViewModelProviderFactory() 方法获取 Factory。反之,使用内部静态的 NewInstanceFactory 对象来创建 Factory 对象。
  • (ViewModelStoreOwner owner, Factory factory):
  • 该构造函数使用 owner 对象的 getViewModelStore() 方法来获取 ViewModelStore 对象,使用传递的 Factory 对象
  • (ViewModelStore store, Factory factory)
  • 使用 ViewModelStoreFactory 对象的构造函数

Factory 接口介绍

在 ViewModelProvider 中,Factory 主要用于创建 ViewModel,Factory 的声明如下:

public interface Factory {
/**

  • 通过给定的Class对象创建ViewModel对象
  • @param modelClass 所需ViewModel的Class对象
  • @param ViewModel的泛型参数
  • @return 新创建的ViewModel对象
    */
    @NonNull
    T create(@NonNull Class modelClass);
    }

通过实现 Factory 接口,我们可以实现自己想要的工厂以创建所需的 ViewModel。在 Android 中有多个类都实现了该接口(如 KeyedFactory, AndroidViewModelFactory),这里以默认的 NewInstanceFactory 为例:

public static class NewInstanceFactory implements Factory {

private static NewInstanceFactory sInstance;

@NonNull
static NewInstanceFactory getInstance() {
if (sInstance == null) {
sInstance = new NewInstanceFactory();
}
return sInstance;
}

@SuppressWarnings(“ClassNewInstance”)
@NonNull
@Override
public T create(@NonNull Class modelClass) {
try {
//默认使用对应ViewModel类无参的构造函数创建实例对象
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);
}
}
}

默认情况下, NewInstanceFactory 会调用 ViewModel 的无参构造函数创建实例对象,当然如果你需要在 ViewModel 中使用其他参数,你也可以传递自定义的 Factory。

ViewModelStore 介绍

ViewModelStore 内部维护了一个 HashMap,其 key 为 DEFAULT_KEY + ViewModel的Class对象底层类规范名称,其 value 为对应 ViewModel 对象。每个 Activity 与 Fragment 都对应着一个 ViewModelStore ,用于存储所需的 ViewModel。ViewModelStore 类声明如下所示:

DEFAULT_KEY 值为:“androidx.lifecycle.ViewModelProvider.DefaultKey”

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 keys() {
return new HashSet<>(mMap.keySet());
}

/**

  • 当内部的 ViewModel 不再使用时,清除所占的内存
    */
    public final void clear() {
    for (ViewModel vm : mMap.values()) {
    //下面调用ViewModel的clear方法
    vm.clear();
    }
    mMap.clear();
    }
    }

Activity 中创建与获取 ViewModel 流程

ViewModel 最终的创建与获取,需要 ViewProvider 类调用 get(Class<T> modelClass)方法(该方法内部通过 ViewModelStore 与 Factory 的配合,创建并保存了所需的 ViewModel 对象),具体代码如下所示:

public T get(@NonNull Class 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);
}

该方法内部会调用 get(String key, Class<T> modelClass) 方法:

public T get(@NonNull String key, @NonNull Class modelClass) {
//👇根据key值从ViewModelStore中取对应的ViewModel
ViewModel viewModel = mViewModelStore.get(key);
//👇判断所传入的Class对象是否是ViewModel的Class类或其子类的对象,如果是,直接返回
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
//👇如果为null,根据传入的Factory创建新的VideModel
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
//👇将新的 ViewModel 存入ViewModelStore,并返回
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}

在该方法中,会在 ViewModelStore 中根据传入的 key 获取并保存 ViewModel。其具体逻辑如下:

  • 根据 key 值从 ViewModelStore 中取对应的 ViewModel。
  • 判断所传入的 Class 对象是否是 ViewModel 的 Class 类或其子类的对象,如果是,直接返回。(当 Object.isInstance(class) 接受的参数为 null 时,该方法会返回 false
  • 如果获取的 ViewModel 为 null,会根据传入的 Factory 对象创建新的 VideModel,并将创建好的 ViewModel 放入 ViewModelStore中。

Activity 中创建与获取 ViewModel 的整体流程如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ViewModel 在 Activity 中不会因配置改变而销毁的原理

我们都知道 ViewModel 不会因为 Activity 的配置发生改变而销毁,ViewModel 作用域如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

观察上图,我相信小伙伴们肯定有如下疑惑:

  • 当 Activity 因配置发生改变时,系统会重新创建一个新的 Activity 。那老的 Activity 中的 ViewModel 是如何传递给新的 Activity 的呢?
  • ViewModel 又是如何感知配置是否改变,进而判断是否销毁的呢?

要解决如上问题,我们需要了解 Android 中数据恢复的方式以及 Activity 生命周期中 ViewModel 实际处理流程。

数据恢复的常见方式

在 Android 系统中,需要数据恢复有如下两种场景:

  • 场景1:资源相关的配置发生改变导致 Activity 被杀死并重新创建。
  • 场景2:资源内存不足导致低优先级的 Activity 被杀死。

针对上述场景,分别对应三种不同的数据恢复方式。

对应场景1,不考虑在清单文件中配置 android:configChanges 的特殊情况。

使用 onSaveInstanceState 与 onRestoreInstanceState

使用 onSaveInstanceState 与 onRestoreInstanceState 方法,能处理场景1与场景2的情况。当你的界面数据简单且轻量时,例如原始数据类型或简单对象(比如 String),则我们可以采用该方式。如果你需要恢复的数据较为复杂,那你应该考虑使用 ViewModle + onSaveInstanceState() (为什么要配合使用,会在下文进行讲解),因为使用 onSaveInstanceState() 会导致序列化或反序列化,而这,有一定的时间消耗。

onSaveInstanceState() 更为详细的介绍以及使用,可参考官方文档:

使用 Fragment 的 setRetainInstance

当配置发生改变时,Fragment 会随着宿主 Activity 销毁与重建,当我们调用 Fragment 中的 setRetainInstance(true) 方法时,系统允许 Fragment 绕开销毁-重建的过程。使用该方法,将会发送信号给系统,让 Activity 重建时,保留 Fragment 的实例。需要注意的是:

  • 使用该方法后,不会调用 Fragment 的 onDestory() 方法,但仍然会调用 onDetach() 方法
  • 使用该方法后,不会调用 Fragment 的 onCreate(Bundle) 方法。因为 Fragment 没有被重建。
  • 使用该方法后,Fragment 的 onAttach(Activity)onActivityCreated(Bundle) 方法仍然会被调用。

以下示例代码展示了如何在配置发生改变时,保留 Fragment 实例,并进行数据的恢复。

public class MainActivity extends AppCompatActivity {

private SaveFragment mSaveFragment;

public static final String TAG_SAVE_FRAGMENT = “save_fragment”;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

FragmentManager fm = getSupportFragmentManager();
mSaveFragment = (SaveFragment) fm.findFragmentByTag(TAG_SAVE_FRAGMENT);

// fragment 不为空,是因为配置发生改变,Fragment 被重建
if (mSaveFragment == null) {
mSaveFragment = SaveFragment.newInstance();
fm.beginTransaction().add(mSaveFragment, TAG_SAVE_FRAGMENT).commit();
}

//获取保存的数据
int saveData = mSaveFragment.getSaveData();
}
}

Fragment :

public class SaveFragment extends Fragment {

private int saveData;

public static SaveFragment newInstance() {
Bundle args = new Bundle();
SaveFragment fragment = new SaveFragment();
fragment.setArguments(args);
return fragment;
}

@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//保存当前Fragment实例
setRetainInstance(true);
saveData = 1010;//通过网络请求或查询数据库,赋值需要保存的数据
}

@Override
public void onDetach() {
super.onDetach();
}

public int getSaveData() {
return saveData;
}
}

关于 Fragment 的 setRetainInstance 更多用法与注意事项,可以参看文章
Handling Configuration Changes with Fragments

使用 onRetainNonConfigurationInstance 与 getLastNonConfigurationInstance

在 Activity 中提供了 onRetainNonConfigurationInstance 方法,用于处理配置发生改变时数据的保存。随后在重新创建的 Activity 中调用 getLastNonConfigurationInstance 获取上次保存的数据。我们不能直接重写上述方法,如果想在 Activity 中自定义想要恢复的数据,需要我们调用上述两个方法的内部方法:

  • onRetainCustomNonConfigurationInstance()
  • getLastCustomNonConfigurationInstance()

注意:onRetainNonConfigurationInstance 方法系统调用时机介于 onStop - onDestory 之间,getLastNonConfigurationInstance 方法可在 onCreateonStart 方法中调用。

以下代码展示了,在 Actiity 中恢复自定义的数据:

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String name = (String) getLastCustomNonConfigurationInstance();
if (!TextUtils.isEmpty(name)) {
//获取恢复后的数据,执行相应操作
}
}

//你可以可以在onStart中,获取恢复的数据
// @Override
// protected void onStart() {
// super.onStart();
// String name = (String) getLastCustomNonConfigurationInstance();
// if (!TextUtils.isEmpty(name)) {
// }
// }

@Nullable
@Override
public Object onRetainCustomNonConfigurationInstance() {
return “AndyJennifer”;
}
}

在 Android 3.0 后,官方推荐使用 Fragment#setRetainInstance(true) 的方式进行数据的恢复。之所以推荐这种方式,个人猜测是为了降低 Activity 的冗余,将数据恢复的任务从 Activity 抽离出来,这更符合单一职责的设计模式。

几种数据恢复方式的总结

通过了解数据恢复的几种方式,我们能得到如下对比图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ViewModel 的恢复

ViewModel 在官方设计之初就倾向于在配置改变时进行数据的恢复。考虑到数据恢复时的效率,官方最终采用了 onRetainNonConfigurationInstance 的方式来恢复 ViewModel 。

在 SDK 27 之前,官方一直采用 Fragment#setRetainInstance(true) 的方式恢复数据。导致官方修改了其内部实现的原因,猜测是因为 Fragment 的坑,程序的扩展性等其他因素。

知道了 ViewModel 的恢复方式,那现在一起来解决我们之前的疑惑。当 Activity 因配置发生改变时,系统会重新创建一个新的 Activity 。那老的 Activity 中的 ViewModel 是如何传递给新的 Activity ?

在 Androidx 中的 Activity 的最新代码中,官方重写了 onRetainNonConfigurationInstance 方法,在该方法中保存了 ViewModelStore (ViweModelStore 中存储了 ViewModel ),进而也保存了 ViewModel,具体代码如下所示:

public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();

ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}

if (viewModelStore == null && custom == null) {
return null;
}

//将ViewModel存储在 NonConfigurationInstances 对象中
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了5、6年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

《Android高级架构师面试指导+2021大厂面试真题》免费领取

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了5、6年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

[外链图片转存中…(img-eouz1X1Y-1710666232005)]

《Android高级架构师面试指导+2021大厂面试真题》免费领取

[外链图片转存中…(img-jWv6VNq2-1710666232005)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值