Loader.java revision 247fe74c934cb3fba85aae7e051a8044f460fb11
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.
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()}.
40 *
41 * @param <D> The result returned when the load is complete
42 */
43public class Loader<D> {
44    int mId;
45    OnLoadCompleteListener<D> mListener;
46    Context mContext;
47    boolean mStarted = false;
48    boolean mReset = true;
49    boolean mContentChanged = false;
50
51    public final class ForceLoadContentObserver extends ContentObserver {
52        public ForceLoadContentObserver() {
53            super(new Handler());
54        }
55
56        @Override
57        public boolean deliverSelfNotifications() {
58            return true;
59        }
60
61        @Override
62        public void onChange(boolean selfChange) {
63            onContentChanged();
64        }
65    }
66
67    public interface OnLoadCompleteListener<D> {
68        /**
69         * Called on the thread that created the Loader when the load is complete.
70         *
71         * @param loader the loader that completed the load
72         * @param data the result of the load
73         */
74        public void onLoadComplete(Loader<D> loader, D data);
75    }
76
77    /**
78     * Stores away the application context associated with context. Since Loaders can be used
79     * across multiple activities it's dangerous to store the context directly.
80     *
81     * @param context used to retrieve the application context.
82     */
83    public Loader(Context context) {
84        mContext = context.getApplicationContext();
85    }
86
87    /**
88     * Sends the result of the load to the registered listener. Should only be called by subclasses.
89     *
90     * Must be called from the process's main thread.
91     *
92     * @param data the result of the load
93     */
94    public void deliverResult(D data) {
95        if (mListener != null) {
96            mListener.onLoadComplete(this, data);
97        }
98    }
99
100    /**
101     * @return an application context retrieved from the Context passed to the constructor.
102     */
103    public Context getContext() {
104        return mContext;
105    }
106
107    /**
108     * @return the ID of this loader
109     */
110    public int getId() {
111        return mId;
112    }
113
114    /**
115     * Registers a class that will receive callbacks when a load is complete.
116     * The callback will be called on the process's main thread so it's safe to
117     * pass the results to widgets.
118     *
119     * <p>Must be called from the process's main thread.
120     */
121    public void registerListener(int id, OnLoadCompleteListener<D> listener) {
122        if (mListener != null) {
123            throw new IllegalStateException("There is already a listener registered");
124        }
125        mListener = listener;
126        mId = id;
127    }
128
129    /**
130     * Remove a listener that was previously added with {@link #registerListener}.
131     *
132     * Must be called from the process's main thread.
133     */
134    public void unregisterListener(OnLoadCompleteListener<D> listener) {
135        if (mListener == null) {
136            throw new IllegalStateException("No listener register");
137        }
138        if (mListener != listener) {
139            throw new IllegalArgumentException("Attempting to unregister the wrong listener");
140        }
141        mListener = null;
142    }
143
144    /**
145     * Return whether this load has been started.  That is, its {@link #startLoading()}
146     * has been called and no calls to {@link #stopLoading()} or
147     * {@link #reset()} have yet been made.
148     */
149    public boolean isStarted() {
150        return mStarted;
151    }
152
153    /**
154     * Return whether this load has been reset.  That is, either the loader
155     * has not yet been started for the first time, or its {@link #reset()}
156     * has been called.
157     */
158    public boolean isReset() {
159        return mReset;
160    }
161
162    /**
163     * Starts an asynchronous load of the Loader's data. When the result
164     * is ready the callbacks will be called on the process's main thread.
165     * If a previous load has been completed and is still valid
166     * the result may be passed to the callbacks immediately.
167     * The loader will monitor the source of
168     * the data set and may deliver future callbacks if the source changes.
169     * Calling {@link #stopLoading} will stop the delivery of callbacks.
170     *
171     * <p>This updates the Loader's internal state so that
172     * {@link #isStarted()} and {@link #isReset()} will return the correct
173     * values, and then calls the implementation's {@link #onStartLoading()}.
174     *
175     * <p>Must be called from the process's main thread.
176     */
177    public final void startLoading() {
178        mStarted = true;
179        mReset = false;
180        onStartLoading();
181    }
182
183    /**
184     * Subclasses must implement this to take care of loading their data,
185     * as per {@link #startLoading()}.  This is not called by clients directly,
186     * but as a result of a call to {@link #startLoading()}.
187     */
188    protected void onStartLoading() {
189    }
190
191    /**
192     * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
193     * loaded data set and load a new one.  This simply calls through to the
194     * implementation's {@link #onForceLoad()}.  You generally should only call this
195     * when the loader is started -- that is, {@link #isStarted()} returns true.
196     *
197     * <p>Must be called from the process's main thread.
198     */
199    public void forceLoad() {
200        onForceLoad();
201    }
202
203    /**
204     * Subclasses must implement this to take care of requests to {@link #forceLoad()}.
205     * This will always be called from the process's main thread.
206     */
207    protected void onForceLoad() {
208    }
209
210    /**
211     * Stops delivery of updates until the next time {@link #startLoading()} is called.
212     * Implementations should <em>not</em> invalidate their data at this point --
213     * clients are still free to use the last data the loader reported.  They will,
214     * however, typically stop reporting new data if the data changes; they can
215     * still monitor for changes, but must not report them to the client until and
216     * if {@link #startLoading()} is later called.
217     *
218     * <p>This updates the Loader's internal state so that
219     * {@link #isStarted()} will return the correct
220     * value, and then calls the implementation's {@link #onStopLoading()}.
221     *
222     * <p>Must be called from the process's main thread.
223     */
224    public void stopLoading() {
225        mStarted = false;
226        onStopLoading();
227    }
228
229    /**
230     * Subclasses must implement this to take care of stopping their loader,
231     * as per {@link #stopLoading()}.  This is not called by clients directly,
232     * but as a result of a call to {@link #stopLoading()}.
233     * This will always be called from the process's main thread.
234     */
235    protected void onStopLoading() {
236    }
237
238    /**
239     * Resets the state of the Loader.  The Loader should at this point free
240     * all of its resources, since it may never be called again; however, its
241     * {@link #startLoading()} may later be called at which point it must be
242     * able to start running again.
243     *
244     * <p>This updates the Loader's internal state so that
245     * {@link #isStarted()} and {@link #isReset()} will return the correct
246     * values, and then calls the implementation's {@link #onReset()}.
247     *
248     * <p>Must be called from the process's main thread.
249     */
250    public void reset() {
251        onReset();
252        mReset = true;
253        mStarted = false;
254        mContentChanged = false;
255    }
256
257    /**
258     * Subclasses must implement this to take care of resetting their loader,
259     * as per {@link #reset()}.  This is not called by clients directly,
260     * but as a result of a call to {@link #reset()}.
261     * This will always be called from the process's main thread.
262     */
263    protected void onReset() {
264    }
265
266    /**
267     * Take the current flag indicating whether the loader's content had
268     * changed while it was stopped.  If it had, true is returned and the
269     * flag is cleared.
270     */
271    public boolean takeContentChanged() {
272        boolean res = mContentChanged;
273        mContentChanged = false;
274        return res;
275    }
276
277    /**
278     * Called when {@link ForceLoadContentObserver} detects a change.  The
279     * default implementation checks to see if the loader is currently started;
280     * if so, it simply calls {@link #forceLoad()}; otherwise, it sets a flag
281     * so that {@link #takeContentChanged()} returns true.
282     *
283     * <p>Must be called from the process's main thread.
284     */
285    public void onContentChanged() {
286        if (mStarted) {
287            forceLoad();
288        } else {
289            // This loader has been stopped, so we don't want to load
290            // new data right now...  but keep track of it changing to
291            // refresh later if we start again.
292            mContentChanged = true;
293        }
294    }
295
296    /**
297     * For debugging, converts an instance of the Loader's data class to
298     * a string that can be printed.  Must handle a null data.
299     */
300    public String dataToString(D data) {
301        StringBuilder sb = new StringBuilder(64);
302        DebugUtils.buildShortClassTag(data, sb);
303        sb.append("}");
304        return sb.toString();
305    }
306
307    @Override
308    public String toString() {
309        StringBuilder sb = new StringBuilder(64);
310        DebugUtils.buildShortClassTag(this, sb);
311        sb.append(" id=");
312        sb.append(mId);
313        sb.append("}");
314        return sb.toString();
315    }
316
317    /**
318     * Print the Loader's state into the given stream.
319     *
320     * @param prefix Text to print at the front of each line.
321     * @param fd The raw file descriptor that the dump is being sent to.
322     * @param writer A PrintWriter to which the dump is to be set.
323     * @param args Additional arguments to the dump request.
324     */
325    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
326        writer.print(prefix); writer.print("mId="); writer.print(mId);
327                writer.print(" mListener="); writer.println(mListener);
328        writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
329                writer.print(" mContentChanged="); writer.print(mContentChanged);
330                writer.print(" mReset="); writer.println(mReset);
331    }
332}