最近项目测试同学反馈,App在进入二级页面后,然后退出到后台,过一段时间后,将App拉回到前台,会发现首页空白的情况。我们的App首页是ViewPager+Fragment的结构,所以怀疑肯定是长期在后台Activity和Fragment在被系统回收,在回到前台Activity和Fragment采用默认的恢复机制导致界面恢复逻辑异常导致的问题,如实就有了这篇文章,旨在记录解决问题的过程,同时研究一下系统自动恢复机制的原理。
本文代码对应API版本为: API 30
本文链接:https://rainmonth.github.io/posts/A220330.html
被系统回收的时机
- 应用处于后台是, 手动点击 Android Studio Logcat 的 Terminated Application 按钮会触发Activity的回收;
- 系统内存不足时,会回收处于后台的Activity;
系统保存状态的时机
什么时候需要进行状态的保存
在调用Activity调用onStop时,不是一定会调用 onSaveInstance的,它是有条件的,条件如下:1
final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null&& !r.isPreHoneycomb();
其中 saveState 是callActivityOnStop传递进来的参数,而callActivityOnStop只有在被
handleRelaunchActivityInner
方法调用时传递的这个值才为true
即onSaveInstance(Bundle outState)
的调用时机,根据该方法的注释说明,Android P(API>= 28)以后,该方法在在onStop之后调用,在之前的版本中,可以确保该方法在onStop之前调用,但不能保证它是在onPause之前还是之后调用,主要通过ActivityThread的callActivityOnStop来保证。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) {
// Before P onSaveInstanceState was called before onStop, starting with P it's
// called after. Before Honeycomb state was always saved before onPause.
final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null
&& !r.isPreHoneycomb();
final boolean isPreP = r.isPreP();
if (shouldSaveState && isPreP) {// 需要保持状态,并且是Android p 以前的设备,先调用 callActivityOnSaveInstanceState
callActivityOnSaveInstanceState(r);
}
try {
r.activity.performStop(r.mPreserveWindow, reason);// 执行 onStop
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to stop activity "
+ r.intent.getComponent().toShortString()
+ ": " + e.toString(), e);
}
}
r.setState(ON_STOP);
if (shouldSaveState && !isPreP) {// Android P 以后的设备 ,在调用 onStop后调用 onSaveInstanceState
callActivityOnSaveInstanceState(r);
}
}
都保存了什么
Activity#onSaveInstanceState
1 | protected void onSaveInstanceState( { Bundle outState) |
主要做了以下几种操作:
- 调用
mWindow.saveHierarchyState()
(PhoneWindow), 并将返回的结果保存在Bundle中; - 调用
mFragments.saveAllState()
(FragmentController),如果返回值不为空,保存在Bundle中; - 如果需要,调用
AutoFillManager
的onSaveInstanceState
方法; - 将回调分发出去;
PhoneWindow#saveHierarchyState
1 | public Bundle saveHierarchyState() { |
- 通过ViewGroup,将 saveHierarchyState 一级一级分发下去,并最终调用View 的 saveHierarchyState方法;
- 保存焦点view View的id;
- 调用
savePanelState
方法,保存 Panel 的状态一遍在 restore 时恢复; - 保存 ToolBar的View层级信息;
FragmentController#saveAllState()
最终调用的时FragmentManager的saveAllState()
方法
- 找到所有 active 状态的Fragment,并调用
saveFragmentBasicState
方法保存所有状态在Fragment.INITIALIZING
之上的Fragment的状态;调用 FragmentManager 的 saveFragmentBasicState 方法,保存 Fragment 的基本信息;
- 里面会将 onSaveInstance 消息分发出去,调用 Fragment的 onSaveInstance 方法;
- 保存Fragment 中 View 的状态,具体通过 FragmentManager的
saveFragmentViewState
方法; - 通过View的
saveHierarchyState
方法 来调用 View的onSaveInstanceState
方法
- 构建已经添加的Fragment 列表;
- 保存 Fragment 的回退栈;
- 构造 FragmentManagerState 对象并返回;
ViewPager的Adapter
ViewPager的Adapter通常有两种,分别是FragmentPagerAdapter
和FragmentStatePagerAdapter
,二者都是PagerAdapter
的子类;
PagerAdapter
PagerAdapter是一个模板类,定义了几个模板方法,包括:
这四个方法子类必须自己重写
- public abstract int getCount(),ViewPager Item的个数;
- public abstract boolean isViewFromObject(@NonNull View var1, @NonNull Object var2),判断View 和 Object的关联关系;
- public Object instantiateItem(@NonNull ViewGroup container, int position),向container的position位置添加View;
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object),从container的position位置移除;
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object);
- public void startUpdate(@NonNull ViewGroup container),ViewPager数据改变会调用这个方法,这个方法一般不作任何处理,默认实现中也就检查了一下容器View是否设置了id;
- public void finishUpdate(@NonNull ViewGroup container),页面数据改变完成后调用这个方法,需要确保改变生效(例如如果是Fragment这里就要进行Fragment事物的提交);
- public Parcelable saveState(),负责ViewPager中具体Item的状态保存;
- public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader), 负责ViewPager中具体Item的状态恢复;
和我们要谈的状态保存与恢复密切相关的两个方法就是saveState
和restoreState
了。ViewPager中这两个方法的调用时机如下:
saveState
,在触发View的onSaveInstance调用的时候,最终会走到ViewPager的onSaveInstance方法,这个方法里面会调用PagerAdapter的saveState方法;restoreState
,这个方法会在View的onRestoreInstanceState调用的时候调用,调用时如果PagerAdapter不为空,直接调用给方法,如果为空会在setAdapter时进行调用;FragmentPagerAdapter
这个PagerAdapter实现中,上面提到的两个方法都是空实现,即没有进行状态的保存与恢复处理;FragmentStatePageAdapter
这个PagerAdapter的实现中,上面两个方法都进行了重写;
虽然说FragmentPagerAdapter没有重写saveState和restoreState方法,但这并不代表在被回收时其关联的ViewPager绑定的Fragment就不会恢复,实际上,Activity因内存不足被系统回收时,默认是会通过Fragment的构造函数来重新实例化Fragment的。要避免这个情况,我们可以在两个点进行处理(其实都是通过清除Bundle中的Fragment信息来达到目的):
- 在Activity的onSaveInstance中处理,系统保存后立即清除保存在Bundle中的Fragment(s)
- 在Activity的onRestoreInstance中处理,系统恢复时先清除Bundle中的Fragment(s)在恢复;
更好的解决方案还是从FragmentManager中渠道保存的Fragment信息来进行处理,然后进行恢复。可以看考FragmentStatePagerAdapter
中instantiateItem
方法的实现,即通过fragment TAG来构造、获取Fragment。
恢复解决方案
- Activity不保存Fragment状态,在Activity恢复的时,直接重建Fragment
- Activity保存Fragment状态,恢复的时候从FragmentManager中获取Fragment状态值,然后恢复;
推荐用第二种。
小结
本文结合实际工作中的问题,对异常回收的Activity、Fragment的状态保存和恢复结合源码做了一些分析,然后给出了一些实际问题是的解决方案:
- 采用系统的策略,根据tag同FragmentManager中找(推荐);
- 不采用系统的策略,需要恢复时重新创建(不推荐,因为这样需要记录一些额外的变量来区分是正常的场景进入还是从恢复的场景进入);