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:
@@ -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<D>"
|
||||
abstract="false"
|
||||
native="false"
|
||||
@@ -28509,7 +28520,24 @@
|
||||
<parameter name="callback" type="android.app.LoaderManager.LoaderCallbacks<D>">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="stopLoading"
|
||||
<method name="restartLoader"
|
||||
return="android.content.Loader<D>"
|
||||
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<D>">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="stopLoader"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user