LoaderManager.java revision 4911b783aa9f4af5ac919db861751d350471f5ef
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            }
176        }
177
178        void destroy() {
179            mDestroyed = true;
180            mCallbacks = null;
181            if (mLoader != null) {
182                if (mListenerRegistered) {
183                    mListenerRegistered = false;
184                    mLoader.unregisterListener(this);
185                }
186                mLoader.destroy();
187            }
188        }
189
190        @Override public void onLoadComplete(Loader<Object> loader, Object data) {
191            if (mDestroyed) {
192                return;
193            }
194
195            // Notify of the new data so the app can switch out the old data before
196            // we try to destroy it.
197            mData = data;
198            if (mCallbacks != null) {
199                mCallbacks.onLoadFinished(loader, data);
200            }
201
202            // Look for an inactive loader and destroy it if found
203            LoaderInfo info = mInactiveLoaders.get(mId);
204            if (info != null) {
205                Loader<Object> oldLoader = info.mLoader;
206                if (oldLoader != null) {
207                    if (info.mListenerRegistered) {
208                        oldLoader.unregisterListener(info);
209                    }
210                    oldLoader.destroy();
211                }
212                mInactiveLoaders.remove(mId);
213            }
214        }
215    }
216
217    LoaderManagerImpl(boolean started) {
218        mStarted = started;
219    }
220
221    private LoaderInfo createLoader(int id, Bundle args,
222            LoaderManager.LoaderCallbacks<Object> callback) {
223        LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
224        mLoaders.put(id, info);
225        Loader<Object> loader = callback.onCreateLoader(id, args);
226        info.mLoader = (Loader<Object>)loader;
227        if (mStarted) {
228            // The activity will start all existing loaders in it's onStart(),
229            // so only start them here if we're past that point of the activitiy's
230            // life cycle
231            info.start();
232        }
233        return info;
234    }
235
236    @SuppressWarnings("unchecked")
237    public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
238        LoaderInfo info = mLoaders.get(id);
239
240        if (info == null) {
241            // Loader doesn't already exist; create.
242            info = createLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
243        } else {
244            info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
245        }
246
247        if (info.mData != null && mStarted) {
248            // If the loader has already generated its data, report it now.
249            info.mCallbacks.onLoadFinished(info.mLoader, info.mData);
250        }
251
252        return (Loader<D>)info.mLoader;
253    }
254
255    @SuppressWarnings("unchecked")
256    public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
257        LoaderInfo info = mLoaders.get(id);
258        if (info != null) {
259            if (mInactiveLoaders.get(id) != null) {
260                // We already have an inactive loader for this ID that we are
261                // waiting for!  Now we have three active loaders... let's just
262                // drop the one in the middle, since we are still waiting for
263                // its result but that result is already out of date.
264                info.destroy();
265            } else {
266                // Keep track of the previous instance of this loader so we can destroy
267                // it when the new one completes.
268                mInactiveLoaders.put(id, info);
269            }
270        }
271
272        info = createLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
273        return (Loader<D>)info.mLoader;
274    }
275
276    public void stopLoader(int id) {
277        int idx = mLoaders.indexOfKey(id);
278        if (idx >= 0) {
279            LoaderInfo info = mLoaders.valueAt(idx);
280            mLoaders.removeAt(idx);
281            info.destroy();
282        }
283    }
284
285    @SuppressWarnings("unchecked")
286    public <D> Loader<D> getLoader(int id) {
287        LoaderInfo loaderInfo = mLoaders.get(id);
288        if (loaderInfo != null) {
289            return (Loader<D>)mLoaders.get(id).mLoader;
290        }
291        return null;
292    }
293
294    void doStart() {
295        // Call out to sub classes so they can start their loaders
296        // Let the existing loaders know that we want to be notified when a load is complete
297        for (int i = mLoaders.size()-1; i >= 0; i--) {
298            mLoaders.valueAt(i).start();
299        }
300        mStarted = true;
301    }
302
303    void doStop() {
304        for (int i = mLoaders.size()-1; i >= 0; i--) {
305            mLoaders.valueAt(i).stop();
306        }
307        mStarted = false;
308    }
309
310    void doRetain() {
311        mRetaining = true;
312        mStarted = false;
313        for (int i = mLoaders.size()-1; i >= 0; i--) {
314            mLoaders.valueAt(i).retain();
315        }
316    }
317
318    void finishRetain() {
319        mRetaining = false;
320        for (int i = mLoaders.size()-1; i >= 0; i--) {
321            mLoaders.valueAt(i).finishRetain();
322        }
323    }
324
325    void doDestroy() {
326        if (!mRetaining) {
327            for (int i = mLoaders.size()-1; i >= 0; i--) {
328                mLoaders.valueAt(i).destroy();
329            }
330        }
331
332        for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
333            mInactiveLoaders.valueAt(i).destroy();
334        }
335        mInactiveLoaders.clear();
336    }
337}
338