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:
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
54
core/java/android/app/FragmentManagerNonConfig.java
Normal file
54
core/java/android/app/FragmentManagerNonConfig.java
Normal 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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user