diff options
Diffstat (limited to 'core/java/android/content')
-rw-r--r-- | core/java/android/content/AsyncTaskLoader.java | 153 | ||||
-rw-r--r-- | core/java/android/content/Context.java | 6 | ||||
-rw-r--r-- | core/java/android/content/ContextWrapper.java | 6 | ||||
-rw-r--r-- | core/java/android/content/CursorLoader.java | 2 | ||||
-rw-r--r-- | core/java/android/content/Loader.java | 51 |
5 files changed, 180 insertions, 38 deletions
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java index 01a2912..ec4e578 100644 --- a/core/java/android/content/AsyncTaskLoader.java +++ b/core/java/android/content/AsyncTaskLoader.java @@ -17,8 +17,13 @@ package android.content; import android.os.AsyncTask; +import android.os.Handler; +import android.os.SystemClock; import android.util.Log; +import android.util.TimeUtils; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.concurrent.ExecutionException; /** @@ -30,14 +35,15 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { private static final String TAG = "AsyncTaskLoader"; - final class LoadTask extends AsyncTask<Void, Void, D> { + final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable { - private D result; + D result; + boolean waiting; /* Runs on a worker thread */ @Override protected D doInBackground(Void... params) { - result = AsyncTaskLoader.this.loadInBackground(); + result = AsyncTaskLoader.this.onLoadInBackground(); return result; } @@ -49,38 +55,91 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { @Override protected void onCancelled() { - AsyncTaskLoader.this.onCancelled(result); + AsyncTaskLoader.this.dispatchOnCancelled(this, result); + } + + @Override + public void run() { + waiting = false; + AsyncTaskLoader.this.executePendingTask(); } } volatile LoadTask mTask; + volatile LoadTask mCancellingTask; + + long mUpdateThrottle; + long mLastLoadCompleteTime = -10000; + Handler mHandler; public AsyncTaskLoader(Context context) { super(context); } + /** + * Set amount to throttle updates by. This is the minimum time from + * when the last {@link #onLoadInBackground()} call has completed until + * a new load is scheduled. + * + * @param delayMS Amount of delay, in milliseconds. + */ + public void setUpdateThrottle(long delayMS) { + mUpdateThrottle = delayMS; + if (delayMS != 0) { + mHandler = new Handler(); + } + } + @Override protected void onForceLoad() { super.onForceLoad(); cancelLoad(); mTask = new LoadTask(); - mTask.execute((Void[]) null); + executePendingTask(); } /** * Attempt to cancel the current load task. See {@link AsyncTask#cancel(boolean)} - * for more info. + * 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 <tt>false</tt> if the task could not be canceled, + * @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, and - * <tt>true</tt> otherwise + * because {@link #startLoading()} hasn't been called; returns + * <tt>true</tt> otherwise. */ public boolean cancelLoad() { if (mTask != null) { - boolean cancelled = mTask.cancel(false); - mTask = null; - return cancelled; + if (mCancellingTask != null) { + // There was a pending task already waiting for a previous + // one being canceled; just drop it. + if (mTask.waiting) { + mTask.waiting = false; + mHandler.removeCallbacks(mTask); + } + mTask = null; + return false; + } else if (mTask.waiting) { + // There is a task, but it is waiting for the time it should + // execute. We can just toss it. + mTask.waiting = false; + mHandler.removeCallbacks(mTask); + mTask = null; + return false; + } else { + boolean cancelled = mTask.cancel(false); + if (cancelled) { + mCancellingTask = mTask; + } + mTask = null; + return cancelled; + } } return false; } @@ -92,32 +151,67 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { public void onCancelled(D data) { } + void executePendingTask() { + if (mCancellingTask == null && mTask != null) { + if (mTask.waiting) { + mTask.waiting = false; + mHandler.removeCallbacks(mTask); + } + if (mUpdateThrottle > 0) { + long now = SystemClock.uptimeMillis(); + if (now < (mLastLoadCompleteTime+mUpdateThrottle)) { + // Not yet time to do another load. + mTask.waiting = true; + mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle); + return; + } + } + mTask.execute((Void[]) null); + } + } + + void dispatchOnCancelled(LoadTask task, D data) { + onCancelled(data); + if (mCancellingTask == task) { + mLastLoadCompleteTime = SystemClock.uptimeMillis(); + mCancellingTask = null; + executePendingTask(); + } + } + void dispatchOnLoadComplete(LoadTask task, D data) { if (mTask != task) { - onCancelled(data); + dispatchOnCancelled(task, data); } else { + mLastLoadCompleteTime = SystemClock.uptimeMillis(); mTask = null; deliverResult(data); } } /** + */ + public abstract D loadInBackground(); + + /** * Called on a worker thread to perform the actual load. Implementations should not deliver the - * results directly, but should return them from this method, which will eventually end up - * calling deliverResult on the UI thread. If implementations need to process - * the results on the UI thread they may override deliverResult and do so + * 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. * - * @return the result of the load + * @return Implementations must return the result of their load operation. */ - public abstract D loadInBackground(); + protected D onLoadInBackground() { + return loadInBackground(); + } /** * Locks the current thread until the loader completes the current load * operation. Returns immediately if there is no load operation running. * Should not be called from the UI thread. * <p> - * Used for testing. + * Use for testing only. <b>Never</b> call this from a UI thread. */ public void waitForLoader() { LoadTask task = mTask; @@ -132,4 +226,25 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { } } } + + @Override + public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + super.dump(prefix, fd, writer, args); + if (mTask != null) { + writer.print(prefix); writer.print("mTask="); writer.print(mTask); + writer.print(" waiting="); writer.println(mTask.waiting); + } + if (mCancellingTask != null) { + writer.print(prefix); writer.print("mCancellingTask="); writer.print(mCancellingTask); + writer.print(" waiting="); writer.println(mCancellingTask.waiting); + } + if (mUpdateThrottle != 0) { + writer.print(prefix); writer.print("mUpdateThrottle="); + TimeUtils.formatDuration(mUpdateThrottle, writer); + writer.print(" mLastLoadCompleteTime="); + TimeUtils.formatDuration(mLastLoadCompleteTime, + SystemClock.uptimeMillis(), writer); + writer.println(); + } + } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index b128d31..227df21 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -227,6 +227,12 @@ public abstract class Context { */ public abstract void setTheme(int resid); + /** @hide Needed for some internal implementation... not public because + * you can't assume this actually means anything. */ + public int getThemeResId() { + return 0; + } + /** * Return the Theme object associated with this Context. */ diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index f8928e4..545144e 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -106,6 +106,12 @@ public class ContextWrapper extends Context { mBase.setTheme(resid); } + /** @hide */ + @Override + public int getThemeResId() { + return mBase.getThemeResId(); + } + @Override public Resources.Theme getTheme() { return mBase.getTheme(); diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java index 8ab0973..38ebaf2 100644 --- a/core/java/android/content/CursorLoader.java +++ b/core/java/android/content/CursorLoader.java @@ -55,7 +55,7 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> { * Registers an observer to get notifications from the content provider * when the cursor needs to be refreshed. */ - public void registerContentObserver(Cursor cursor, ContentObserver observer) { + void registerContentObserver(Cursor cursor, ContentObserver observer) { cursor.registerContentObserver(mObserver); } diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java index ef81fe4..d63fe69 100644 --- a/core/java/android/content/Loader.java +++ b/core/java/android/content/Loader.java @@ -28,6 +28,13 @@ import java.io.PrintWriter; * they should monitor the source of their data and deliver new results when the contents * change. * + * <p><b>Note on threading:</b> Clients of loaders should as a rule perform + * any calls on to a Loader from the main thread of their process (that is, + * the thread the Activity callbacks and other things occur on). Subclasses + * of Loader (such as {@link AsyncTaskLoader}) will often perform their work + * in a separate thread, but when delivering their results this too should + * be done on the main thread.</p> + * * <p>Subclasses generally must implement at least {@link #onStartLoading()}, * {@link #onStopLoading()}, {@link #onForceLoad()}, and {@link #onReset()}. * @@ -80,7 +87,7 @@ public class Loader<D> { /** * Sends the result of the load to the registered listener. Should only be called by subclasses. * - * Must be called from the UI thread. + * Must be called from the process's main thread. * * @param data the result of the load */ @@ -105,10 +112,11 @@ public class Loader<D> { } /** - * Registers a class that will receive callbacks when a load is complete. The callbacks will - * be called on the UI thread so it's safe to pass the results to widgets. + * Registers a class that will receive callbacks when a load is complete. + * 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 UI thread + * <p>Must be called from the process's main thread. */ public void registerListener(int id, OnLoadCompleteListener<D> listener) { if (mListener != null) { @@ -119,7 +127,9 @@ public class Loader<D> { } /** - * Must be called from the UI thread + * Remove a listener that was previously added with {@link #registerListener}. + * + * Must be called from the process's main thread. */ public void unregisterListener(OnLoadCompleteListener<D> listener) { if (mListener == null) { @@ -150,17 +160,19 @@ public class Loader<D> { } /** - * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks - * will be called on the UI thread. If a previous load has been completed and is still valid - * the result may be passed to the callbacks immediately. The loader will monitor the source of - * the data set and may deliver future callbacks if the source changes. Calling - * {@link #stopLoading} will stop the delivery of callbacks. + * Starts an asynchronous load of the Loader's data. When the result + * is ready the callbacks will be called on the process's main thread. + * If a previous load has been completed and is still valid + * the result may be passed to the callbacks immediately. + * The loader will monitor the source of + * the data set and may deliver future callbacks if the source changes. + * Calling {@link #stopLoading} will stop the delivery of callbacks. * * <p>This updates the Loader's internal state so that * {@link #isStarted()} and {@link #isReset()} will return the correct * values, and then calls the implementation's {@link #onStartLoading()}. * - * <p>Must be called from the UI thread. + * <p>Must be called from the process's main thread. */ public final void startLoading() { mStarted = true; @@ -182,14 +194,15 @@ public class Loader<D> { * implementation's {@link #onForceLoad()}. You generally should only call this * when the loader is started -- that is, {@link #isStarted()} returns true. * - * <p>Must be called from the UI thread. + * <p>Must be called from the process's main thread. */ - public final void forceLoad() { + public void forceLoad() { onForceLoad(); } /** * Subclasses must implement this to take care of requests to {@link #forceLoad()}. + * This will always be called from the process's main thread. */ protected void onForceLoad() { } @@ -206,9 +219,9 @@ public class Loader<D> { * {@link #isStarted()} will return the correct * value, and then calls the implementation's {@link #onStopLoading()}. * - * <p>Must be called from the UI thread. + * <p>Must be called from the process's main thread. */ - public final void stopLoading() { + public void stopLoading() { mStarted = false; onStopLoading(); } @@ -217,6 +230,7 @@ public class Loader<D> { * Subclasses must implement this to take care of stopping their loader, * as per {@link #stopLoading()}. This is not called by clients directly, * but as a result of a call to {@link #stopLoading()}. + * This will always be called from the process's main thread. */ protected void onStopLoading() { } @@ -231,9 +245,9 @@ public class Loader<D> { * {@link #isStarted()} and {@link #isReset()} will return the correct * values, and then calls the implementation's {@link #onReset()}. * - * <p>Must be called from the UI thread. + * <p>Must be called from the process's main thread. */ - public final void reset() { + public void reset() { onReset(); mReset = true; mStarted = false; @@ -244,6 +258,7 @@ public class Loader<D> { * Subclasses must implement this to take care of resetting their loader, * as per {@link #reset()}. This is not called by clients directly, * but as a result of a call to {@link #reset()}. + * This will always be called from the process's main thread. */ protected void onReset() { } @@ -265,7 +280,7 @@ public class Loader<D> { * if so, it simply calls {@link #forceLoad()}; otherwise, it sets a flag * so that {@link #takeContentChanged()} returns true. * - * <p>Must be called from the UI thread. + * <p>Must be called from the process's main thread. */ public void onContentChanged() { if (mStarted) { |