• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.app;
18 
19 import android.content.Loader;
20 import android.os.Bundle;
21 import android.util.DebugUtils;
22 import android.util.Log;
23 import android.util.SparseArray;
24 
25 import java.io.FileDescriptor;
26 import java.io.PrintWriter;
27 import java.lang.reflect.Modifier;
28 
29 /**
30  * Interface associated with an {@link Activity} or {@link Fragment} for managing
31  * one or more {@link android.content.Loader} instances associated with it.  This
32  * helps an application manage longer-running operations in conjunction with the
33  * Activity or Fragment lifecycle; the most common use of this is with a
34  * {@link android.content.CursorLoader}, however applications are free to write
35  * their own loaders for loading other types of data.
36  *
37  * While the LoaderManager API was introduced in
38  * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, a version of the API
39  * at is also available for use on older platforms through
40  * {@link android.support.v4.app.FragmentActivity}.  See the blog post
41  * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
42  * Fragments For All</a> for more details.
43  *
44  * <p>As an example, here is the full implementation of a {@link Fragment}
45  * that displays a {@link android.widget.ListView} containing the results of
46  * a query against the contacts content provider.  It uses a
47  * {@link android.content.CursorLoader} to manage the query on the provider.
48  *
49  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java
50  *      fragment_cursor}
51  *
52  * <div class="special reference">
53  * <h3>Developer Guides</h3>
54  * <p>For more information about using loaders, read the
55  * <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
56  * </div>
57  */
58 public abstract class LoaderManager {
59     /**
60      * Callback interface for a client to interact with the manager.
61      */
62     public interface LoaderCallbacks<D> {
63         /**
64          * Instantiate and return a new Loader for the given ID.
65          *
66          * @param id The ID whose loader is to be created.
67          * @param args Any arguments supplied by the caller.
68          * @return Return a new Loader instance that is ready to start loading.
69          */
onCreateLoader(int id, Bundle args)70         public Loader<D> onCreateLoader(int id, Bundle args);
71 
72         /**
73          * Called when a previously created loader has finished its load.  Note
74          * that normally an application is <em>not</em> allowed to commit fragment
75          * transactions while in this call, since it can happen after an
76          * activity's state is saved.  See {@link FragmentManager#beginTransaction()
77          * FragmentManager.openTransaction()} for further discussion on this.
78          *
79          * <p>This function is guaranteed to be called prior to the release of
80          * the last data that was supplied for this Loader.  At this point
81          * you should remove all use of the old data (since it will be released
82          * soon), but should not do your own release of the data since its Loader
83          * owns it and will take care of that.  The Loader will take care of
84          * management of its data so you don't have to.  In particular:
85          *
86          * <ul>
87          * <li> <p>The Loader will monitor for changes to the data, and report
88          * them to you through new calls here.  You should not monitor the
89          * data yourself.  For example, if the data is a {@link android.database.Cursor}
90          * and you place it in a {@link android.widget.CursorAdapter}, use
91          * the {@link android.widget.CursorAdapter#CursorAdapter(android.content.Context,
92          * android.database.Cursor, int)} constructor <em>without</em> passing
93          * in either {@link android.widget.CursorAdapter#FLAG_AUTO_REQUERY}
94          * or {@link android.widget.CursorAdapter#FLAG_REGISTER_CONTENT_OBSERVER}
95          * (that is, use 0 for the flags argument).  This prevents the CursorAdapter
96          * from doing its own observing of the Cursor, which is not needed since
97          * when a change happens you will get a new Cursor throw another call
98          * here.
99          * <li> The Loader will release the data once it knows the application
100          * is no longer using it.  For example, if the data is
101          * a {@link android.database.Cursor} from a {@link android.content.CursorLoader},
102          * you should not call close() on it yourself.  If the Cursor is being placed in a
103          * {@link android.widget.CursorAdapter}, you should use the
104          * {@link android.widget.CursorAdapter#swapCursor(android.database.Cursor)}
105          * method so that the old Cursor is not closed.
106          * </ul>
107          *
108          * @param loader The Loader that has finished.
109          * @param data The data generated by the Loader.
110          */
onLoadFinished(Loader<D> loader, D data)111         public void onLoadFinished(Loader<D> loader, D data);
112 
113         /**
114          * Called when a previously created loader is being reset, and thus
115          * making its data unavailable.  The application should at this point
116          * remove any references it has to the Loader's data.
117          *
118          * @param loader The Loader that is being reset.
119          */
onLoaderReset(Loader<D> loader)120         public void onLoaderReset(Loader<D> loader);
121     }
122 
123     /**
124      * Ensures a loader is initialized and active.  If the loader doesn't
125      * already exist, one is created and (if the activity/fragment is currently
126      * started) starts the loader.  Otherwise the last created
127      * loader is re-used.
128      *
129      * <p>In either case, the given callback is associated with the loader, and
130      * will be called as the loader state changes.  If at the point of call
131      * the caller is in its started state, and the requested loader
132      * already exists and has generated its data, then
133      * callback {@link LoaderCallbacks#onLoadFinished} will
134      * be called immediately (inside of this function), so you must be prepared
135      * for this to happen.
136      *
137      * @param id A unique identifier for this loader.  Can be whatever you want.
138      * Identifiers are scoped to a particular LoaderManager instance.
139      * @param args Optional arguments to supply to the loader at construction.
140      * If a loader already exists (a new one does not need to be created), this
141      * parameter will be ignored and the last arguments continue to be used.
142      * @param callback Interface the LoaderManager will call to report about
143      * changes in the state of the loader.  Required.
144      */
initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback)145     public abstract <D> Loader<D> initLoader(int id, Bundle args,
146             LoaderManager.LoaderCallbacks<D> callback);
147 
148     /**
149      * Starts a new or restarts an existing {@link android.content.Loader} in
150      * this manager, registers the callbacks to it,
151      * and (if the activity/fragment is currently started) starts loading it.
152      * If a loader with the same id has previously been
153      * started it will automatically be destroyed when the new loader completes
154      * its work. The callback will be delivered before the old loader
155      * is destroyed.
156      *
157      * @param id A unique identifier for this loader.  Can be whatever you want.
158      * Identifiers are scoped to a particular LoaderManager instance.
159      * @param args Optional arguments to supply to the loader at construction.
160      * @param callback Interface the LoaderManager will call to report about
161      * changes in the state of the loader.  Required.
162      */
restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback)163     public abstract <D> Loader<D> restartLoader(int id, Bundle args,
164             LoaderManager.LoaderCallbacks<D> callback);
165 
166     /**
167      * Stops and removes the loader with the given ID.  If this loader
168      * had previously reported data to the client through
169      * {@link LoaderCallbacks#onLoadFinished(Loader, Object)}, a call
170      * will be made to {@link LoaderCallbacks#onLoaderReset(Loader)}.
171      */
destroyLoader(int id)172     public abstract void destroyLoader(int id);
173 
174     /**
175      * Return the Loader with the given id or null if no matching Loader
176      * is found.
177      */
getLoader(int id)178     public abstract <D> Loader<D> getLoader(int id);
179 
180     /**
181      * Print the LoaderManager's state into the given stream.
182      *
183      * @param prefix Text to print at the front of each line.
184      * @param fd The raw file descriptor that the dump is being sent to.
185      * @param writer A PrintWriter to which the dump is to be set.
186      * @param args Additional arguments to the dump request.
187      */
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)188     public abstract void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args);
189 
190     /**
191      * Control whether the framework's internal loader manager debugging
192      * logs are turned on.  If enabled, you will see output in logcat as
193      * the framework performs loader operations.
194      */
enableDebugLogging(boolean enabled)195     public static void enableDebugLogging(boolean enabled) {
196         LoaderManagerImpl.DEBUG = enabled;
197     }
198 
199     /** @hide for internal testing only */
getFragmentHostCallback()200     public FragmentHostCallback getFragmentHostCallback() { return null; }
201 }
202 
203 class LoaderManagerImpl extends LoaderManager {
204     static final String TAG = "LoaderManager";
205     static boolean DEBUG = false;
206 
207     // These are the currently active loaders.  A loader is here
208     // from the time its load is started until it has been explicitly
209     // stopped or restarted by the application.
210     final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>(0);
211 
212     // These are previously run loaders.  This list is maintained internally
213     // to avoid destroying a loader while an application is still using it.
214     // It allows an application to restart a loader, but continue using its
215     // previously run loader until the new loader's data is available.
216     final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>(0);
217 
218     final String mWho;
219 
220     boolean mStarted;
221     boolean mRetaining;
222     boolean mRetainingStarted;
223 
224     boolean mCreatingLoader;
225     private FragmentHostCallback mHost;
226 
227     final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>,
228             Loader.OnLoadCanceledListener<Object> {
229         final int mId;
230         final Bundle mArgs;
231         LoaderManager.LoaderCallbacks<Object> mCallbacks;
232         Loader<Object> mLoader;
233         boolean mHaveData;
234         boolean mDeliveredData;
235         Object mData;
236         boolean mStarted;
237         boolean mRetaining;
238         boolean mRetainingStarted;
239         boolean mReportNextStart;
240         boolean mDestroyed;
241         boolean mListenerRegistered;
242 
243         LoaderInfo mPendingLoader;
244 
LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks)245         public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {
246             mId = id;
247             mArgs = args;
248             mCallbacks = callbacks;
249         }
250 
start()251         void start() {
252             if (mRetaining && mRetainingStarted) {
253                 // Our owner is started, but we were being retained from a
254                 // previous instance in the started state...  so there is really
255                 // nothing to do here, since the loaders are still started.
256                 mStarted = true;
257                 return;
258             }
259 
260             if (mStarted) {
261                 // If loader already started, don't restart.
262                 return;
263             }
264 
265             mStarted = true;
266 
267             if (DEBUG) Log.v(TAG, "  Starting: " + this);
268             if (mLoader == null && mCallbacks != null) {
269                mLoader = mCallbacks.onCreateLoader(mId, mArgs);
270             }
271             if (mLoader != null) {
272                 if (mLoader.getClass().isMemberClass()
273                         && !Modifier.isStatic(mLoader.getClass().getModifiers())) {
274                     throw new IllegalArgumentException(
275                             "Object returned from onCreateLoader must not be a non-static inner member class: "
276                             + mLoader);
277                 }
278                 if (!mListenerRegistered) {
279                     mLoader.registerListener(mId, this);
280                     mLoader.registerOnLoadCanceledListener(this);
281                     mListenerRegistered = true;
282                 }
283                 mLoader.startLoading();
284             }
285         }
286 
retain()287         void retain() {
288             if (DEBUG) Log.v(TAG, "  Retaining: " + this);
289             mRetaining = true;
290             mRetainingStarted = mStarted;
291             mStarted = false;
292             mCallbacks = null;
293         }
294 
finishRetain()295         void finishRetain() {
296             if (mRetaining) {
297                 if (DEBUG) Log.v(TAG, "  Finished Retaining: " + this);
298                 mRetaining = false;
299                 if (mStarted != mRetainingStarted) {
300                     if (!mStarted) {
301                         // This loader was retained in a started state, but
302                         // at the end of retaining everything our owner is
303                         // no longer started...  so make it stop.
304                         stop();
305                     }
306                 }
307             }
308 
309             if (mStarted && mHaveData && !mReportNextStart) {
310                 // This loader has retained its data, either completely across
311                 // a configuration change or just whatever the last data set
312                 // was after being restarted from a stop, and now at the point of
313                 // finishing the retain we find we remain started, have
314                 // our data, and the owner has a new callback...  so
315                 // let's deliver the data now.
316                 callOnLoadFinished(mLoader, mData);
317             }
318         }
319 
reportStart()320         void reportStart() {
321             if (mStarted) {
322                 if (mReportNextStart) {
323                     mReportNextStart = false;
324                     if (mHaveData && !mRetaining) {
325                         callOnLoadFinished(mLoader, mData);
326                     }
327                 }
328             }
329         }
330 
stop()331         void stop() {
332             if (DEBUG) Log.v(TAG, "  Stopping: " + this);
333             mStarted = false;
334             if (!mRetaining) {
335                 if (mLoader != null && mListenerRegistered) {
336                     // Let the loader know we're done with it
337                     mListenerRegistered = false;
338                     mLoader.unregisterListener(this);
339                     mLoader.unregisterOnLoadCanceledListener(this);
340                     mLoader.stopLoading();
341                 }
342             }
343         }
344 
cancel()345         boolean cancel() {
346             if (DEBUG) Log.v(TAG, "  Canceling: " + this);
347             if (mStarted && mLoader != null && mListenerRegistered) {
348                 final boolean cancelLoadResult = mLoader.cancelLoad();
349                 if (!cancelLoadResult) {
350                     onLoadCanceled(mLoader);
351                 }
352                 return cancelLoadResult;
353             }
354             return false;
355         }
356 
destroy()357         void destroy() {
358             if (DEBUG) Log.v(TAG, "  Destroying: " + this);
359             mDestroyed = true;
360             boolean needReset = mDeliveredData;
361             mDeliveredData = false;
362             if (mCallbacks != null && mLoader != null && mHaveData && needReset) {
363                 if (DEBUG) Log.v(TAG, "  Reseting: " + this);
364                 String lastBecause = null;
365                 if (mHost != null) {
366                     lastBecause = mHost.mFragmentManager.mNoTransactionsBecause;
367                     mHost.mFragmentManager.mNoTransactionsBecause = "onLoaderReset";
368                 }
369                 try {
370                     mCallbacks.onLoaderReset(mLoader);
371                 } finally {
372                     if (mHost != null) {
373                         mHost.mFragmentManager.mNoTransactionsBecause = lastBecause;
374                     }
375                 }
376             }
377             mCallbacks = null;
378             mData = null;
379             mHaveData = false;
380             if (mLoader != null) {
381                 if (mListenerRegistered) {
382                     mListenerRegistered = false;
383                     mLoader.unregisterListener(this);
384                     mLoader.unregisterOnLoadCanceledListener(this);
385                 }
386                 mLoader.reset();
387             }
388             if (mPendingLoader != null) {
389                 mPendingLoader.destroy();
390             }
391         }
392 
393         @Override
onLoadCanceled(Loader<Object> loader)394         public void onLoadCanceled(Loader<Object> loader) {
395             if (DEBUG) Log.v(TAG, "onLoadCanceled: " + this);
396 
397             if (mDestroyed) {
398                 if (DEBUG) Log.v(TAG, "  Ignoring load canceled -- destroyed");
399                 return;
400             }
401 
402             if (mLoaders.get(mId) != this) {
403                 // This cancellation message is not coming from the current active loader.
404                 // We don't care about it.
405                 if (DEBUG) Log.v(TAG, "  Ignoring load canceled -- not active");
406                 return;
407             }
408 
409             LoaderInfo pending = mPendingLoader;
410             if (pending != null) {
411                 // There is a new request pending and we were just
412                 // waiting for the old one to cancel or complete before starting
413                 // it.  So now it is time, switch over to the new loader.
414                 if (DEBUG) Log.v(TAG, "  Switching to pending loader: " + pending);
415                 mPendingLoader = null;
416                 mLoaders.put(mId, null);
417                 destroy();
418                 installLoader(pending);
419             }
420         }
421 
422         @Override
onLoadComplete(Loader<Object> loader, Object data)423         public void onLoadComplete(Loader<Object> loader, Object data) {
424             if (DEBUG) Log.v(TAG, "onLoadComplete: " + this);
425 
426             if (mDestroyed) {
427                 if (DEBUG) Log.v(TAG, "  Ignoring load complete -- destroyed");
428                 return;
429             }
430 
431             if (mLoaders.get(mId) != this) {
432                 // This data is not coming from the current active loader.
433                 // We don't care about it.
434                 if (DEBUG) Log.v(TAG, "  Ignoring load complete -- not active");
435                 return;
436             }
437 
438             LoaderInfo pending = mPendingLoader;
439             if (pending != null) {
440                 // There is a new request pending and we were just
441                 // waiting for the old one to complete before starting
442                 // it.  So now it is time, switch over to the new loader.
443                 if (DEBUG) Log.v(TAG, "  Switching to pending loader: " + pending);
444                 mPendingLoader = null;
445                 mLoaders.put(mId, null);
446                 destroy();
447                 installLoader(pending);
448                 return;
449             }
450 
451             // Notify of the new data so the app can switch out the old data before
452             // we try to destroy it.
453             if (mData != data || !mHaveData) {
454                 mData = data;
455                 mHaveData = true;
456                 if (mStarted) {
457                     callOnLoadFinished(loader, data);
458                 }
459             }
460 
461             //if (DEBUG) Log.v(TAG, "  onLoadFinished returned: " + this);
462 
463             // We have now given the application the new loader with its
464             // loaded data, so it should have stopped using the previous
465             // loader.  If there is a previous loader on the inactive list,
466             // clean it up.
467             LoaderInfo info = mInactiveLoaders.get(mId);
468             if (info != null && info != this) {
469                 info.mDeliveredData = false;
470                 info.destroy();
471                 mInactiveLoaders.remove(mId);
472             }
473 
474             if (mHost != null && !hasRunningLoaders()) {
475                 mHost.mFragmentManager.startPendingDeferredFragments();
476             }
477         }
478 
callOnLoadFinished(Loader<Object> loader, Object data)479         void callOnLoadFinished(Loader<Object> loader, Object data) {
480             if (mCallbacks != null) {
481                 String lastBecause = null;
482                 if (mHost != null) {
483                     lastBecause = mHost.mFragmentManager.mNoTransactionsBecause;
484                     mHost.mFragmentManager.mNoTransactionsBecause = "onLoadFinished";
485                 }
486                 try {
487                     if (DEBUG) Log.v(TAG, "  onLoadFinished in " + loader + ": "
488                             + loader.dataToString(data));
489                     mCallbacks.onLoadFinished(loader, data);
490                 } finally {
491                     if (mHost != null) {
492                         mHost.mFragmentManager.mNoTransactionsBecause = lastBecause;
493                     }
494                 }
495                 mDeliveredData = true;
496             }
497         }
498 
499         @Override
toString()500         public String toString() {
501             StringBuilder sb = new StringBuilder(64);
502             sb.append("LoaderInfo{");
503             sb.append(Integer.toHexString(System.identityHashCode(this)));
504             sb.append(" #");
505             sb.append(mId);
506             sb.append(" : ");
507             DebugUtils.buildShortClassTag(mLoader, sb);
508             sb.append("}}");
509             return sb.toString();
510         }
511 
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)512         public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
513             writer.print(prefix); writer.print("mId="); writer.print(mId);
514                     writer.print(" mArgs="); writer.println(mArgs);
515             writer.print(prefix); writer.print("mCallbacks="); writer.println(mCallbacks);
516             writer.print(prefix); writer.print("mLoader="); writer.println(mLoader);
517             if (mLoader != null) {
518                 mLoader.dump(prefix + "  ", fd, writer, args);
519             }
520             if (mHaveData || mDeliveredData) {
521                 writer.print(prefix); writer.print("mHaveData="); writer.print(mHaveData);
522                         writer.print("  mDeliveredData="); writer.println(mDeliveredData);
523                 writer.print(prefix); writer.print("mData="); writer.println(mData);
524             }
525             writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
526                     writer.print(" mReportNextStart="); writer.print(mReportNextStart);
527                     writer.print(" mDestroyed="); writer.println(mDestroyed);
528             writer.print(prefix); writer.print("mRetaining="); writer.print(mRetaining);
529                     writer.print(" mRetainingStarted="); writer.print(mRetainingStarted);
530                     writer.print(" mListenerRegistered="); writer.println(mListenerRegistered);
531             if (mPendingLoader != null) {
532                 writer.print(prefix); writer.println("Pending Loader ");
533                         writer.print(mPendingLoader); writer.println(":");
534                 mPendingLoader.dump(prefix + "  ", fd, writer, args);
535             }
536         }
537     }
538 
LoaderManagerImpl(String who, FragmentHostCallback host, boolean started)539     LoaderManagerImpl(String who, FragmentHostCallback host, boolean started) {
540         mWho = who;
541         mHost = host;
542         mStarted = started;
543     }
544 
updateHostController(FragmentHostCallback host)545     void updateHostController(FragmentHostCallback host) {
546         mHost = host;
547     }
548 
getFragmentHostCallback()549     public FragmentHostCallback getFragmentHostCallback() {
550         return mHost;
551     }
552 
createLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callback)553     private LoaderInfo createLoader(int id, Bundle args,
554             LoaderManager.LoaderCallbacks<Object> callback) {
555         LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
556         Loader<Object> loader = callback.onCreateLoader(id, args);
557         info.mLoader = (Loader<Object>)loader;
558         return info;
559     }
560 
createAndInstallLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callback)561     private LoaderInfo createAndInstallLoader(int id, Bundle args,
562             LoaderManager.LoaderCallbacks<Object> callback) {
563         try {
564             mCreatingLoader = true;
565             LoaderInfo info = createLoader(id, args, callback);
566             installLoader(info);
567             return info;
568         } finally {
569             mCreatingLoader = false;
570         }
571     }
572 
installLoader(LoaderInfo info)573     void installLoader(LoaderInfo info) {
574         mLoaders.put(info.mId, info);
575         if (mStarted) {
576             // The activity will start all existing loaders in it's onStart(),
577             // so only start them here if we're past that point of the activitiy's
578             // life cycle
579             info.start();
580         }
581     }
582 
583     /**
584      * Call to initialize a particular ID with a Loader.  If this ID already
585      * has a Loader associated with it, it is left unchanged and any previous
586      * callbacks replaced with the newly provided ones.  If there is not currently
587      * a Loader for the ID, a new one is created and started.
588      *
589      * <p>This function should generally be used when a component is initializing,
590      * to ensure that a Loader it relies on is created.  This allows it to re-use
591      * an existing Loader's data if there already is one, so that for example
592      * when an {@link Activity} is re-created after a configuration change it
593      * does not need to re-create its loaders.
594      *
595      * <p>Note that in the case where an existing Loader is re-used, the
596      * <var>args</var> given here <em>will be ignored</em> because you will
597      * continue using the previous Loader.
598      *
599      * @param id A unique (to this LoaderManager instance) identifier under
600      * which to manage the new Loader.
601      * @param args Optional arguments that will be propagated to
602      * {@link LoaderCallbacks#onCreateLoader(int, Bundle) LoaderCallbacks.onCreateLoader()}.
603      * @param callback Interface implementing management of this Loader.  Required.
604      * Its onCreateLoader() method will be called while inside of the function to
605      * instantiate the Loader object.
606      */
607     @SuppressWarnings("unchecked")
initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback)608     public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
609         if (mCreatingLoader) {
610             throw new IllegalStateException("Called while creating a loader");
611         }
612 
613         LoaderInfo info = mLoaders.get(id);
614 
615         if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
616 
617         if (info == null) {
618             // Loader doesn't already exist; create.
619             info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
620             if (DEBUG) Log.v(TAG, "  Created new loader " + info);
621         } else {
622             if (DEBUG) Log.v(TAG, "  Re-using existing loader " + info);
623             info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
624         }
625 
626         if (info.mHaveData && mStarted) {
627             // If the loader has already generated its data, report it now.
628             info.callOnLoadFinished(info.mLoader, info.mData);
629         }
630 
631         return (Loader<D>)info.mLoader;
632     }
633 
634     /**
635      * Call to re-create the Loader associated with a particular ID.  If there
636      * is currently a Loader associated with this ID, it will be
637      * canceled/stopped/destroyed as appropriate.  A new Loader with the given
638      * arguments will be created and its data delivered to you once available.
639      *
640      * <p>This function does some throttling of Loaders.  If too many Loaders
641      * have been created for the given ID but not yet generated their data,
642      * new calls to this function will create and return a new Loader but not
643      * actually start it until some previous loaders have completed.
644      *
645      * <p>After calling this function, any previous Loaders associated with
646      * this ID will be considered invalid, and you will receive no further
647      * data updates from them.
648      *
649      * @param id A unique (to this LoaderManager instance) identifier under
650      * which to manage the new Loader.
651      * @param args Optional arguments that will be propagated to
652      * {@link LoaderCallbacks#onCreateLoader(int, Bundle) LoaderCallbacks.onCreateLoader()}.
653      * @param callback Interface implementing management of this Loader.  Required.
654      * Its onCreateLoader() method will be called while inside of the function to
655      * instantiate the Loader object.
656      */
657     @SuppressWarnings("unchecked")
restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback)658     public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
659         if (mCreatingLoader) {
660             throw new IllegalStateException("Called while creating a loader");
661         }
662 
663         LoaderInfo info = mLoaders.get(id);
664         if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": args=" + args);
665         if (info != null) {
666             LoaderInfo inactive = mInactiveLoaders.get(id);
667             if (inactive != null) {
668                 if (info.mHaveData) {
669                     // This loader now has data...  we are probably being
670                     // called from within onLoadComplete, where we haven't
671                     // yet destroyed the last inactive loader.  So just do
672                     // that now.
673                     if (DEBUG) Log.v(TAG, "  Removing last inactive loader: " + info);
674                     inactive.mDeliveredData = false;
675                     inactive.destroy();
676                     info.mLoader.abandon();
677                     mInactiveLoaders.put(id, info);
678                 } else {
679                     // We already have an inactive loader for this ID that we are
680                     // waiting for! Try to cancel; if this returns true then the task is still
681                     // running and we have more work to do.
682                     if (!info.cancel()) {
683                         // The current Loader has not been started or was successfully canceled,
684                         // we thus have no reason to keep it around. Remove it and a new
685                         // LoaderInfo will be created below.
686                         if (DEBUG) Log.v(TAG, "  Current loader is stopped; replacing");
687                         mLoaders.put(id, null);
688                         info.destroy();
689                     } else {
690                         // Now we have three active loaders... we'll queue
691                         // up this request to be processed once one of the other loaders
692                         // finishes.
693                         if (DEBUG) Log.v(TAG,
694                                 "  Current loader is running; configuring pending loader");
695                         if (info.mPendingLoader != null) {
696                             if (DEBUG) Log.v(TAG, "  Removing pending loader: " + info.mPendingLoader);
697                             info.mPendingLoader.destroy();
698                             info.mPendingLoader = null;
699                         }
700                         if (DEBUG) Log.v(TAG, "  Enqueuing as new pending loader");
701                         info.mPendingLoader = createLoader(id, args,
702                                 (LoaderManager.LoaderCallbacks<Object>)callback);
703                         return (Loader<D>)info.mPendingLoader.mLoader;
704                     }
705                 }
706             } else {
707                 // Keep track of the previous instance of this loader so we can destroy
708                 // it when the new one completes.
709                 if (DEBUG) Log.v(TAG, "  Making last loader inactive: " + info);
710                 info.mLoader.abandon();
711                 mInactiveLoaders.put(id, info);
712             }
713         }
714 
715         info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
716         return (Loader<D>)info.mLoader;
717     }
718 
719     /**
720      * Rip down, tear apart, shred to pieces a current Loader ID.  After returning
721      * from this function, any Loader objects associated with this ID are
722      * destroyed.  Any data associated with them is destroyed.  You better not
723      * be using it when you do this.
724      * @param id Identifier of the Loader to be destroyed.
725      */
destroyLoader(int id)726     public void destroyLoader(int id) {
727         if (mCreatingLoader) {
728             throw new IllegalStateException("Called while creating a loader");
729         }
730 
731         if (DEBUG) Log.v(TAG, "destroyLoader in " + this + " of " + id);
732         int idx = mLoaders.indexOfKey(id);
733         if (idx >= 0) {
734             LoaderInfo info = mLoaders.valueAt(idx);
735             mLoaders.removeAt(idx);
736             info.destroy();
737         }
738         idx = mInactiveLoaders.indexOfKey(id);
739         if (idx >= 0) {
740             LoaderInfo info = mInactiveLoaders.valueAt(idx);
741             mInactiveLoaders.removeAt(idx);
742             info.destroy();
743         }
744         if (mHost != null && !hasRunningLoaders()) {
745             mHost.mFragmentManager.startPendingDeferredFragments();
746         }
747     }
748 
749     /**
750      * Return the most recent Loader object associated with the
751      * given ID.
752      */
753     @SuppressWarnings("unchecked")
getLoader(int id)754     public <D> Loader<D> getLoader(int id) {
755         if (mCreatingLoader) {
756             throw new IllegalStateException("Called while creating a loader");
757         }
758 
759         LoaderInfo loaderInfo = mLoaders.get(id);
760         if (loaderInfo != null) {
761             if (loaderInfo.mPendingLoader != null) {
762                 return (Loader<D>)loaderInfo.mPendingLoader.mLoader;
763             }
764             return (Loader<D>)loaderInfo.mLoader;
765         }
766         return null;
767     }
768 
doStart()769     void doStart() {
770         if (DEBUG) Log.v(TAG, "Starting in " + this);
771         if (mStarted) {
772             RuntimeException e = new RuntimeException("here");
773             e.fillInStackTrace();
774             Log.w(TAG, "Called doStart when already started: " + this, e);
775             return;
776         }
777 
778         mStarted = true;
779 
780         // Call out to sub classes so they can start their loaders
781         // Let the existing loaders know that we want to be notified when a load is complete
782         for (int i = mLoaders.size()-1; i >= 0; i--) {
783             mLoaders.valueAt(i).start();
784         }
785     }
786 
doStop()787     void doStop() {
788         if (DEBUG) Log.v(TAG, "Stopping in " + this);
789         if (!mStarted) {
790             RuntimeException e = new RuntimeException("here");
791             e.fillInStackTrace();
792             Log.w(TAG, "Called doStop when not started: " + this, e);
793             return;
794         }
795 
796         for (int i = mLoaders.size()-1; i >= 0; i--) {
797             mLoaders.valueAt(i).stop();
798         }
799         mStarted = false;
800     }
801 
doRetain()802     void doRetain() {
803         if (DEBUG) Log.v(TAG, "Retaining in " + this);
804         if (!mStarted) {
805             RuntimeException e = new RuntimeException("here");
806             e.fillInStackTrace();
807             Log.w(TAG, "Called doRetain when not started: " + this, e);
808             return;
809         }
810 
811         mRetaining = true;
812         mStarted = false;
813         for (int i = mLoaders.size()-1; i >= 0; i--) {
814             mLoaders.valueAt(i).retain();
815         }
816     }
817 
finishRetain()818     void finishRetain() {
819         if (mRetaining) {
820             if (DEBUG) Log.v(TAG, "Finished Retaining in " + this);
821 
822             mRetaining = false;
823             for (int i = mLoaders.size()-1; i >= 0; i--) {
824                 mLoaders.valueAt(i).finishRetain();
825             }
826         }
827     }
828 
doReportNextStart()829     void doReportNextStart() {
830         for (int i = mLoaders.size()-1; i >= 0; i--) {
831             mLoaders.valueAt(i).mReportNextStart = true;
832         }
833     }
834 
doReportStart()835     void doReportStart() {
836         for (int i = mLoaders.size()-1; i >= 0; i--) {
837             mLoaders.valueAt(i).reportStart();
838         }
839     }
840 
doDestroy()841     void doDestroy() {
842         if (!mRetaining) {
843             if (DEBUG) Log.v(TAG, "Destroying Active in " + this);
844             for (int i = mLoaders.size()-1; i >= 0; i--) {
845                 mLoaders.valueAt(i).destroy();
846             }
847             mLoaders.clear();
848         }
849 
850         if (DEBUG) Log.v(TAG, "Destroying Inactive in " + this);
851         for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
852             mInactiveLoaders.valueAt(i).destroy();
853         }
854         mInactiveLoaders.clear();
855         mHost = null;
856     }
857 
858     @Override
toString()859     public String toString() {
860         StringBuilder sb = new StringBuilder(128);
861         sb.append("LoaderManager{");
862         sb.append(Integer.toHexString(System.identityHashCode(this)));
863         sb.append(" in ");
864         DebugUtils.buildShortClassTag(mHost, sb);
865         sb.append("}}");
866         return sb.toString();
867     }
868 
869     @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)870     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
871         if (mLoaders.size() > 0) {
872             writer.print(prefix); writer.println("Active Loaders:");
873             String innerPrefix = prefix + "    ";
874             for (int i=0; i < mLoaders.size(); i++) {
875                 LoaderInfo li = mLoaders.valueAt(i);
876                 writer.print(prefix); writer.print("  #"); writer.print(mLoaders.keyAt(i));
877                         writer.print(": "); writer.println(li.toString());
878                 li.dump(innerPrefix, fd, writer, args);
879             }
880         }
881         if (mInactiveLoaders.size() > 0) {
882             writer.print(prefix); writer.println("Inactive Loaders:");
883             String innerPrefix = prefix + "    ";
884             for (int i=0; i < mInactiveLoaders.size(); i++) {
885                 LoaderInfo li = mInactiveLoaders.valueAt(i);
886                 writer.print(prefix); writer.print("  #"); writer.print(mInactiveLoaders.keyAt(i));
887                         writer.print(": "); writer.println(li.toString());
888                 li.dump(innerPrefix, fd, writer, args);
889             }
890         }
891     }
892 
hasRunningLoaders()893     public boolean hasRunningLoaders() {
894         boolean loadersRunning = false;
895         final int count = mLoaders.size();
896         for (int i = 0; i < count; i++) {
897             final LoaderInfo li = mLoaders.valueAt(i);
898             loadersRunning |= li.mStarted && !li.mDeliveredData;
899         }
900         return loadersRunning;
901     }
902 }
903