0x1 起因
我们先来看段代码(仅关键代码)
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 初始化吗?销毁并不会创建对象,下面我们慢慢来分析!
0x2 分析
经过分析发现,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被实例了两次。
还记得这个fragment没有无参构造函数的提示的错误吗? 系统恢复的时候就是调用无参构造函数
0x3 解决方案
方案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);
}