现代码构造
主界面由ViewPager + Fragment
构成,ViewPager
和Fragment
均在Activity
的onCreate
中进行初始化。
现象
APP长时间运行在后台,当内存不足被系统回收掉之后,此时从任务界面再次启动APP,APP此时的行为,经过初始化界面再次打开主界面。此时判断在代码里面可以判断到,当前所显示的Fragment
和新创建的Fragment
不是同一个对象。
简单分析
经过日志打印分析发现,虽然此时新创建了Fragment
对象,但是APP
展示在前台使用的还是APP
被回收之前的Fragment
对象。
源码分析
怎么回事呢?APP
进程在后台都已经被回收了,但是Fragment
却被保存了。怎么回事呢
思考一下,一般情况下,如果我们自己需要在Activity
被系统回收的时候,保存东西,那么我们会写在哪里呢
没错,重写onSaveInstanceState
方法。那会不会是系统替我们保存了呢?emm,往上找一找源码吧!
不负所望,最终分别在FragmentActivity
和Activity
中找到了和Fragment
相关的保存。️️️️️️
而这两个地方保存的key
分别是android:support:fragments
与android:fragments
。分析到这里就可以解决问题了,还需要更深入的分析,请看最后面的额外分析。下面先看解决方案。
解决方案
既然系统帮我们保存了,但是我希望每次启动Activity
都使用新创建的Fragment
,所以最简单的方案就是,将保存的Fragment
置null
。
方案一
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
//直接将保存的`Fragment`置null
outState.putParcelable("android:support:fragments", null);
outState.putParcelable("android:fragments",null);
}
那么还有更狠一点的方案,如果你确定只要Activity被回收之后,就全部重来,不进行状态保存,那么直接清除Bundle
即可。简单粗暴
方案二
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.clear();
}
经测试,两种处理方案,均可以完美处理Fragment
对象覆盖的问题。看到这里本篇博客就完了,下面是额外的源码分析,感兴趣的话,继续看下去吧*️
额外分析
下面进行额外分析,系统是如何将保存的Fragment
进行复用了呢
好,首先先看一下保存的mFragments.saveAllState()
返回的Parcelable
是个什么东西。点进去瞅瞅
FragmentController 类
public Parcelable saveAllState() {
return mHost.mFragmentManager.saveAllState();
}
emm,上面的代码简单分析一下,mHost
指的是在FragmentActivity
里面的内部类HostCallbacks
,mFragmentManager
指的是FragmentManagerImpl
(这里的分析很简单,大家自己跟一下代码就可以找到了)。所以最终调用到了FragmentManagerImpl
的saveAllState()
方法。
FragmentManagerImpl
Parcelable saveAllState() {
...
ArrayList<FragmentState> active = new ArrayList<>(size);
boolean haveFragments = false;
for (Fragment f : mActive.values()) {
...
active.add(fs);
}
...
//最终返回了FragmentManagerState 而 FragmentManagerState里面包含了FragmentState
FragmentManagerState fms = new FragmentManagerState();
fms.mActive = active;
...
return fms;
...
}
省略了一些非核心的代码,现在可以很直观的看出来最终返回了FragmentManagerState
,而FragmentManagerState
里面包含了FragmentState
,所以跟进来看一下FragmentState
是什么东西。
final class FragmentState implements Parcelable {
final String mClassName;
final String mWho;
final boolean mFromLayout;
final int mFragmentId;
final int mContainerId;
final String mTag;
...
Bundle mSavedFragmentState;
Fragment mInstance;
...
可以很直观的看到,这玩意其实就是一个实体类,实现了Parcelable
,里面包含了Fragment
的实例以及其相关的各种信息。
所以,关于保存总结一下,Fragment
会以FragmentState
的形式形成集合,放到FragmentManagerState
中最终返回出去,通过onSaveInstanceState
保存到Bundle
中。
那么在哪里恢复呢?我们在onCreate
中找一下。
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreSaveState(p);
...
mFragments.dispatchCreate();
}
有两个很关键的方法,restoreSaveState
和dispatchCreate
,通过上面的分析,我们直到这里调用到了FragmentManagerImpl
里面对应的方法,我们找一下
FragmentManagerImpl
void restoreSaveState(Parcelable state) {
//这里判断,如果获取为null的话,则return了,所以我们的解决方案里面最后传入null,这里就不会走了,也可以在调用onCreate的super之前传入null,也可以自己给fragment设置tag,尝试自己获取恢复的fragment,总之,直到原理之后,怎么样都好解决。
if (state == null) return;
FragmentManagerState fms = (FragmentManagerState)state;
if (fms.mActive == null) return;
for (Fragment f : mNonConfig.getRetainedFragments()) {
if (DEBUG) Log.v(TAG, "restoreSaveState: re-attaching retained " + f);
FragmentState fs = null;
for (FragmentState fragmentState : fms.mActive) {
...
public void dispatchCreate() {
...
dispatchStateChange(Fragment.CREATED);
}
private void dispatchStateChange(int nextState) {
try {
mExecutingActions = true;
moveToState(nextState, false);
} finally {
mExecutingActions = false;
}
execPendingActions();
}
这一块代码简化一下很简单,首先调用restoreSaveState
,拿到FragmentManagerState
进而拿到FragmentState
。然后恢复所有的Fragment
。之后调用了dispatchCreate
,传入了CREATED
调用moveToState
方法,开始进行我们熟知的Fragment
生命周期调用。
这里稍微插一句,可以看到restoreSaveState
方法入口直接进行了判断,如果获取为null的话,则return了,所以我们的解决方案里面最后传入null,方法就不会走了;也可以在调用onCreate的super之前传入null;也可以自己给fragment设置tag,尝试自己获取恢复的fragment。总之,知道原理之后,怎么样都好解决。
创作不易,如有帮助一键三连咯*️。欢迎技术探讨噢!
文章评论