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