Activity recreate重建导致Fragment多次初始化问题探讨
现象:Activity recreate重建导致Fragment多次初始化,甚至造成Fragment白屏
参考:http://www.apkfuns.com/fragmentactivity-recreate-cause-multiple-initialization/
起因
我们先来看段代码(仅关键代码)
public class TestActivity extends FragmentActivity {
...
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.xxxx);
getSupportFragmentManager().beginTransaction()
.replace(R.id.content, new TestFragment())
.commit();
}
...
}
public class TestFragment extends Fragment {
public TestFragment() {
Log.d("TestFragment", "new TestFragment");
}
...
}
运行,并切换横竖屏 或者 切换系统语言,请问TestFragment的构造函数会执行几次呢?(注意:Manifest中没有配置configChanges属性)
06-01 10:34:37.764 24689-24689/com.apkfuns.androiddemo D/TestFragment: new TestFragment
06-01 10:34:37.774 24689-24689/com.apkfuns.androiddemo D/TestFragment: new TestFragment
答案是执行两次,为什么呢?我们都知道不配置configChanges
情况下当前Activity会重启,就是先销毁再创建,按正常流程来说不就一次onCreate
初始化吗?销毁并不会创建对象,下面我们慢慢来分析!
分析
经过分析发现,TestFragment构造函数被调用了两次,除了我们主动在TestActivity调用的,另外的一次是更早的调用,执行时间在重新启动的onCreate()
和onStart()
之间,更准确的说是在super.onCreate(savedInstanceState);
里面创建的。我们看下FragmentActivity.onCreate
的实现
/**
* Perform initialization of all fragments and loaders.
*/
@SuppressWarnings("deprecation")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);
super.onCreate(savedInstanceState);
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mFragments.restoreLoaderNonConfig(nc.loaders);
}
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
// Check if there are any pending onActivityResult calls to descendent Fragments.
if (savedInstanceState.containsKey(NEXT_CANDIDATE_REQUEST_INDEX_TAG)) {
mNextCandidateRequestIndex =
savedInstanceState.getInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG);
int[] requestCodes = savedInstanceState.getIntArray(ALLOCATED_REQUEST_INDICIES_TAG);
String[] fragmentWhos = savedInstanceState.getStringArray(REQUEST_FRAGMENT_WHO_TAG);
if (requestCodes == null || fragmentWhos == null ||
requestCodes.length != fragmentWhos.length) {
Log.w(TAG, "Invalid requestCode mapping in savedInstanceState.");
} else {
mPendingFragmentActivityResults = new SparseArrayCompat<>(requestCodes.length);
for (int i = 0; i < requestCodes.length; i++) {
mPendingFragmentActivityResults.put(requestCodes[i], fragmentWhos[i]);
}
}
}
}
if (mPendingFragmentActivityResults == null) {
mPendingFragmentActivityResults = new SparseArrayCompat<>();
mNextCandidateRequestIndex = 0;
}
mFragments.dispatchCreate();
}
从savedInstanceState
读取Parcelable对象,并恢复到fragment(mFragments.restoreAllState
)。我们再看下FRAGMENTS_TAG
是哪里设置的呢?
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mPendingFragmentActivityResults.size() > 0) {
outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);
int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
}
outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
}
}
在onSaveInstanceState
回调里我们看到把Fragment信息保存到Parcelable
对象。然后恢复的时候传递到onCreate
里面的Bundle savedInstanceState
,系统在super.onCreate()
帮我们恢复了,所以导致Fragment被实例了两次。
还记得Android Studio
中Fragment没有无参构造函数的提示的错误吗?系统恢复的时候就是调用无参构造函数。
解决方案
方案1:从恢复的fragments里面读取,不重新创建
Fragment fragment = getSupportFragmentManager().findFragmentByTag("tag");
if (fragment == null) {
fragment = new TestFragment();
getSupportFragmentManager().beginTransaction()
.replace(R.id.content, fragment, "tag")
.commit();
}
如果不知道fragment id
或者name
的情况下(比如使用FragmentPagerAdapter
)可以使用
getSupportFragmentManager().getFragments()
方案2:阻止Activity恢复fragment数据
- 设置fragment不恢复
fragment.setRetainInstance(true);
- 或者最粗暴简单的方式
@Override
protected void onSaveInstanceState(Bundle outState) {
// super.onSaveInstanceState(outState);
}
- 或者从saveInstance删除目标fragment
@Override
protected void onSaveInstanceState(Bundle outState) {
getSupportFragmentManager().beginTransaction()
.remove(fragment).commitAllowingStateLoss();
super.onSaveInstanceState(outState);
}
参考文档
Activity后台运行一段时间回来crash问题的分析与解决
Fragment全解析系列(一):那些年踩过的坑
Android实战技巧:Fragment的那些坑
android源码分析(一) - 语言切换机制
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/25/discussion-on-the-multiple-initialization-of-fragments-caused-by-activity-recreate-reconstruction/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论