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全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Activity recreate重建导致Fragment多次初始化问题探讨
现象:Activity recreate重建导致Fragment多次初始化,甚至造成Fragment白屏 参考:http://www.apkfuns.com/fragmentactivity-recreate-cause-multiple-initia……
<<上一篇
下一篇>>
文章目录
关闭
目 录