Implement instance passing in LoaderManager.

Activity now propagates loaders across instances when retaining
state.  Adjusted APIs to make it better for apps to deal with this.

Change-Id: I8a6448cff1132e66207f9223eb29ccfc0decf2ca
This commit is contained in:
Dianne Hackborn
2010-07-09 18:01:20 -07:00
parent d63c5d4e93
commit 2707d60262
5 changed files with 299 additions and 84 deletions

View File

@@ -26078,6 +26078,17 @@
visibility="public"
>
</method>
<method name="isResumed"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="true"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="isVisible"
return="boolean"
abstract="false"
@@ -28492,7 +28503,7 @@
<parameter name="id" type="int">
</parameter>
</method>
<method name="startLoading"
<method name="initLoader"
return="android.content.Loader&lt;D&gt;"
abstract="false"
native="false"
@@ -28509,7 +28520,24 @@
<parameter name="callback" type="android.app.LoaderManager.LoaderCallbacks&lt;D&gt;">
</parameter>
</method>
<method name="stopLoading"
<method name="restartLoader"
return="android.content.Loader&lt;D&gt;"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="id" type="int">
</parameter>
<parameter name="args" type="android.os.Bundle">
</parameter>
<parameter name="callback" type="android.app.LoaderManager.LoaderCallbacks&lt;D&gt;">
</parameter>
</method>
<method name="stopLoader"
return="void"
abstract="false"
native="false"

View File

@@ -635,6 +635,7 @@ public class Activity extends ContextThemeWrapper
/*package*/ ActivityThread mMainThread;
Activity mParent;
boolean mCalled;
boolean mStarted;
private boolean mResumed;
private boolean mStopped;
boolean mFinished;
@@ -843,6 +844,9 @@ public class Activity extends ContextThemeWrapper
protected void onCreate(Bundle savedInstanceState) {
mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, false);
if (mLastNonConfigurationInstances != null) {
mAllLoaderManagers = mLastNonConfigurationInstances.loaders;
}
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
@@ -985,6 +989,10 @@ public class Activity extends ContextThemeWrapper
*/
protected void onStart() {
mCalled = true;
mStarted = true;
if (mLoaderManager != null) {
mLoaderManager.doStart();
}
}
/**
@@ -1522,7 +1530,20 @@ public class Activity extends ContextThemeWrapper
Object activity = onRetainNonConfigurationInstance();
HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
ArrayList<Fragment> fragments = mFragments.retainNonConfig();
if (activity == null && children == null && fragments == null) {
boolean retainLoaders = false;
if (mAllLoaderManagers != null) {
// prune out any loader managers that were already stopped, so
// have nothing useful to retain.
for (int i=mAllLoaderManagers.size()-1; i>=0; i--) {
LoaderManager lm = mAllLoaderManagers.valueAt(i);
if (lm.mRetaining) {
retainLoaders = true;
} else {
mAllLoaderManagers.removeAt(i);
}
}
}
if (activity == null && children == null && fragments == null && !retainLoaders) {
return null;
}
@@ -1530,6 +1551,7 @@ public class Activity extends ContextThemeWrapper
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = mAllLoaderManagers;
return nci;
}
@@ -4065,6 +4087,11 @@ public class Activity extends ContextThemeWrapper
" did not call through to super.onStart()");
}
mFragments.dispatchStart();
if (mAllLoaderManagers != null) {
for (int i=mAllLoaderManagers.size()-1; i>=0; i--) {
mAllLoaderManagers.valueAt(i).finishRetain();
}
}
}
final void performRestart() {
@@ -4136,6 +4163,17 @@ public class Activity extends ContextThemeWrapper
}
final void performStop() {
if (mStarted) {
mStarted = false;
if (mLoaderManager != null) {
if (!mChangingConfigurations) {
mLoaderManager.doStop();
} else {
mLoaderManager.doRetain();
}
}
}
if (!mStopped) {
if (mWindow != null) {
mWindow.closeAllPanels();

View File

@@ -158,6 +158,9 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
// True if the fragment is in the list of added fragments.
boolean mAdded;
// True if the fragment is in the resumed state.
boolean mResumed;
// Set to true if this fragment was instantiated from a layout file.
boolean mFromLayout;
@@ -320,6 +323,14 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
return mActivity != null && mActivity.mFragments.mAdded.contains(this);
}
/**
* Return true if the fragment is in the resumed state. This is true
* for the duration of {@link #onResume()} and {@link #onPause()} as well.
*/
final public boolean isResumed() {
return mResumed;
}
/**
* Return true if the fragment is currently visible to the user. This means
* it: (1) has been added, (2) has its view attached to the window, and
@@ -575,10 +586,6 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
*/
public void onStop() {
mCalled = true;
mStarted = false;
if (mLoaderManager != null) {
mLoaderManager.doStop();
}
}
public void onLowMemory() {
@@ -746,4 +753,18 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
public boolean onContextItemSelected(MenuItem item) {
return false;
}
void performStop() {
onStop();
if (mStarted) {
mStarted = false;
if (mLoaderManager != null) {
if (mActivity == null || !mActivity.mChangingConfigurations) {
mLoaderManager.doStop();
} else {
mLoaderManager.doRetain();
}
}
}
}
}

View File

@@ -238,6 +238,7 @@ public class FragmentManager {
if (newState > Fragment.STARTED) {
if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
f.mCalled = false;
f.mResumed = true;
f.onResume();
if (!f.mCalled) {
throw new SuperNotCalledException("Fragment " + f
@@ -256,12 +257,13 @@ public class FragmentManager {
throw new SuperNotCalledException("Fragment " + f
+ " did not call through to super.onPause()");
}
f.mResumed = false;
}
case Fragment.STARTED:
if (newState < Fragment.STARTED) {
if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f);
f.mCalled = false;
f.onStop();
f.performStop();
if (!f.mCalled) {
throw new SuperNotCalledException("Fragment " + f
+ " did not call through to super.onStop()");

View File

@@ -26,6 +26,12 @@ import android.util.SparseArray;
* one or more {@link android.content.Loader} instances associated with it.
*/
public class LoaderManager {
final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>();
final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>();
boolean mStarted;
boolean mRetaining;
boolean mRetainingStarted;
/**
* Callback interface for a client to interact with the manager.
*/
@@ -35,81 +41,208 @@ public class LoaderManager {
}
final class LoaderInfo implements Loader.OnLoadCompleteListener<Object> {
public Bundle args;
public Loader<Object> loader;
public LoaderManager.LoaderCallbacks<Object> callback;
final int mId;
final Bundle mArgs;
LoaderManager.LoaderCallbacks<Object> mCallbacks;
Loader<Object> mLoader;
Object mData;
boolean mStarted;
boolean mRetaining;
boolean mRetainingStarted;
boolean mDestroyed;
boolean mListenerRegistered;
public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {
mId = id;
mArgs = args;
mCallbacks = callbacks;
}
void start() {
if (mRetaining && mRetainingStarted) {
// Our owner is started, but we were being retained from a
// previous instance in the started state... so there is really
// nothing to do here, since the loaders are still started.
mStarted = true;
return;
}
if (mLoader == null && mCallbacks != null) {
mLoader = mCallbacks.onCreateLoader(mId, mArgs);
}
if (mLoader != null) {
mLoader.registerListener(mId, this);
mListenerRegistered = true;
mLoader.startLoading();
mStarted = true;
}
}
void retain() {
mRetaining = true;
mRetainingStarted = mStarted;
mStarted = false;
mCallbacks = null;
}
void finishRetain() {
if (mRetaining) {
mRetaining = false;
if (mStarted != mRetainingStarted) {
if (!mStarted) {
// This loader was retained in a started state, but
// at the end of retaining everything our owner is
// no longer started... so make it stop.
stop();
}
}
if (mStarted && mData != null && mCallbacks != null) {
// This loader was retained, and now at the point of
// finishing the retain we find we remain started, have
// our data, and the owner has a new callback... so
// let's deliver the data now.
mCallbacks.onLoadFinished(mLoader, mData);
}
}
}
void stop() {
mStarted = false;
if (mLoader != null && mListenerRegistered) {
// Let the loader know we're done with it
mListenerRegistered = false;
mLoader.unregisterListener(this);
}
}
void destroy() {
mDestroyed = true;
mCallbacks = null;
if (mLoader != null) {
if (mListenerRegistered) {
mListenerRegistered = false;
mLoader.unregisterListener(this);
}
mLoader.destroy();
}
}
@Override public void onLoadComplete(Loader<Object> loader, Object data) {
if (mDestroyed) {
return;
}
// Notify of the new data so the app can switch out the old data before
// we try to destroy it.
callback.onLoadFinished(loader, data);
mData = data;
if (mCallbacks != null) {
mCallbacks.onLoadFinished(loader, data);
}
// Look for an inactive loader and destroy it if found
int id = loader.getId();
LoaderInfo info = mInactiveLoaders.get(id);
LoaderInfo info = mInactiveLoaders.get(mId);
if (info != null) {
Loader<Object> oldLoader = info.loader;
Loader<Object> oldLoader = info.mLoader;
if (oldLoader != null) {
oldLoader.unregisterListener(info);
oldLoader.destroy();
}
mInactiveLoaders.remove(id);
mInactiveLoaders.remove(mId);
}
}
}
SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>();
SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>();
boolean mStarted;
LoaderManager(boolean started) {
mStarted = started;
}
private LoaderInfo createLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<Object> callback) {
LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
mLoaders.put(id, info);
Loader<Object> loader = callback.onCreateLoader(id, args);
info.mLoader = (Loader<Object>)loader;
if (mStarted) {
// The activity will start all existing loaders in it's onStart(), so only start them
// here if we're past that point of the activitiy's life cycle
loader.registerListener(id, info);
loader.startLoading();
}
return info;
}
/**
* Associates a loader with this managers, registers the callbacks on it,
* Ensures a loader is initialized an active. If the loader doesn't
* already exist, one is created and started. Otherwise the last created
* loader is re-used.
*
* <p>In either case, the given callback is associated with the loader, and
* will be called as the loader state changes. If at the point of call
* the caller is in its started state, and the requested loader
* already exists and has generated its data, then
* callback.{@link LoaderCallbacks#onLoadFinished(Loader, Object)} will
* be called immediately (inside of this function), so you must be prepared
* for this to happen.
*/
@SuppressWarnings("unchecked")
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
LoaderInfo info = mLoaders.get(id);
if (info == null) {
// Loader doesn't already exist; create.
info = createLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
} else {
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
if (info.mData != null && mStarted) {
// If the loader has already generated its data, report it now.
info.mCallbacks.onLoadFinished(info.mLoader, info.mData);
}
return (Loader<D>)info.mLoader;
}
/**
* Create a new loader in this manager, registers the callbacks to it,
* and starts it loading. If a loader with the same id has previously been
* started it will automatically be destroyed when the new loader completes
* its work. The callback will be delivered before the old loader
* is destroyed.
*/
@SuppressWarnings("unchecked")
public <D> Loader<D> startLoading(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
LoaderInfo info = mLoaders.get(id);
if (info != null) {
// Keep track of the previous instance of this loader so we can destroy
// it when the new one completes.
mInactiveLoaders.put(id, info);
if (mInactiveLoaders.get(id) != null) {
// We already have an inactive loader for this ID that we are
// waiting for! Now we have three active loaders... let's just
// drop the one in the middle, since we are still waiting for
// its result but that result is already out of date.
info.destroy();
} else {
// Keep track of the previous instance of this loader so we can destroy
// it when the new one completes.
mInactiveLoaders.put(id, info);
}
}
info = new LoaderInfo();
info.args = args;
info.callback = (LoaderManager.LoaderCallbacks<Object>)callback;
mLoaders.put(id, info);
Loader<D> loader = callback.onCreateLoader(id, args);
info.loader = (Loader<Object>)loader;
if (mStarted) {
// The activity will start all existing loaders in it's onStart(), so only start them
// here if we're past that point of the activitiy's life cycle
loader.registerListener(id, (OnLoadCompleteListener<D>)info);
loader.startLoading();
}
return loader;
info = createLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
return (Loader<D>)info.mLoader;
}
/**
* Stops and removes the loader with the given ID.
*/
public void stopLoading(int id) {
if (mLoaders != null) {
int idx = mLoaders.indexOfKey(id);
if (idx >= 0) {
LoaderInfo info = mLoaders.valueAt(idx);
mLoaders.removeAt(idx);
Loader<Object> loader = info.loader;
if (loader != null) {
loader.unregisterListener(info);
loader.destroy();
}
public void stopLoader(int id) {
int idx = mLoaders.indexOfKey(id);
if (idx >= 0) {
LoaderInfo info = mLoaders.valueAt(idx);
mLoaders.removeAt(idx);
Loader<Object> loader = info.mLoader;
if (loader != null) {
loader.unregisterListener(info);
loader.destroy();
}
}
}
@@ -122,7 +255,7 @@ public class LoaderManager {
public <D> Loader<D> getLoader(int id) {
LoaderInfo loaderInfo = mLoaders.get(id);
if (loaderInfo != null) {
return (Loader<D>)mLoaders.get(id).loader;
return (Loader<D>)mLoaders.get(id).mLoader;
}
return null;
}
@@ -131,50 +264,43 @@ public class LoaderManager {
// Call out to sub classes so they can start their loaders
// Let the existing loaders know that we want to be notified when a load is complete
for (int i = mLoaders.size()-1; i >= 0; i--) {
LoaderInfo info = mLoaders.valueAt(i);
Loader<Object> loader = info.loader;
int id = mLoaders.keyAt(i);
if (loader == null) {
loader = info.callback.onCreateLoader(id, info.args);
info.loader = loader;
}
loader.registerListener(id, info);
loader.startLoading();
mLoaders.valueAt(i).start();
}
mStarted = true;
}
void doStop() {
for (int i = mLoaders.size()-1; i >= 0; i--) {
LoaderInfo info = mLoaders.valueAt(i);
Loader<Object> loader = info.loader;
if (loader == null) {
continue;
}
// Let the loader know we're done with it
loader.unregisterListener(info);
// The loader isn't getting passed along to the next instance so ask it to stop loading
//if (!getActivity().isChangingConfigurations()) {
// loader.stopLoading();
//}
mLoaders.valueAt(i).stop();
}
mStarted = false;
}
void doDestroy() {
if (mLoaders != null) {
for (int i = mLoaders.size()-1; i >= 0; i--) {
LoaderInfo info = mLoaders.valueAt(i);
Loader<Object> loader = info.loader;
if (loader == null) {
continue;
}
loader.destroy();
}
void doRetain() {
mRetaining = true;
mStarted = false;
for (int i = mLoaders.size()-1; i >= 0; i--) {
mLoaders.valueAt(i).retain();
}
}
void finishRetain() {
mRetaining = false;
for (int i = mLoaders.size()-1; i >= 0; i--) {
mLoaders.valueAt(i).finishRetain();
}
}
void doDestroy() {
if (!mRetaining) {
for (int i = mLoaders.size()-1; i >= 0; i--) {
mLoaders.valueAt(i).destroy();
}
}
for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
mInactiveLoaders.valueAt(i).destroy();
}
mInactiveLoaders.clear();
}
}