diff options
| author | 2016-02-04 16:20:37 -0800 | |
|---|---|---|
| committer | 2016-02-08 19:10:44 +0000 | |
| commit | 44ba79e47d6db54e5501f994880fa09eb880c185 (patch) | |
| tree | 4d20af401f2ee2a8d69f145260a0fab1ea612ce8 | |
| parent | 8585ed66b947ef30d6a43f0bb91885ed0c5ebfb4 (diff) | |
Permit setRetainInstance(true) on nested fragments, framework edition
Save arbitrarily nested fragments across config changes as
nonconfiguration objects. This permits the use of retain-instance
child fragments as arbitrary opaque dependencies within other
fragments.
Change-Id: Ia6640b76cfcf7ec28ba252628957a0c14863e957
(cherry picked from commit 7466be66263d5ebffb786ea402d9ed6e36c254f0)
| -rw-r--r-- | api/current.txt | 9 | ||||
| -rw-r--r-- | api/system-current.txt | 9 | ||||
| -rw-r--r-- | api/test-current.txt | 9 | ||||
| -rw-r--r-- | core/java/android/app/Activity.java | 4 | ||||
| -rw-r--r-- | core/java/android/app/Fragment.java | 64 | ||||
| -rw-r--r-- | core/java/android/app/FragmentController.java | 26 | ||||
| -rw-r--r-- | core/java/android/app/FragmentManager.java | 72 | ||||
| -rw-r--r-- | core/java/android/app/FragmentManagerNonConfig.java | 54 |
8 files changed, 187 insertions, 60 deletions
diff --git a/api/current.txt b/api/current.txt index 7779ec17d2ef..ae343cdc5e8b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4531,10 +4531,12 @@ package android.app { method public void noteStateNotSaved(); method public android.view.View onCreateView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet); method public void reportLoaderStart(); - method public void restoreAllState(android.os.Parcelable, java.util.List<android.app.Fragment>); + method public deprecated void restoreAllState(android.os.Parcelable, java.util.List<android.app.Fragment>); + method public void restoreAllState(android.os.Parcelable, android.app.FragmentManagerNonConfig); method public void restoreLoaderNonConfig(android.util.ArrayMap<java.lang.String, android.app.LoaderManager>); method public android.util.ArrayMap<java.lang.String, android.app.LoaderManager> retainLoaderNonConfig(); - method public java.util.List<android.app.Fragment> retainNonConfig(); + method public android.app.FragmentManagerNonConfig retainNestedNonConfig(); + method public deprecated java.util.List<android.app.Fragment> retainNonConfig(); method public android.os.Parcelable saveAllState(); } @@ -4594,6 +4596,9 @@ package android.app { method public abstract void onBackStackChanged(); } + public class FragmentManagerNonConfig { + } + public abstract class FragmentTransaction { ctor public FragmentTransaction(); method public abstract android.app.FragmentTransaction add(android.app.Fragment, java.lang.String); diff --git a/api/system-current.txt b/api/system-current.txt index 1f9d2916f04d..bcd39fe3272e 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4664,10 +4664,12 @@ package android.app { method public void noteStateNotSaved(); method public android.view.View onCreateView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet); method public void reportLoaderStart(); - method public void restoreAllState(android.os.Parcelable, java.util.List<android.app.Fragment>); + method public deprecated void restoreAllState(android.os.Parcelable, java.util.List<android.app.Fragment>); + method public void restoreAllState(android.os.Parcelable, android.app.FragmentManagerNonConfig); method public void restoreLoaderNonConfig(android.util.ArrayMap<java.lang.String, android.app.LoaderManager>); method public android.util.ArrayMap<java.lang.String, android.app.LoaderManager> retainLoaderNonConfig(); - method public java.util.List<android.app.Fragment> retainNonConfig(); + method public android.app.FragmentManagerNonConfig retainNestedNonConfig(); + method public deprecated java.util.List<android.app.Fragment> retainNonConfig(); method public android.os.Parcelable saveAllState(); } @@ -4727,6 +4729,9 @@ package android.app { method public abstract void onBackStackChanged(); } + public class FragmentManagerNonConfig { + } + public abstract class FragmentTransaction { ctor public FragmentTransaction(); method public abstract android.app.FragmentTransaction add(android.app.Fragment, java.lang.String); diff --git a/api/test-current.txt b/api/test-current.txt index 6e6f99ac5f66..c1cd2750424e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -4531,10 +4531,12 @@ package android.app { method public void noteStateNotSaved(); method public android.view.View onCreateView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet); method public void reportLoaderStart(); - method public void restoreAllState(android.os.Parcelable, java.util.List<android.app.Fragment>); + method public deprecated void restoreAllState(android.os.Parcelable, java.util.List<android.app.Fragment>); + method public void restoreAllState(android.os.Parcelable, android.app.FragmentManagerNonConfig); method public void restoreLoaderNonConfig(android.util.ArrayMap<java.lang.String, android.app.LoaderManager>); method public android.util.ArrayMap<java.lang.String, android.app.LoaderManager> retainLoaderNonConfig(); - method public java.util.List<android.app.Fragment> retainNonConfig(); + method public android.app.FragmentManagerNonConfig retainNestedNonConfig(); + method public deprecated java.util.List<android.app.Fragment> retainNonConfig(); method public android.os.Parcelable saveAllState(); } @@ -4594,6 +4596,9 @@ package android.app { method public abstract void onBackStackChanged(); } + public class FragmentManagerNonConfig { + } + public abstract class FragmentTransaction { ctor public FragmentTransaction(); method public abstract android.app.FragmentTransaction add(android.app.Fragment, java.lang.String); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index e0eb3895b3d2..622012ecf2e2 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -753,7 +753,7 @@ public class Activity extends ContextThemeWrapper static final class NonConfigurationInstances { Object activity; HashMap<String, Object> children; - List<Fragment> fragments; + FragmentManagerNonConfig fragments; ArrayMap<String, LoaderManager> loaders; VoiceInteractor voiceInteractor; } @@ -2070,7 +2070,7 @@ public class Activity extends ContextThemeWrapper NonConfigurationInstances retainNonConfigurationInstances() { Object activity = onRetainNonConfigurationInstance(); HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); - List<Fragment> fragments = mFragments.retainNonConfig(); + FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig(); ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig(); if (activity == null && children == null && fragments == null && loaders == null && mVoiceInteractor == null) { diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 688c9f8e4fc4..02a898b33dd1 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -97,35 +97,34 @@ final class FragmentState implements Parcelable { mSavedFragmentState = in.readBundle(); } - public Fragment instantiate(FragmentHostCallback host, Fragment parent) { - if (mInstance != null) { - return mInstance; - } - - final Context context = host.getContext(); - if (mArguments != null) { - mArguments.setClassLoader(context.getClassLoader()); - } + public Fragment instantiate(FragmentHostCallback host, Fragment parent, + FragmentManagerNonConfig childNonConfig) { + if (mInstance == null) { + final Context context = host.getContext(); + if (mArguments != null) { + mArguments.setClassLoader(context.getClassLoader()); + } - mInstance = Fragment.instantiate(context, mClassName, mArguments); - - if (mSavedFragmentState != null) { - mSavedFragmentState.setClassLoader(context.getClassLoader()); - mInstance.mSavedFragmentState = mSavedFragmentState; - } - mInstance.setIndex(mIndex, parent); - mInstance.mFromLayout = mFromLayout; - mInstance.mRestored = true; - mInstance.mFragmentId = mFragmentId; - mInstance.mContainerId = mContainerId; - mInstance.mTag = mTag; - mInstance.mRetainInstance = mRetainInstance; - mInstance.mDetached = mDetached; - mInstance.mHidden = mHidden; - mInstance.mFragmentManager = host.mFragmentManager; - if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, - "Instantiated fragment " + mInstance); + mInstance = Fragment.instantiate(context, mClassName, mArguments); + if (mSavedFragmentState != null) { + mSavedFragmentState.setClassLoader(context.getClassLoader()); + mInstance.mSavedFragmentState = mSavedFragmentState; + } + mInstance.setIndex(mIndex, parent); + mInstance.mFromLayout = mFromLayout; + mInstance.mRestored = true; + mInstance.mFragmentId = mFragmentId; + mInstance.mContainerId = mContainerId; + mInstance.mTag = mTag; + mInstance.mRetainInstance = mRetainInstance; + mInstance.mDetached = mDetached; + mInstance.mHidden = mHidden; + mInstance.mFragmentManager = host.mFragmentManager; + if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, + "Instantiated fragment " + mInstance); + } + mInstance.mChildNonConfig = childNonConfig; return mInstance; } @@ -433,6 +432,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene // Private fragment manager for child fragments inside of this one. FragmentManagerImpl mChildFragmentManager; + // For use when restoring fragment state and descendant fragments are retained. + // This state is set by FragmentState.instantiate and cleared in onCreate. + FragmentManagerNonConfig mChildNonConfig; + // If this Fragment is contained in another Fragment, this is that container. Fragment mParentFragment; @@ -975,10 +978,6 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * </ul> */ public void setRetainInstance(boolean retain) { - if (retain && mParentFragment != null) { - throw new IllegalStateException( - "Can't retain fragements that are nested in other fragments"); - } mRetainInstance = retain; } @@ -1436,7 +1435,8 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene if (mChildFragmentManager == null) { instantiateChildFragmentManager(); } - mChildFragmentManager.restoreAllState(p, null); + mChildFragmentManager.restoreAllState(p, mChildNonConfig); + mChildNonConfig = null; mChildFragmentManager.dispatchCreate(); } } diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java index a9270bc43e09..57b0ff134818 100644 --- a/core/java/android/app/FragmentController.java +++ b/core/java/android/app/FragmentController.java @@ -117,16 +117,40 @@ public class FragmentController { * instances retained across configuration changes. * * @see #retainNonConfig() + * + * @deprecated use {@link #restoreAllState(Parcelable, FragmentManagerNonConfig)} */ public void restoreAllState(Parcelable state, List<Fragment> nonConfigList) { - mHost.mFragmentManager.restoreAllState(state, nonConfigList); + mHost.mFragmentManager.restoreAllState(state, + new FragmentManagerNonConfig(nonConfigList, null)); + } + + /** + * Restores the saved state for all Fragments. The given FragmentManagerNonConfig are Fragment + * instances retained across configuration changes, including nested fragments + * + * @see #retainNestedNonConfig() + */ + public void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) { + mHost.mFragmentManager.restoreAllState(state, nonConfig); } /** * Returns a list of Fragments that have opted to retain their instance across * configuration changes. + * + * @deprecated use {@link #retainNestedNonConfig()} to also track retained + * nested child fragments */ public List<Fragment> retainNonConfig() { + return mHost.mFragmentManager.retainNonConfig().getFragments(); + } + + /** + * Returns a nested tree of Fragments that have opted to retain their instance across + * configuration changes. + */ + public FragmentManagerNonConfig retainNestedNonConfig() { return mHost.mFragmentManager.retainNonConfig(); } diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 8b5dbaef7452..4c8761c86b93 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -1672,24 +1672,47 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } return true; } - - ArrayList<Fragment> retainNonConfig() { + + FragmentManagerNonConfig retainNonConfig() { ArrayList<Fragment> fragments = null; + ArrayList<FragmentManagerNonConfig> childFragments = null; if (mActive != null) { for (int i=0; i<mActive.size(); i++) { Fragment f = mActive.get(i); - if (f != null && f.mRetainInstance) { - if (fragments == null) { - fragments = new ArrayList<Fragment>(); + if (f != null) { + if (f.mRetainInstance) { + if (fragments == null) { + fragments = new ArrayList<>(); + } + fragments.add(f); + f.mRetaining = true; + f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1; + if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f); + } + boolean addedChild = false; + if (f.mChildFragmentManager != null) { + FragmentManagerNonConfig child = f.mChildFragmentManager.retainNonConfig(); + if (child != null) { + if (childFragments == null) { + childFragments = new ArrayList<>(); + for (int j = 0; j < i; j++) { + childFragments.add(null); + } + } + childFragments.add(child); + addedChild = true; + } + } + if (childFragments != null && !addedChild) { + childFragments.add(null); } - fragments.add(f); - f.mRetaining = true; - f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1; - if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f); } } } - return fragments; + if (fragments == null && childFragments == null) { + return null; + } + return new FragmentManagerNonConfig(fragments, childFragments); } void saveFragmentViewState(Fragment f) { @@ -1846,18 +1869,23 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate return fms; } - void restoreAllState(Parcelable state, List<Fragment> nonConfig) { + void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) { // If there is no saved state at all, then there can not be // any nonConfig fragments either, so that is that. if (state == null) return; FragmentManagerState fms = (FragmentManagerState)state; if (fms.mActive == null) return; - + + List<FragmentManagerNonConfig> childNonConfigs = null; + // First re-attach any non-config instances we are retaining back // to their saved state, so we don't try to instantiate them again. if (nonConfig != null) { - for (int i=0; i<nonConfig.size(); i++) { - Fragment f = nonConfig.get(i); + List<Fragment> nonConfigFragments = nonConfig.getFragments(); + childNonConfigs = nonConfig.getChildNonConfigs(); + final int count = nonConfigFragments != null ? nonConfigFragments.size() : 0; + for (int i = 0; i < count; i++) { + Fragment f = nonConfigFragments.get(i); if (DEBUG) Log.v(TAG, "restoreAllState: re-attaching retained " + f); FragmentState fs = fms.mActive[f.mIndex]; fs.mInstance = f; @@ -1877,14 +1905,18 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate // Build the full list of active fragments, instantiating them from // their saved state. - mActive = new ArrayList<Fragment>(fms.mActive.length); + mActive = new ArrayList<>(fms.mActive.length); if (mAvailIndices != null) { mAvailIndices.clear(); } for (int i=0; i<fms.mActive.length; i++) { FragmentState fs = fms.mActive[i]; if (fs != null) { - Fragment f = fs.instantiate(mHost, mParent); + FragmentManagerNonConfig childNonConfig = null; + if (childNonConfigs != null && i < childNonConfigs.size()) { + childNonConfig = childNonConfigs.get(i); + } + Fragment f = fs.instantiate(mHost, mParent, childNonConfig); if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f); mActive.add(f); // Now that the fragment is instantiated (or came from being @@ -1894,7 +1926,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } else { mActive.add(null); if (mAvailIndices == null) { - mAvailIndices = new ArrayList<Integer>(); + mAvailIndices = new ArrayList<>(); } if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i); mAvailIndices.add(i); @@ -1903,8 +1935,10 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate // Update the target of all retained fragments. if (nonConfig != null) { - for (int i=0; i<nonConfig.size(); i++) { - Fragment f = nonConfig.get(i); + List<Fragment> nonConfigFragments = nonConfig.getFragments(); + final int count = nonConfigFragments != null ? nonConfigFragments.size() : 0; + for (int i = 0; i < count; i++) { + Fragment f = nonConfigFragments.get(i); if (f.mTargetIndex >= 0) { if (f.mTargetIndex < mActive.size()) { f.mTarget = mActive.get(f.mTargetIndex); diff --git a/core/java/android/app/FragmentManagerNonConfig.java b/core/java/android/app/FragmentManagerNonConfig.java new file mode 100644 index 000000000000..50d3797dd59d --- /dev/null +++ b/core/java/android/app/FragmentManagerNonConfig.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.app; + +import java.util.List; + +/** + * FragmentManagerNonConfig stores the retained instance fragments across + * activity recreation events. + * + * <p>Apps should treat objects of this type as opaque, returned by + * and passed to the state save and restore process for fragments in + * {@link FragmentController#retainNonConfig()} and + * {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p> + */ +public class FragmentManagerNonConfig { + private final List<Fragment> mFragments; + private final List<FragmentManagerNonConfig> mChildNonConfigs; + + FragmentManagerNonConfig(List<Fragment> fragments, + List<FragmentManagerNonConfig> childNonConfigs) { + mFragments = fragments; + mChildNonConfigs = childNonConfigs; + } + + /** + * @return the retained instance fragments returned by a FragmentManager + */ + List<Fragment> getFragments() { + return mFragments; + } + + /** + * @return the FragmentManagerNonConfigs from any applicable fragment's child FragmentManager + */ + List<FragmentManagerNonConfig> getChildNonConfigs() { + return mChildNonConfigs; + } +} |