使用 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
方法可在onCreate
与onStart
方法中调用。
以下代码展示了,在 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 的绑定过程,我们需要先了解 FragmentManager
与 FragmentManagerViewModel
相关知识。
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,并使用自身的ChildFragmentManager
中mNonConfig
变量进行保存。 - 第三步:将 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,并使用自身的ChildFragmentManager
中mNonConfig
变量进行保存。
当 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移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
题外话,我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料。
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
【Android进阶学习视频】、【全套Android面试秘籍】
希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算