Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2707d602 authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

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
parent d63c5d4e
Loading
Loading
Loading
Loading
+30 −2
Original line number Diff line number Diff line
@@ -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"
+39 −1
Original line number Diff line number Diff line
@@ -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();
+25 −4
Original line number Diff line number Diff line
@@ -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();
                }
            }
        }
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -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()");
+199 −73
Original line number Diff line number Diff line
@@ -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,84 +41,211 @@ 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) {
            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) {
    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.loader;
            Loader<Object> loader = info.mLoader;
            if (loader != null) {
                loader.unregisterListener(info);
                loader.destroy();
            }
        }
    }
    }

    /**
     * Return the Loader with the given id or null if no matching Loader
@@ -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;
            mLoaders.valueAt(i).stop();
        }

            // 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();
            //}
        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;
                mLoaders.valueAt(i).destroy();
            }
                loader.destroy();
        }
        
        for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
            mInactiveLoaders.valueAt(i).destroy();
        }
        mInactiveLoaders.clear();
    }
}