summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.xml32
-rw-r--r--core/java/android/app/Activity.java40
-rw-r--r--core/java/android/app/Fragment.java29
-rw-r--r--core/java/android/app/FragmentManager.java4
-rw-r--r--core/java/android/app/LoaderManager.java272
5 files changed, 296 insertions, 81 deletions
diff --git a/api/current.xml b/api/current.xml
index db2456a..ea7804c 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -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"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d846610..20272df 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -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();
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index da3072c..51cce5e 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -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;
@@ -321,6 +324,14 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
}
/**
+ * 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
* (3) is not hidden.
@@ -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();
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index b324dfb..cb928a7 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -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()");
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index a0d2aec..31e3c40 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -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;
+ }
+
+ /**
+ * 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;
+ }
+
/**
- * Associates a loader with this managers, registers the callbacks on it,
+ * 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);
- }
- 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();
+ 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);
+ }
}
- 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 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 (mLoaders != null) {
+ if (!mRetaining) {
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();
+ mLoaders.valueAt(i).destroy();
}
}
+
+ for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
+ mInactiveLoaders.valueAt(i).destroy();
+ }
+ mInactiveLoaders.clear();
}
}