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