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