LoaderManager.java revision f5100a7823a60f369a6c0014b1b80ca945128e4a
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.app;
18
19import android.content.Loader;
20import android.os.Bundle;
21import android.util.SparseArray;
22
23/**
24 * Interface associated with an {@link Activity} or {@link Fragment} for managing
25 * one or more {@link android.content.Loader} instances associated with it.
26 */
27public interface LoaderManager {
28    /**
29     * Callback interface for a client to interact with the manager.
30     */
31    public interface LoaderCallbacks<D> {
32        /**
33         * Instantiate and return a new Loader for the given ID.
34         *
35         * @param id The ID whose loader is to be created.
36         * @param args Any arguments supplied by the caller.
37         * @return Return a new Loader instance that is ready to start loading.
38         */
39        public Loader<D> onCreateLoader(int id, Bundle args);
40
41        /**
42         * Called when a previously created loader has finished its load.
43         * @param loader The Loader that has finished.
44         * @param data The data generated by the Loader.
45         */
46        public void onLoadFinished(Loader<D> loader, D data);
47    }
48
49    /**
50     * Ensures a loader is initialized and active.  If the loader doesn't
51     * already exist, one is created and (if the activity/fragment is currently
52     * started) starts the loader.  Otherwise the last created
53     * loader is re-used.
54     *
55     * <p>In either case, the given callback is associated with the loader, and
56     * will be called as the loader state changes.  If at the point of call
57     * the caller is in its started state, and the requested loader
58     * already exists and has generated its data, then
59     * callback. {@link LoaderCallbacks#onLoadFinished} will
60     * be called immediately (inside of this function), so you must be prepared
61     * for this to happen.
62     */
63    public <D> Loader<D> initLoader(int id, Bundle args,
64            LoaderManager.LoaderCallbacks<D> callback);
65
66    /**
67     * Creates a new loader in this manager, registers the callbacks to it,
68     * and (if the activity/fragment is currently started) starts loading it.
69     * If a loader with the same id has previously been
70     * started it will automatically be destroyed when the new loader completes
71     * its work. The callback will be delivered before the old loader
72     * is destroyed.
73     */
74    public <D> Loader<D> restartLoader(int id, Bundle args,
75            LoaderManager.LoaderCallbacks<D> callback);
76
77    /**
78     * Stops and removes the loader with the given ID.
79     */
80    public void stopLoader(int id);
81
82    /**
83     * Return the Loader with the given id or null if no matching Loader
84     * is found.
85     */
86    public <D> Loader<D> getLoader(int id);
87}
88
89class LoaderManagerImpl implements LoaderManager {
90    final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>();
91    final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>();
92    boolean mStarted;
93    boolean mRetaining;
94    boolean mRetainingStarted;
95
96    final class LoaderInfo implements Loader.OnLoadCompleteListener<Object> {
97        final int mId;
98        final Bundle mArgs;
99        LoaderManager.LoaderCallbacks<Object> mCallbacks;
100        Loader<Object> mLoader;
101        Object mData;
102        boolean mStarted;
103        boolean mRetaining;
104        boolean mRetainingStarted;
105        boolean mDestroyed;
106        boolean mListenerRegistered;
107
108        public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {
109            mId = id;
110            mArgs = args;
111            mCallbacks = callbacks;
112        }
113
114        void start() {
115            if (mRetaining && mRetainingStarted) {
116                // Our owner is started, but we were being retained from a
117                // previous instance in the started state...  so there is really
118                // nothing to do here, since the loaders are still started.
119                mStarted = true;
120                return;
121            }
122
123            if (mStarted) {
124                // If loader already started, don't restart.
125                return;
126            }
127
128            if (mLoader == null && mCallbacks != null) {
129               mLoader = mCallbacks.onCreateLoader(mId, mArgs);
130            }
131            if (mLoader != null) {
132                if (!mListenerRegistered) {
133                    mLoader.registerListener(mId, this);
134                    mListenerRegistered = true;
135                }
136                mLoader.startLoading();
137                mStarted = true;
138            }
139        }
140
141        void retain() {
142            mRetaining = true;
143            mRetainingStarted = mStarted;
144            mStarted = false;
145            mCallbacks = null;
146        }
147
148        void finishRetain() {
149            if (mRetaining) {
150                mRetaining = false;
151                if (mStarted != mRetainingStarted) {
152                    if (!mStarted) {
153                        // This loader was retained in a started state, but
154                        // at the end of retaining everything our owner is
155                        // no longer started...  so make it stop.
156                        stop();
157                    }
158                }
159                if (mStarted && mData != null && mCallbacks != null) {
160                    // This loader was retained, and now at the point of
161                    // finishing the retain we find we remain started, have
162                    // our data, and the owner has a new callback...  so
163                    // let's deliver the data now.
164                    mCallbacks.onLoadFinished(mLoader, mData);
165                }
166            }
167        }
168
169        void stop() {
170            mStarted = false;
171            if (mLoader != null && mListenerRegistered) {
172                // Let the loader know we're done with it
173                mListenerRegistered = false;
174                mLoader.unregisterListener(this);
175                mLoader.stopLoading();
176            }
177        }
178
179        void destroy() {
180            mDestroyed = true;
181            mCallbacks = null;
182            if (mLoader != null) {
183                if (mListenerRegistered) {
184                    mListenerRegistered = false;
185                    mLoader.unregisterListener(this);
186                }
187                mLoader.destroy();
188            }
189        }
190
191        @Override public void onLoadComplete(Loader<Object> loader, Object data) {
192            if (mDestroyed) {
193                return;
194            }
195
196            // Notify of the new data so the app can switch out the old data before
197            // we try to destroy it.
198            mData = data;
199            if (mCallbacks != null) {
200                mCallbacks.onLoadFinished(loader, data);
201            }
202
203            // Look for an inactive loader and destroy it if found
204            LoaderInfo info = mInactiveLoaders.get(mId);
205            if (info != null) {
206                Loader<Object> oldLoader = info.mLoader;
207                if (oldLoader != null) {
208                    if (info.mListenerRegistered) {
209                        oldLoader.unregisterListener(info);
210                    }
211                    oldLoader.destroy();
212                }
213                mInactiveLoaders.remove(mId);
214            }
215        }
216    }
217
218    LoaderManagerImpl(boolean started) {
219        mStarted = started;
220    }
221
222    private LoaderInfo createLoader(int id, Bundle args,
223            LoaderManager.LoaderCallbacks<Object> callback) {
224        LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
225        mLoaders.put(id, info);
226        Loader<Object> loader = callback.onCreateLoader(id, args);
227        info.mLoader = (Loader<Object>)loader;
228        if (mStarted) {
229            // The activity will start all existing loaders in it's onStart(),
230            // so only start them here if we're past that point of the activitiy's
231            // life cycle
232            info.start();
233        }
234        return info;
235    }
236
237    @SuppressWarnings("unchecked")
238    public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
239        LoaderInfo info = mLoaders.get(id);
240
241        if (info == null) {
242            // Loader doesn't already exist; create.
243            info = createLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
244        } else {
245            info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
246        }
247
248        if (info.mData != null && mStarted) {
249            // If the loader has already generated its data, report it now.
250            info.mCallbacks.onLoadFinished(info.mLoader, info.mData);
251        }
252
253        return (Loader<D>)info.mLoader;
254    }
255
256    @SuppressWarnings("unchecked")
257    public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
258        LoaderInfo info = mLoaders.get(id);
259        if (info != null) {
260            if (mInactiveLoaders.get(id) != null) {
261                // We already have an inactive loader for this ID that we are
262                // waiting for!  Now we have three active loaders... let's just
263                // drop the one in the middle, since we are still waiting for
264                // its result but that result is already out of date.
265                info.destroy();
266            } else {
267                // Keep track of the previous instance of this loader so we can destroy
268                // it when the new one completes.
269                mInactiveLoaders.put(id, info);
270            }
271        }
272
273        info = createLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
274        return (Loader<D>)info.mLoader;
275    }
276
277    public void stopLoader(int id) {
278        int idx = mLoaders.indexOfKey(id);
279        if (idx >= 0) {
280            LoaderInfo info = mLoaders.valueAt(idx);
281            mLoaders.removeAt(idx);
282            info.destroy();
283        }
284    }
285
286    @SuppressWarnings("unchecked")
287    public <D> Loader<D> getLoader(int id) {
288        LoaderInfo loaderInfo = mLoaders.get(id);
289        if (loaderInfo != null) {
290            return (Loader<D>)mLoaders.get(id).mLoader;
291        }
292        return null;
293    }
294
295    void doStart() {
296        // Call out to sub classes so they can start their loaders
297        // Let the existing loaders know that we want to be notified when a load is complete
298        for (int i = mLoaders.size()-1; i >= 0; i--) {
299            mLoaders.valueAt(i).start();
300        }
301        mStarted = true;
302    }
303
304    void doStop() {
305        for (int i = mLoaders.size()-1; i >= 0; i--) {
306            mLoaders.valueAt(i).stop();
307        }
308        mStarted = false;
309    }
310
311    void doRetain() {
312        mRetaining = true;
313        mStarted = false;
314        for (int i = mLoaders.size()-1; i >= 0; i--) {
315            mLoaders.valueAt(i).retain();
316        }
317    }
318
319    void finishRetain() {
320        mRetaining = false;
321        for (int i = mLoaders.size()-1; i >= 0; i--) {
322            mLoaders.valueAt(i).finishRetain();
323        }
324    }
325
326    void doDestroy() {
327        if (!mRetaining) {
328            for (int i = mLoaders.size()-1; i >= 0; i--) {
329                mLoaders.valueAt(i).destroy();
330            }
331        }
332
333        for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
334            mInactiveLoaders.valueAt(i).destroy();
335        }
336        mInactiveLoaders.clear();
337    }
338}
339