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

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

当新的 Activity 重新创建,并调用 ViewModelProviders.of(this).get(xxxModel.class) 时,又会在 getViewModelStore() 方法中获取老 Activity 保存的 ViewModelStore。那么也就拿到了 ViewModel。具体代码如下所示:

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) {
    //👇获取保存的NonConfigurationInstances,
    NonConfigurationInstances nc =
    (NonConfigurationInstances) getLastNonConfigurationInstance();
    if (nc != null) {
    //👇从该对象中获取ViewModelStore
    mViewModelStore = nc.viewModelStore;
    }
    if (mViewModelStore == null) {
    mViewModelStore = new ViewModelStore();
    }
    }
    return mViewModelStore;
    }

ViewModel 何时判断是否被移除

ViewModel 最重要的特性就是不会在配置发生改变的时候被移除。其内部实现也非常简单,监听 Activity 声明周期,在 onDestory 方法被调用时,判断配置是否改变。如果没有发送改变,则调用 Activity 中的 ViewModelStore 的 clear() 方法,清除所有的 ViewModel。具体代码如下所示:

public ComponentActivity() {
Lifecycle lifecycle = getLifecycle();
//省略更多…
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
//👇在配置没发生改变且走到onDestory方法时,清除所有的ViewModel
getViewModelStore().clear();
}
}
}
});
}

ViewModel 在 Fragment 的绑定过程

在官方的最新代码实现中,Fragment 中的 ViewModel 与其宿主 Activity 有着密切的联系。要了解 ViewModel 与 Fragment 的绑定过程,我们需要先了解 FragmentManagerFragmentManagerViewModel 相关知识。

FragmentManager 介绍

每个 Fragment 及宿主 Activity (继承自 FragmentActivity)都会在创建时,初始化一个 FragmentManager 对象,了解 Fragment 中的 ViewModel 与 Activity 的联系的关键,就是理清这些不同阶级的栈视图。

下面给出一个简要的关系图:

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

  • 对于宿主 Activity , getSupportFragmentManager()获取的是 FragmentActivity 的 FragmentManager 对象;
  • 对于 Fragment , getFragmentManager() 是获取的父 Fragment (如果没有,则是 FragmentActivity )的 FragmentManager 对象,而 getChildFragmentManager() 是获取自身的 FragmentManager 对象。

FragmentManagerViewModel 介绍

每个 Fragment 创建时,都会创建一个 FragmentManagerViewModel 对象,在该对象中主要存储其 子Fragment 的 ViewModelStore 与 FragmentManagerViewMoel。具体结构如下所示:

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

在 FragmentManagerViewModel 中:

  • mViewModelStore 是类型为 <String, FragmentManagerViewModel> 的 HashMap
  • mChildNonConfigs 是类型为 <String, ViewModelStore> 的 HashMap

上述两个 Map 对应的 Key 值都为 Fragment 的唯一 UUID。该 UUID 会在 Fragment 对象创建时自动生成。也就是每个 Fragment 对应唯一 UUID。

ViewModel 在 Fragment 绑定具体流程

ViewModel 与 Fragment 的绑定流程比较复杂,主要分为三个流程:

  • 第一步:在宿主 Activity 创建时,默认会在其 FramgentManager 中创建一个 FragmentManagerViewModel。同时将生成的 FragmentManagerViewModel 存储在其本身的 ViewModelStore 中。同时使用自身的FragmentManager
  • 第二步:在 Fragment 创建时,从 宿主Activity父Fragment 中的 FramgentManager 中获取对应的 FragmentManagerViewModel,并使用自身的 ChildFragmentManagermNonConfig 变量进行保存。
  • 第三步:将 Fragment 中所创建的 ViewModel 与其自身的 ViewModelStore 关联 ,并自身的 ViewModelStore 存储在 mNonConfig 所指向的 FragmentManaerViewModel 中的 mViewModelStores 中。

下面我将结合源码对这三个流程进行详细的介绍。

第一步流程

在宿主 Activity 创建时,默认会在其 FramgentManager 中创建一个 FragmentManagerViewModel。同时将生成的 FragmentManagerViewModel 存储在其本身的 ViewModelStore 中。同时使用自身的FragmentManager

FragmentActivity 中的 onCreate 方法:

protected void onCreate(@Nullable Bundle savedInstanceState){
mFragments.attachHost(null /parent/);//👈传入null
//省略更多…
}

mFragments 是 FragmentController,内部通过 FragmentHostCallback 间接控制 FragmentManager。

该方法最终会执行 FragmentActivity 中 FragmentManager 的 attachController 方法:

void attachController(@NonNull FragmentHostCallback<?> host,
@NonNull FragmentContainer container, @Nullable final Fragment parent) {
//省略更多…
if (parent != null) {
mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
} else if (host instanceof ViewModelStoreOwner) {
//👇走这里
ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
} else {
mNonConfig = new FragmentManagerViewModel(false);
}
}

因为传入的 parent = null,且 Activity 默认实现了 ViewModelStoreOwner 接口,所以会获取 Activity 中的 ViewModelStore,接着调用 FragmentManagerViewModel 的 getInstance() 方法:

static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,
FACTORY);
return viewModelProvider.get(FragmentManagerViewModel.class);
}

在该方法中,会创建 FragmentManagerViewModel,并将其添加到 Activity 中的 ViewModelStore 中。

整体流程如下所示:

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

第二步流程

在 Fragment 创建时,从 宿主Activity父Fragment 中的 FramgentManager 中获取对应的 FragmentManagerViewModel,并使用自身的 ChildFragmentManagermNonConfig 变量进行保存。

当 Fragment 与 Activity 关联时,在其 performAttach() 方法中

void performAttach() {
//👇又会调用attachController
mChildFragmentManager.attachController(mHost, new FragmentContainer() {
@Override
@Nullable
public View onFindViewById(int id) {
if (mView == null) {
throw new IllegalStateException(“Fragment " + this + " does not have a view”);
}
return mView.findViewById(id);
}

@Override
public boolean onHasView() {
return (mView != null);
}
}, this);//👈注意这里的this传入的parent是当前Fragment
//省略更多…
}

该方法会调用 Fragment 中 ChildFragmentManager 中的 attachController 方法如下所:

void attachController(@NonNull FragmentHostCallback<?> host,
@NonNull FragmentContainer container, @Nullable final Fragment parent) {
//省略更多…
if (parent != null) {
//👆因为parent为this,故我们会获取Activity的FragmentManager
mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
} else if (host instanceof ViewModelStoreOwner) {
ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
mNonConfig = FragmentManagerViewModel.getInstance(a);
} else {
mNonConfig = new FragmentManagerViewModel(false);
}
}

注意,当 Fragment 是 子Fragment 时,parent.fragmentManager 的值为父Fragment 的 FragmentManager,否则为 Activity 中的 FragmentManager。

继续追踪 FragmentManager 下的 getChildNonConfig 方法:

private FragmentManagerViewModel getChildNonConfig(Fragment f){
return mNonConfig.getChildNonConfig(f);
}

mNonConfig 本身为 FragmentManagerViewModel,我们继续跟踪:

FragmentManagerViewModel getChildNonConfig(Fragment f){
FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
if (childNonConfig == null) {
//👇创建Fragment的FragmentManagerViewModel
childNonConfig = new FragmentManagerViewModel(mStateAutomaticallySaved);
mChildNonConfigs.put(f.mWho, childNonConfig);
}
return childNonConfig;
}

在该方法中,会从 Activity 中的 FragmentManagerViewModel 中的 mChildNonConfigs 中获取 Fragment 的 FragmentManagerViewModel,如果有,直接返回。反之,存入mChildNonConfigs 中。

整体流程如下所示:

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

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

题外话,我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

Android开发8年,阿里、百度一面惨被吊打!我是否应该转行了?

【Android进阶学习视频】、【全套Android面试秘籍】

希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

  • 22
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值