Loader.java revision b54e7a3d9f60ac605f404f9eb3c5e92ca51bbd23
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.content;
18
19import android.database.ContentObserver;
20import android.os.Handler;
21import android.util.DebugUtils;
22
23import java.io.FileDescriptor;
24import java.io.PrintWriter;
25
26/**
27 * An abstract class that performs asynchronous loading of data. While Loaders are active
28 * they should monitor the source of their data and deliver new results when the contents
29 * change.  See {@link android.app.LoaderManager} for more detail.
30 *
31 * <p><b>Note on threading:</b> Clients of loaders should as a rule perform
32 * any calls on to a Loader from the main thread of their process (that is,
33 * the thread the Activity callbacks and other things occur on).  Subclasses
34 * of Loader (such as {@link AsyncTaskLoader}) will often perform their work
35 * in a separate thread, but when delivering their results this too should
36 * be done on the main thread.</p>
37 *
38 * <p>Subclasses generally must implement at least {@link #onStartLoading()},
39 * {@link #onStopLoading()}, {@link #onForceLoad()}, and {@link #onReset()}.</p>
40 *
41 * <p>Most implementations should not derive directly from this class, but
42 * instead inherit from {@link AsyncTaskLoader}.</p>
43 *
44 * <div class="special reference">
45 * <h3>Developer Guides</h3>
46 * <p>For more information about using loaders, read the
47 * <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
48 * </div>
49 *
50 * @param <D> The result returned when the load is complete
51 */
52public class Loader<D> {
53    int mId;
54    OnLoadCompleteListener<D> mListener;
55    Context mContext;
56    boolean mStarted = false;
57    boolean mAbandoned = false;
58    boolean mReset = true;
59    boolean mContentChanged = false;
60
61    public final class ForceLoadContentObserver extends ContentObserver {
62        public ForceLoadContentObserver() {
63            super(new Handler());
64        }
65
66        @Override
67        public boolean deliverSelfNotifications() {
68            return true;
69        }
70
71        @Override
72        public void onChange(boolean selfChange) {
73            onContentChanged();
74        }
75    }
76
77    public interface OnLoadCompleteListener<D> {
78        /**
79         * Called on the thread that created the Loader when the load is complete.
80         *
81         * @param loader the loader that completed the load
82         * @param data the result of the load
83         */
84        public void onLoadComplete(Loader<D> loader, D data);
85    }
86
87    /**
88     * Stores away the application context associated with context.
89     * Since Loaders can be used across multiple activities it's dangerous to
90     * store the context directly; always use {@link #getContext()} to retrieve
91     * the Loader's Context, don't use the constructor argument directly.
92     * The Context returned by {@link #getContext} is safe to use across
93     * Activity instances.
94     *
95     * @param context used to retrieve the application context.
96     */
97    public Loader(Context context) {
98        mContext = context.getApplicationContext();
99    }
100
101    /**
102     * Sends the result of the load to the registered listener. Should only be called by subclasses.
103     *
104     * Must be called from the process's main thread.
105     *
106     * @param data the result of the load
107     */
108    public void deliverResult(D data) {
109        if (mListener != null) {
110            mListener.onLoadComplete(this, data);
111        }
112    }
113
114    /**
115     * @return an application context retrieved from the Context passed to the constructor.
116     */
117    public Context getContext() {
118        return mContext;
119    }
120
121    /**
122     * @return the ID of this loader
123     */
124    public int getId() {
125        return mId;
126    }
127
128    /**
129     * Registers a class that will receive callbacks when a load is complete.
130     * The callback will be called on the process's main thread so it's safe to
131     * pass the results to widgets.
132     *
133     * <p>Must be called from the process's main thread.
134     */
135    public void registerListener(int id, OnLoadCompleteListener<D> listener) {
136        if (mListener != null) {
137            throw new IllegalStateException("There is already a listener registered");
138        }
139        mListener = listener;
140        mId = id;
141    }
142
143    /**
144     * Remove a listener that was previously added with {@link #registerListener}.
145     *
146     * Must be called from the process's main thread.
147     */
148    public void unregisterListener(OnLoadCompleteListener<D> listener) {
149        if (mListener == null) {
150            throw new IllegalStateException("No listener register");
151        }
152        if (mListener != listener) {
153            throw new IllegalArgumentException("Attempting to unregister the wrong listener");
154        }
155        mListener = null;
156    }
157
158    /**
159     * Return whether this load has been started.  That is, its {@link #startLoading()}
160     * has been called and no calls to {@link #stopLoading()} or
161     * {@link #reset()} have yet been made.
162     */
163    public boolean isStarted() {
164        return mStarted;
165    }
166
167    /**
168     * Return whether this loader has been abandoned.  In this state, the
169     * loader <em>must not</em> report any new data, and <em>must</em> keep
170     * its last reported data valid until it is finally reset.
171     */
172    public boolean isAbandoned() {
173        return mAbandoned;
174    }
175
176    /**
177     * Return whether this load has been reset.  That is, either the loader
178     * has not yet been started for the first time, or its {@link #reset()}
179     * has been called.
180     */
181    public boolean isReset() {
182        return mReset;
183    }
184
185    /**
186     * Starts an asynchronous load of the Loader's data. When the result
187     * is ready the callbacks will be called on the process's main thread.
188     * If a previous load has been completed and is still valid
189     * the result may be passed to the callbacks immediately.
190     * The loader will monitor the source of
191     * the data set and may deliver future callbacks if the source changes.
192     * Calling {@link #stopLoading} will stop the delivery of callbacks.
193     *
194     * <p>This updates the Loader's internal state so that
195     * {@link #isStarted()} and {@link #isReset()} will return the correct
196     * values, and then calls the implementation's {@link #onStartLoading()}.
197     *
198     * <p>Must be called from the process's main thread.
199     */
200    public final void startLoading() {
201        mStarted = true;
202        mReset = false;
203        mAbandoned = false;
204        onStartLoading();
205    }
206
207    /**
208     * Subclasses must implement this to take care of loading their data,
209     * as per {@link #startLoading()}.  This is not called by clients directly,
210     * but as a result of a call to {@link #startLoading()}.
211     */
212    protected void onStartLoading() {
213    }
214
215    /**
216     * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
217     * loaded data set and load a new one.  This simply calls through to the
218     * implementation's {@link #onForceLoad()}.  You generally should only call this
219     * when the loader is started -- that is, {@link #isStarted()} returns true.
220     *
221     * <p>Must be called from the process's main thread.
222     */
223    public void forceLoad() {
224        onForceLoad();
225    }
226
227    /**
228     * Subclasses must implement this to take care of requests to {@link #forceLoad()}.
229     * This will always be called from the process's main thread.
230     */
231    protected void onForceLoad() {
232    }
233
234    /**
235     * Stops delivery of updates until the next time {@link #startLoading()} is called.
236     * Implementations should <em>not</em> invalidate their data at this point --
237     * clients are still free to use the last data the loader reported.  They will,
238     * however, typically stop reporting new data if the data changes; they can
239     * still monitor for changes, but must not report them to the client until and
240     * if {@link #startLoading()} is later called.
241     *
242     * <p>This updates the Loader's internal state so that
243     * {@link #isStarted()} will return the correct
244     * value, and then calls the implementation's {@link #onStopLoading()}.
245     *
246     * <p>Must be called from the process's main thread.
247     */
248    public void stopLoading() {
249        mStarted = false;
250        onStopLoading();
251    }
252
253    /**
254     * Subclasses must implement this to take care of stopping their loader,
255     * as per {@link #stopLoading()}.  This is not called by clients directly,
256     * but as a result of a call to {@link #stopLoading()}.
257     * This will always be called from the process's main thread.
258     */
259    protected void onStopLoading() {
260    }
261
262    /**
263     * Tell the Loader that it is being abandoned.  This is called prior
264     * to {@link #reset} to have it retain its current data but not report
265     * any new data.
266     */
267    public void abandon() {
268        mAbandoned = true;
269        onAbandon();
270    }
271
272    /**
273     * Subclasses implement this to take care of being abandoned.  This is
274     * an optional intermediate state prior to {@link #onReset()} -- it means that
275     * the client is no longer interested in any new data from the loader,
276     * so the loader must not report any further updates.  However, the
277     * loader <em>must</em> keep its last reported data valid until the final
278     * {@link #onReset()} happens.  You can retrieve the current abandoned
279     * state with {@link #isAbandoned}.
280     */
281    protected void onAbandon() {
282    }
283
284    /**
285     * Resets the state of the Loader.  The Loader should at this point free
286     * all of its resources, since it may never be called again; however, its
287     * {@link #startLoading()} may later be called at which point it must be
288     * able to start running again.
289     *
290     * <p>This updates the Loader's internal state so that
291     * {@link #isStarted()} and {@link #isReset()} will return the correct
292     * values, and then calls the implementation's {@link #onReset()}.
293     *
294     * <p>Must be called from the process's main thread.
295     */
296    public void reset() {
297        onReset();
298        mReset = true;
299        mStarted = false;
300        mAbandoned = false;
301        mContentChanged = false;
302    }
303
304    /**
305     * Subclasses must implement this to take care of resetting their loader,
306     * as per {@link #reset()}.  This is not called by clients directly,
307     * but as a result of a call to {@link #reset()}.
308     * This will always be called from the process's main thread.
309     */
310    protected void onReset() {
311    }
312
313    /**
314     * Take the current flag indicating whether the loader's content had
315     * changed while it was stopped.  If it had, true is returned and the
316     * flag is cleared.
317     */
318    public boolean takeContentChanged() {
319        boolean res = mContentChanged;
320        mContentChanged = false;
321        return res;
322    }
323
324    /**
325     * Called when {@link ForceLoadContentObserver} detects a change.  The
326     * default implementation checks to see if the loader is currently started;
327     * if so, it simply calls {@link #forceLoad()}; otherwise, it sets a flag
328     * so that {@link #takeContentChanged()} returns true.
329     *
330     * <p>Must be called from the process's main thread.
331     */
332    public void onContentChanged() {
333        if (mStarted) {
334            forceLoad();
335        } else {
336            // This loader has been stopped, so we don't want to load
337            // new data right now...  but keep track of it changing to
338            // refresh later if we start again.
339            mContentChanged = true;
340        }
341    }
342
343    /**
344     * For debugging, converts an instance of the Loader's data class to
345     * a string that can be printed.  Must handle a null data.
346     */
347    public String dataToString(D data) {
348        StringBuilder sb = new StringBuilder(64);
349        DebugUtils.buildShortClassTag(data, sb);
350        sb.append("}");
351        return sb.toString();
352    }
353
354    @Override
355    public String toString() {
356        StringBuilder sb = new StringBuilder(64);
357        DebugUtils.buildShortClassTag(this, sb);
358        sb.append(" id=");
359        sb.append(mId);
360        sb.append("}");
361        return sb.toString();
362    }
363
364    /**
365     * Print the Loader's state into the given stream.
366     *
367     * @param prefix Text to print at the front of each line.
368     * @param fd The raw file descriptor that the dump is being sent to.
369     * @param writer A PrintWriter to which the dump is to be set.
370     * @param args Additional arguments to the dump request.
371     */
372    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
373        writer.print(prefix); writer.print("mId="); writer.print(mId);
374                writer.print(" mListener="); writer.println(mListener);
375        writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
376                writer.print(" mContentChanged="); writer.print(mContentChanged);
377                writer.print(" mAbandoned="); writer.print(mAbandoned);
378                writer.print(" mReset="); writer.println(mReset);
379    }
380}