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

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);
}

0x4 参考文档