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 7466be6626)
This commit is contained in:
Adam Powell
2016-02-04 16:20:37 -08:00
parent 8585ed66b9
commit 44ba79e47d
8 changed files with 188 additions and 61 deletions

View File

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

View File

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

View File

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

View File

@ -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) {

View File

@ -97,35 +97,34 @@ final class FragmentState implements Parcelable {
mSavedFragmentState = in.readBundle();
}
public Fragment instantiate(FragmentHostCallback host, Fragment parent) {
if (mInstance != null) {
return mInstance;
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);
}
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.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();
}
}

View File

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

View File

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

View File

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