From bb3aa34055be35676e6557210f894b1e0ead4e0a Mon Sep 17 00:00:00 2001 From: Adam Powell Date: Wed, 16 Nov 2016 10:34:57 -0800 Subject: [PATCH] Fix bugs around inflated child fragments Framework edition * Ensure that a fragment that has had its view destroyed has a chance to recreate it. * Clear mInLayout after views are destroyed * Make sure that when a fragment is restored from the back stack and reinflates its view that child fragments from fragment tags are reconnected properly. Bug: 32691935 Test: cts Change-Id: I370d6db46e72fd8ac35f716e2130b9e10b93b1da --- core/java/android/app/Fragment.java | 8 +- core/java/android/app/FragmentManager.java | 122 +++++++++++---------- 2 files changed, 73 insertions(+), 57 deletions(-) diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 8124232bd49ec..10ab2bc715904 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -414,6 +414,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene // True if this fragment has been restored from previously saved state. boolean mRestored; + // True if performCreateView has been called and a matching call to performDestroyView + // has not yet happened. + boolean mPerformedCreateView; + // Number of active back stack entries this fragment is in. int mBackStackNesting; @@ -611,7 +615,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene Fragment f = (Fragment)clazz.newInstance(); if (args != null) { args.setClassLoader(f.getClass().getClassLoader()); - f.mArguments = args; + f.setArguments(args); } return f; } catch (ClassNotFoundException e) { @@ -2464,6 +2468,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene if (mChildFragmentManager != null) { mChildFragmentManager.noteStateNotSaved(); } + mPerformedCreateView = true; return onCreateView(inflater, container, savedInstanceState); } @@ -2690,6 +2695,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene if (mLoaderManager != null) { mLoaderManager.doReportNextStart(); } + mPerformedCreateView = false; } void performDestroy() { diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 07ef136e7ac51..c7d6a4843f609 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -1072,7 +1072,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) { newState = Fragment.STOPPED; } - if (f.mState < newState) { + if (f.mState <= newState) { // For fragments that are created from a layout, when restoring from // state we don't want to allow them to be created until they are // being reloaded from the layout. @@ -1089,65 +1089,59 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } switch (f.mState) { case Fragment.INITIALIZING: - if (DEBUG) Log.v(TAG, "moveto CREATED: " + f); - if (f.mSavedFragmentState != null) { - f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray( - FragmentManagerImpl.VIEW_STATE_TAG); - f.mTarget = getFragment(f.mSavedFragmentState, - FragmentManagerImpl.TARGET_STATE_TAG); - if (f.mTarget != null) { - f.mTargetRequestCode = f.mSavedFragmentState.getInt( - FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0); - } - f.mUserVisibleHint = f.mSavedFragmentState.getBoolean( - FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true); - if (!f.mUserVisibleHint) { - f.mDeferStart = true; - if (newState > Fragment.STOPPED) { - newState = Fragment.STOPPED; + if (newState > Fragment.INITIALIZING) { + if (DEBUG) Log.v(TAG, "moveto CREATED: " + f); + if (f.mSavedFragmentState != null) { + f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray( + FragmentManagerImpl.VIEW_STATE_TAG); + f.mTarget = getFragment(f.mSavedFragmentState, + FragmentManagerImpl.TARGET_STATE_TAG); + if (f.mTarget != null) { + f.mTargetRequestCode = f.mSavedFragmentState.getInt( + FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0); + } + f.mUserVisibleHint = f.mSavedFragmentState.getBoolean( + FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true); + if (!f.mUserVisibleHint) { + f.mDeferStart = true; + if (newState > Fragment.STOPPED) { + newState = Fragment.STOPPED; + } } } - } - f.mHost = mHost; - f.mParentFragment = mParent; - f.mFragmentManager = mParent != null - ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl(); - dispatchOnFragmentPreAttached(f, mHost.getContext(), false); - f.mCalled = false; - f.onAttach(mHost.getContext()); - if (!f.mCalled) { - throw new SuperNotCalledException("Fragment " + f - + " did not call through to super.onAttach()"); - } - if (f.mParentFragment == null) { - mHost.onAttachFragment(f); - } else { - f.mParentFragment.onAttachFragment(f); - } - dispatchOnFragmentAttached(f, mHost.getContext(), false); - - if (!f.mRetaining) { - f.performCreate(f.mSavedFragmentState); - dispatchOnFragmentCreated(f, f.mSavedFragmentState, false); - } else { - f.restoreChildFragmentState(f.mSavedFragmentState, true); - f.mState = Fragment.CREATED; - } - f.mRetaining = false; - if (f.mFromLayout) { - // For fragments that are part of the content view - // layout, we need to instantiate the view immediately - // and the inflater will take care of adding it. - f.mView = f.performCreateView(f.getLayoutInflater( - f.mSavedFragmentState), null, f.mSavedFragmentState); - if (f.mView != null) { - f.mView.setSaveFromParentEnabled(false); - if (f.mHidden) f.mView.setVisibility(View.GONE); - f.onViewCreated(f.mView, f.mSavedFragmentState); - dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState, false); + f.mHost = mHost; + f.mParentFragment = mParent; + f.mFragmentManager = mParent != null + ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl(); + dispatchOnFragmentPreAttached(f, mHost.getContext(), false); + f.mCalled = false; + f.onAttach(mHost.getContext()); + if (!f.mCalled) { + throw new SuperNotCalledException("Fragment " + f + + " did not call through to super.onAttach()"); } + if (f.mParentFragment == null) { + mHost.onAttachFragment(f); + } else { + f.mParentFragment.onAttachFragment(f); + } + dispatchOnFragmentAttached(f, mHost.getContext(), false); + + if (!f.mRetaining) { + f.performCreate(f.mSavedFragmentState); + dispatchOnFragmentCreated(f, f.mSavedFragmentState, false); + } else { + f.restoreChildFragmentState(f.mSavedFragmentState, true); + f.mState = Fragment.CREATED; + } + f.mRetaining = false; } case Fragment.CREATED: + // This is outside the if statement below on purpose; we want this to run + // even if we do a moveToState from CREATED => *, CREATED => CREATED, and + // * => CREATED as part of the case fallthrough above. + ensureInflatedFragmentView(f); + if (newState > Fragment.CREATED) { if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f); if (!f.mFromLayout) { @@ -1281,6 +1275,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } f.mContainer = null; f.mView = null; + f.mInLayout = false; } case Fragment.CREATED: if (newState < Fragment.CREATED) { @@ -1340,6 +1335,19 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate moveToState(f, mCurState, 0, 0, false); } + void ensureInflatedFragmentView(Fragment f) { + if (f.mFromLayout && !f.mPerformedCreateView) { + f.mView = f.performCreateView(f.getLayoutInflater( + f.mSavedFragmentState), null, f.mSavedFragmentState); + if (f.mView != null) { + f.mView.setSaveFromParentEnabled(false); + if (f.mHidden) f.mView.setVisibility(View.GONE); + f.onViewCreated(f.mView, f.mSavedFragmentState); + dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState, false); + } + } + } + /** * Fragments that have been shown or hidden don't have their visibility changed or * animations run during the {@link #showFragment(Fragment)} or {@link #hideFragment(Fragment)} @@ -3262,7 +3270,9 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } // If we haven't finished entering the CREATED state ourselves yet, - // push the inflated child fragment along. + // push the inflated child fragment along. This will ensureInflatedFragmentView + // at the right phase of the lifecycle so that we will have mView populated + // for compliant fragments below. if (mCurState < Fragment.CREATED && fragment.mFromLayout) { moveToState(fragment, Fragment.CREATED, 0, 0, false); } else {