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

Commit 9dbfc331 authored by Jeff Brown's avatar Jeff Brown Committed by Android (Google) Code Review
Browse files

Merge "Support automatic cancellation of Loaders."

parents bbf1bc8b b19a71a2
Loading
Loading
Loading
Loading
+11 −3
Original line number Diff line number Diff line
@@ -4670,10 +4670,9 @@ package android.content {
  public abstract class AsyncTaskLoader extends android.content.Loader {
    ctor public AsyncTaskLoader(android.content.Context);
    method public boolean cancelLoad();
    method protected boolean isLoadInBackgroundCanceled();
    method public void cancelLoadInBackground();
    method public boolean isLoadInBackgroundCanceled();
    method public abstract D loadInBackground();
    method protected void onCancelLoadInBackground();
    method public void onCanceled(D);
    method protected D onLoadInBackground();
    method public void setUpdateThrottle(long);
@@ -5765,7 +5764,9 @@ package android.content {
  public class Loader {
    ctor public Loader(android.content.Context);
    method public void abandon();
    method public boolean cancelLoad();
    method public java.lang.String dataToString(D);
    method public void deliverCancellation();
    method public void deliverResult(D);
    method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
    method public void forceLoad();
@@ -5775,23 +5776,30 @@ package android.content {
    method public boolean isReset();
    method public boolean isStarted();
    method protected void onAbandon();
    method protected boolean onCancelLoad();
    method public void onContentChanged();
    method protected void onForceLoad();
    method protected void onReset();
    method protected void onStartLoading();
    method protected void onStopLoading();
    method public void registerListener(int, android.content.Loader.OnLoadCompleteListener<D>);
    method public void registerOnLoadCanceledListener(android.content.Loader.OnLoadCanceledListener<D>);
    method public void reset();
    method public final void startLoading();
    method public void stopLoading();
    method public boolean takeContentChanged();
    method public void unregisterListener(android.content.Loader.OnLoadCompleteListener<D>);
    method public void unregisterOnLoadCanceledListener(android.content.Loader.OnLoadCanceledListener<D>);
  }
  public final class Loader.ForceLoadContentObserver extends android.database.ContentObserver {
    ctor public Loader.ForceLoadContentObserver();
  }
  public static abstract interface Loader.OnLoadCanceledListener {
    method public abstract void onLoadCanceled(android.content.Loader<D>);
  }
  public static abstract interface Loader.OnLoadCompleteListener {
    method public abstract void onLoadComplete(android.content.Loader<D>, D);
  }
+51 −5
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.app;

import android.content.Loader;
import android.content.Loader.OnLoadCanceledListener;
import android.os.Bundle;
import android.util.DebugUtils;
import android.util.Log;
@@ -219,7 +220,8 @@ class LoaderManagerImpl extends LoaderManager {
    
    boolean mCreatingLoader;

    final class LoaderInfo implements Loader.OnLoadCompleteListener<Object> {
    final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>,
            Loader.OnLoadCanceledListener<Object> {
        final int mId;
        final Bundle mArgs;
        LoaderManager.LoaderCallbacks<Object> mCallbacks;
@@ -271,6 +273,7 @@ class LoaderManagerImpl extends LoaderManager {
                }
                if (!mListenerRegistered) {
                    mLoader.registerListener(mId, this);
                    mLoader.registerOnLoadCanceledListener(this);
                    mListenerRegistered = true;
                }
                mLoader.startLoading();
@@ -329,11 +332,21 @@ class LoaderManagerImpl extends LoaderManager {
                    // Let the loader know we're done with it
                    mListenerRegistered = false;
                    mLoader.unregisterListener(this);
                    mLoader.unregisterOnLoadCanceledListener(this);
                    mLoader.stopLoading();
                }
            }
        }

        void cancel() {
            if (DEBUG) Log.v(TAG, "  Canceling: " + this);
            if (mStarted && mLoader != null && mListenerRegistered) {
                if (!mLoader.cancelLoad()) {
                    onLoadCanceled(mLoader);
                }
            }
        }

        void destroy() {
            if (DEBUG) Log.v(TAG, "  Destroying: " + this);
            mDestroyed = true;
@@ -361,6 +374,7 @@ class LoaderManagerImpl extends LoaderManager {
                if (mListenerRegistered) {
                    mListenerRegistered = false;
                    mLoader.unregisterListener(this);
                    mLoader.unregisterOnLoadCanceledListener(this);
                }
                mLoader.reset();
            }
@@ -369,7 +383,37 @@ class LoaderManagerImpl extends LoaderManager {
            }
        }

        @Override public void onLoadComplete(Loader<Object> loader, Object data) {
        @Override
        public void onLoadCanceled(Loader<Object> loader) {
            if (DEBUG) Log.v(TAG, "onLoadCanceled: " + this);

            if (mDestroyed) {
                if (DEBUG) Log.v(TAG, "  Ignoring load canceled -- destroyed");
                return;
            }

            if (mLoaders.get(mId) != this) {
                // This cancellation message is not coming from the current active loader.
                // We don't care about it.
                if (DEBUG) Log.v(TAG, "  Ignoring load canceled -- not active");
                return;
            }

            LoaderInfo pending = mPendingLoader;
            if (pending != null) {
                // There is a new request pending and we were just
                // waiting for the old one to cancel or complete before starting
                // it.  So now it is time, switch over to the new loader.
                if (DEBUG) Log.v(TAG, "  Switching to pending loader: " + pending);
                mPendingLoader = null;
                mLoaders.put(mId, null);
                destroy();
                installLoader(pending);
            }
        }

        @Override
        public void onLoadComplete(Loader<Object> loader, Object data) {
            if (DEBUG) Log.v(TAG, "onLoadComplete: " + this);
            
            if (mDestroyed) {
@@ -632,7 +676,9 @@ class LoaderManagerImpl extends LoaderManager {
                    } else {
                        // Now we have three active loaders... we'll queue
                        // up this request to be processed once one of the other loaders
                        // finishes.
                        // finishes or is canceled.
                        if (DEBUG) Log.v(TAG, "  Current loader is running; attempting to cancel");
                        info.cancel();
                        if (info.mPendingLoader != null) {
                            if (DEBUG) Log.v(TAG, "  Removing pending loader: " + info.mPendingLoader);
                            info.mPendingLoader.destroy();
+96 −50
Original line number Diff line number Diff line
@@ -53,19 +53,33 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
    static final boolean DEBUG = false;

    final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
        private final CountDownLatch mDone = new CountDownLatch(1);

        D result;
        // Set to true to indicate that the task has been posted to a handler for
        // execution at a later time.  Used to throttle updates.
        boolean waiting;

        private CountDownLatch done = new CountDownLatch(1);

        /* Runs on a worker thread */
        @Override
        protected D doInBackground(Void... params) {
            if (DEBUG) Slog.v(TAG, this + " >>> doInBackground");
            result = AsyncTaskLoader.this.onLoadInBackground();
            try {
                D data = AsyncTaskLoader.this.onLoadInBackground();
                if (DEBUG) Slog.v(TAG, this + "  <<< doInBackground");
            return result;
                return data;
            } catch (OperationCanceledException ex) {
                if (!isCancelled()) {
                    // onLoadInBackground threw a canceled exception spuriously.
                    // This is problematic because it means that the LoaderManager did not
                    // cancel the Loader itself and still expects to receive a result.
                    // Additionally, the Loader's own state will not have been updated to
                    // reflect the fact that the task was being canceled.
                    // So we treat this case as an unhandled exception.
                    throw ex;
                }
                if (DEBUG) Slog.v(TAG, this + "  <<< doInBackground (was canceled)");
                return null;
            }
        }

        /* Runs on the UI thread */
@@ -75,25 +89,37 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
            try {
                AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
            } finally {
                done.countDown();
                mDone.countDown();
            }
        }

        /* Runs on the UI thread */
        @Override
        protected void onCancelled() {
        protected void onCancelled(D data) {
            if (DEBUG) Slog.v(TAG, this + " onCancelled");
            try {
                AsyncTaskLoader.this.dispatchOnCancelled(this, result);
                AsyncTaskLoader.this.dispatchOnCancelled(this, data);
            } finally {
                done.countDown();
                mDone.countDown();
            }
        }

        /* Runs on the UI thread, when the waiting task is posted to a handler.
         * This method is only executed when task execution was deferred (waiting was true). */
        @Override
        public void run() {
            waiting = false;
            AsyncTaskLoader.this.executePendingTask();
        }

        /* Used for testing purposes to wait for the task to complete. */
        public void waitForLoader() {
            try {
                mDone.await();
            } catch (InterruptedException e) {
                // Ignore
            }
        }
    }

    volatile LoadTask mTask;
@@ -109,7 +135,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {

    /**
     * Set amount to throttle updates by.  This is the minimum time from
     * when the last {@link #onLoadInBackground()} call has completed until
     * when the last {@link #loadInBackground()} call has completed until
     * a new load is scheduled.
     *
     * @param delayMS Amount of delay, in milliseconds.
@@ -130,24 +156,9 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
        executePendingTask();
    }

    /**
     * Attempt to cancel the current load task. See {@link AsyncTask#cancel(boolean)}
     * for more info.  Must be called on the main thread of the process.
     *
     * <p>Cancelling is not an immediate operation, since the load is performed
     * in a background thread.  If there is currently a load in progress, this
     * method requests that the load be cancelled, and notes this is the case;
     * once the background thread has completed its work its remaining state
     * will be cleared.  If another load request comes in during this time,
     * it will be held until the cancelled load is complete.
     *
     * @return Returns <tt>false</tt> if the task could not be cancelled,
     *         typically because it has already completed normally, or
     *         because {@link #startLoading()} hasn't been called; returns
     *         <tt>true</tt> otherwise.
     */
    public boolean cancelLoad() {
        if (DEBUG) Slog.v(TAG, "cancelLoad: mTask=" + mTask);
    @Override
    protected boolean onCancelLoad() {
        if (DEBUG) Slog.v(TAG, "onCancelLoad: mTask=" + mTask);
        if (mTask != null) {
            if (mCancellingTask != null) {
                // There was a pending task already waiting for a previous
@@ -173,7 +184,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
                if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled);
                if (cancelled) {
                    mCancellingTask = mTask;
                    onCancelLoadInBackground();
                    cancelLoadInBackground();
                }
                mTask = null;
                return cancelled;
@@ -184,7 +195,10 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {

    /**
     * Called if the task was canceled before it was completed.  Gives the class a chance
     * to properly dispose of the result.
     * to clean up post-cancellation and to properly dispose of the result.
     *
     * @param data The value that was returned by {@link #loadInBackground}, or null
     * if the task threw {@link OperationCanceledException}.
     */
    public void onCanceled(D data) {
    }
@@ -218,6 +232,8 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
            if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!");
            mLastLoadCompleteTime = SystemClock.uptimeMillis();
            mCancellingTask = null;
            if (DEBUG) Slog.v(TAG, "Delivering cancellation");
            deliverCancellation();
            executePendingTask();
        }
    }
@@ -240,38 +256,72 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
    }

    /**
     * Called on a worker thread to perform the actual load and to return
     * the result of the load operation.
     *
     * Implementations should not deliver the result directly, but should return them
     * from this method, which will eventually end up calling {@link #deliverResult} on
     * the UI thread.  If implementations need to process the results on the UI thread
     * they may override {@link #deliverResult} and do so there.
     *
     * To support cancellation, this method should periodically check the value of
     * {@link #isLoadInBackgroundCanceled} and terminate when it returns true.
     * Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load
     * directly instead of polling {@link #isLoadInBackgroundCanceled}.
     *
     * When the load is canceled, this method may either return normally or throw
     * {@link OperationCanceledException}.  In either case, the {@link Loader} will
     * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
     * result object, if any.
     *
     * @return The result of the load operation.
     *
     * @throws OperationCanceledException if the load is canceled during execution.
     *
     * @see #isLoadInBackgroundCanceled
     * @see #cancelLoadInBackground
     * @see #onCanceled
     */
    public abstract D loadInBackground();

    /**
     * Called on a worker thread to perform the actual load. Implementations should not deliver the
     * result directly, but should return them from this method, which will eventually end up
     * calling {@link #deliverResult} on the UI thread. If implementations need to process
     * the results on the UI thread they may override {@link #deliverResult} and do so
     * there.
     * Calls {@link #loadInBackground()}.
     *
     * This method is reserved for use by the loader framework.
     * Subclasses should override {@link #loadInBackground} instead of this method.
     *
     * @return The result of the load operation.
     *
     * @return Implementations must return the result of their load operation.
     * @throws OperationCanceledException if the load is canceled during execution.
     *
     * @see #loadInBackground
     */
    protected D onLoadInBackground() {
        return loadInBackground();
    }

    /**
     * Override this method to try to abort the computation currently taking
     * place on a background thread.
     * Called on the main thread to abort a load in progress.
     *
     * Override this method to abort the current invocation of {@link #loadInBackground}
     * that is running in the background on a worker thread.
     *
     * Note that when this method is called, it is possible that {@link #loadInBackground}
     * has not started yet or has already completed.
     * This method should do nothing if {@link #loadInBackground} has not started
     * running or if it has already finished.
     *
     * @see #loadInBackground
     */
    protected void onCancelLoadInBackground() {
    public void cancelLoadInBackground() {
    }

    /**
     * Returns true if the current execution of {@link #loadInBackground()} is being canceled.
     * Returns true if the current invocation of {@link #loadInBackground} is being canceled.
     *
     * @return True if the current invocation of {@link #loadInBackground} is being canceled.
     *
     * @return True if the current execution of {@link #loadInBackground()} is being canceled.
     * @see #loadInBackground
     */
    protected boolean isLoadInBackgroundCanceled() {
    public boolean isLoadInBackgroundCanceled() {
        return mCancellingTask != null;
    }

@@ -288,11 +338,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
    public void waitForLoader() {
        LoadTask task = mTask;
        if (task != null) {
            try {
                task.done.await();
            } catch (InterruptedException e) {
                // Ignore
            }
            task.waitForLoader();
        }
    }

+2 −2
Original line number Diff line number Diff line
@@ -76,8 +76,8 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
    }

    @Override
    protected void onCancelLoadInBackground() {
        super.onCancelLoadInBackground();
    public void cancelLoadInBackground() {
        super.cancelLoadInBackground();

        synchronized (this) {
            if (mCancelationSignal != null) {
+101 −0
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import java.io.PrintWriter;
public class Loader<D> {
    int mId;
    OnLoadCompleteListener<D> mListener;
    OnLoadCanceledListener<D> mOnLoadCanceledListener;
    Context mContext;
    boolean mStarted = false;
    boolean mAbandoned = false;
@@ -99,6 +100,23 @@ public class Loader<D> {
        public void onLoadComplete(Loader<D> loader, D data);
    }

    /**
     * Interface that is implemented to discover when a Loader has been canceled
     * before it finished loading its data.  You do not normally need to implement
     * this yourself; it is used in the implementation of {@link android.app.LoaderManager}
     * to find out when a Loader it is managing has been canceled so that it
     * can schedule the next Loader.  This interface should only be used if a
     * Loader is not being used in conjunction with LoaderManager.
     */
    public interface OnLoadCanceledListener<D> {
        /**
         * Called on the thread that created the Loader when the load is canceled.
         *
         * @param loader the loader that canceled the load
         */
        public void onLoadCanceled(Loader<D> loader);
    }

    /**
     * Stores away the application context associated with context.
     * Since Loaders can be used across multiple activities it's dangerous to
@@ -126,6 +144,18 @@ public class Loader<D> {
        }
    }

    /**
     * Informs the registered {@link OnLoadCanceledListener} that the load has been canceled.
     * Should only be called by subclasses.
     *
     * Must be called from the process's main thread.
     */
    public void deliverCancellation() {
        if (mOnLoadCanceledListener != null) {
            mOnLoadCanceledListener.onLoadCanceled(this);
        }
    }

    /**
     * @return an application context retrieved from the Context passed to the constructor.
     */
@@ -170,6 +200,40 @@ public class Loader<D> {
        mListener = null;
    }

    /**
     * Registers a listener that will receive callbacks when a load is canceled.
     * The callback will be called on the process's main thread so it's safe to
     * pass the results to widgets.
     *
     * Must be called from the process's main thread.
     *
     * @param listener The listener to register.
     */
    public void registerOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
        if (mOnLoadCanceledListener != null) {
            throw new IllegalStateException("There is already a listener registered");
        }
        mOnLoadCanceledListener = listener;
    }

    /**
     * Unregisters a listener that was previously added with
     * {@link #registerOnLoadCanceledListener}.
     *
     * Must be called from the process's main thread.
     *
     * @param listener The listener to unregister.
     */
    public void unregisterOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
        if (mOnLoadCanceledListener == null) {
            throw new IllegalStateException("No listener register");
        }
        if (mOnLoadCanceledListener != listener) {
            throw new IllegalArgumentException("Attempting to unregister the wrong listener");
        }
        mOnLoadCanceledListener = null;
    }

    /**
     * Return whether this load has been started.  That is, its {@link #startLoading()}
     * has been called and no calls to {@link #stopLoading()} or
@@ -233,6 +297,43 @@ public class Loader<D> {
    protected void onStartLoading() {
    }

    /**
     * Attempt to cancel the current load task.
     * Must be called on the main thread of the process.
     *
     * <p>Cancellation is not an immediate operation, since the load is performed
     * in a background thread.  If there is currently a load in progress, this
     * method requests that the load be canceled, and notes this is the case;
     * once the background thread has completed its work its remaining state
     * will be cleared.  If another load request comes in during this time,
     * it will be held until the canceled load is complete.
     *
     * @return Returns <tt>false</tt> if the task could not be canceled,
     * typically because it has already completed normally, or
     * because {@link #startLoading()} hasn't been called; returns
     * <tt>true</tt> otherwise.  When <tt>true</tt> is returned, the task
     * is still running and the {@link OnLoadCanceledListener} will be called
     * when the task completes.
     */
    public boolean cancelLoad() {
        return onCancelLoad();
    }

    /**
     * Subclasses must implement this to take care of requests to {@link #cancelLoad()}.
     * This will always be called from the process's main thread.
     *
     * @return Returns <tt>false</tt> if the task could not be canceled,
     * typically because it has already completed normally, or
     * because {@link #startLoading()} hasn't been called; returns
     * <tt>true</tt> otherwise.  When <tt>true</tt> is returned, the task
     * is still running and the {@link OnLoadCanceledListener} will be called
     * when the task completes.
     */
    protected boolean onCancelLoad() {
        return false;
    }

    /**
     * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
     * loaded data set and load a new one.  This simply calls through to the
Loading