Activity recreate重建导致Fragment多次初始化问题探讨

现象:Activity recreate重建导致Fragment多次初始化,甚至造成Fragment白屏
参考:http://www.apkfuns.com/fragmentactivity-recreate-cause-multiple-initialization/

起因

我们先来看段代码(仅关键代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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属性)

1
2
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的实现

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 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是哪里设置的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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里面读取,不重新创建

1
2
3
4
5
6
7
Fragment fragment = getSupportFragmentManager().findFragmentByTag("tag");  
if (fragment == null) {
fragment = new TestFragment();
getSupportFragmentManager().beginTransaction()
.replace(R.id.content, fragment, "tag")
.commit();
}

如果不知道fragment id或者name的情况下(比如使用FragmentPagerAdapter)可以使用

1
getSupportFragmentManager().getFragments()

方案2:阻止Activity恢复fragment数据

  • 设置fragment不恢复
1
fragment.setRetainInstance(true);
  • 或者最粗暴简单的方式
1
2
3
4
@Override
protected void onSaveInstanceState(Bundle outState) {
// super.onSaveInstanceState(outState);
}
  • 或者从saveInstance删除目标fragment
1
2
3
4
5
6
@Override
protected void onSaveInstanceState(Bundle outState) {
getSupportFragmentManager().beginTransaction()
.remove(fragment).commitAllowingStateLoss();
super.onSaveInstanceState(outState);
}

参考文档

Activity后台运行一段时间回来crash问题的分析与解决
Fragment全解析系列(一):那些年踩过的坑
Android实战技巧:Fragment的那些坑
android源码分析(一) - 语言切换机制

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :