diff options
author | Dianne Hackborn <hackbod@google.com> | 2010-07-09 18:01:20 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2010-07-09 18:43:34 -0700 |
commit | 2707d6026240bcca6f0e35e2e1138958882e90ce (patch) | |
tree | 503f3e8913a979de2dd1d5e5e3cd08b8457e8460 /core/java/android | |
parent | d63c5d4e930732ef2a41266fcc8237cb1ec81320 (diff) | |
download | frameworks_base-2707d6026240bcca6f0e35e2e1138958882e90ce.zip frameworks_base-2707d6026240bcca6f0e35e2e1138958882e90ce.tar.gz frameworks_base-2707d6026240bcca6f0e35e2e1138958882e90ce.tar.bz2 |
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
Diffstat (limited to 'core/java/android')
-rw-r--r-- | core/java/android/app/Activity.java | 40 | ||||
-rw-r--r-- | core/java/android/app/Fragment.java | 29 | ||||
-rw-r--r-- | core/java/android/app/FragmentManager.java | 4 | ||||
-rw-r--r-- | core/java/android/app/LoaderManager.java | 272 |
4 files changed, 266 insertions, 79 deletions
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(); } } |