LoaderManager.java revision 9c53b844bd525e6a04e17291efc38713893074cd
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 mReportNextStart; 214 boolean mDestroyed; 215 boolean mListenerRegistered; 216 217 LoaderInfo mPendingLoader; 218 219 public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) { 220 mId = id; 221 mArgs = args; 222 mCallbacks = callbacks; 223 } 224 225 void start() { 226 if (mRetaining && mRetainingStarted) { 227 // Our owner is started, but we were being retained from a 228 // previous instance in the started state... so there is really 229 // nothing to do here, since the loaders are still started. 230 mStarted = true; 231 return; 232 } 233 234 if (mStarted) { 235 // If loader already started, don't restart. 236 return; 237 } 238 239 mStarted = true; 240 241 if (DEBUG) Log.v(TAG, " Starting: " + this); 242 if (mLoader == null && mCallbacks != null) { 243 mLoader = mCallbacks.onCreateLoader(mId, mArgs); 244 } 245 if (mLoader != null) { 246 if (mLoader.getClass().isMemberClass() 247 && !Modifier.isStatic(mLoader.getClass().getModifiers())) { 248 throw new IllegalArgumentException( 249 "Object returned from onCreateLoader must not be a non-static inner member class: " 250 + mLoader); 251 } 252 if (!mListenerRegistered) { 253 mLoader.registerListener(mId, this); 254 mListenerRegistered = true; 255 } 256 mLoader.startLoading(); 257 } 258 } 259 260 void retain() { 261 if (DEBUG) Log.v(TAG, " Retaining: " + this); 262 mRetaining = true; 263 mRetainingStarted = mStarted; 264 mStarted = false; 265 mCallbacks = null; 266 } 267 268 void finishRetain() { 269 if (mRetaining) { 270 if (DEBUG) Log.v(TAG, " Finished Retaining: " + this); 271 mRetaining = false; 272 if (mStarted != mRetainingStarted) { 273 if (!mStarted) { 274 // This loader was retained in a started state, but 275 // at the end of retaining everything our owner is 276 // no longer started... so make it stop. 277 stop(); 278 } 279 } 280 } 281 282 if (mStarted && mHaveData && !mReportNextStart) { 283 // This loader has retained its data, either completely across 284 // a configuration change or just whatever the last data set 285 // was after being restarted from a stop, and now at the point of 286 // finishing the retain we find we remain started, have 287 // our data, and the owner has a new callback... so 288 // let's deliver the data now. 289 callOnLoadFinished(mLoader, mData); 290 } 291 } 292 293 void reportStart() { 294 if (mStarted) { 295 if (mReportNextStart) { 296 mReportNextStart = false; 297 if (mHaveData) { 298 callOnLoadFinished(mLoader, mData); 299 } 300 } 301 } 302 } 303 304 void stop() { 305 if (DEBUG) Log.v(TAG, " Stopping: " + this); 306 mStarted = false; 307 if (!mRetaining) { 308 if (mLoader != null && mListenerRegistered) { 309 // Let the loader know we're done with it 310 mListenerRegistered = false; 311 mLoader.unregisterListener(this); 312 mLoader.stopLoading(); 313 } 314 } 315 } 316 317 void destroy() { 318 if (DEBUG) Log.v(TAG, " Destroying: " + this); 319 mDestroyed = true; 320 boolean needReset = mDeliveredData; 321 mDeliveredData = false; 322 if (mCallbacks != null && mLoader != null && mHaveData && needReset) { 323 if (DEBUG) Log.v(TAG, " Reseting: " + this); 324 String lastBecause = null; 325 if (mActivity != null) { 326 lastBecause = mActivity.mFragments.mNoTransactionsBecause; 327 mActivity.mFragments.mNoTransactionsBecause = "onLoaderReset"; 328 } 329 try { 330 mCallbacks.onLoaderReset(mLoader); 331 } finally { 332 if (mActivity != null) { 333 mActivity.mFragments.mNoTransactionsBecause = lastBecause; 334 } 335 } 336 } 337 mCallbacks = null; 338 mData = null; 339 mHaveData = false; 340 if (mLoader != null) { 341 if (mListenerRegistered) { 342 mListenerRegistered = false; 343 mLoader.unregisterListener(this); 344 } 345 mLoader.reset(); 346 } 347 if (mPendingLoader != null) { 348 mPendingLoader.destroy(); 349 } 350 } 351 352 @Override public void onLoadComplete(Loader<Object> loader, Object data) { 353 if (DEBUG) Log.v(TAG, "onLoadComplete: " + this); 354 355 if (mDestroyed) { 356 if (DEBUG) Log.v(TAG, " Ignoring load complete -- destroyed"); 357 return; 358 } 359 360 if (mLoaders.get(mId) != this) { 361 // This data is not coming from the current active loader. 362 // We don't care about it. 363 if (DEBUG) Log.v(TAG, " Ignoring load complete -- not active"); 364 return; 365 } 366 367 LoaderInfo pending = mPendingLoader; 368 if (pending != null) { 369 // There is a new request pending and we were just 370 // waiting for the old one to complete before starting 371 // it. So now it is time, switch over to the new loader. 372 if (DEBUG) Log.v(TAG, " Switching to pending loader: " + pending); 373 mPendingLoader = null; 374 mLoaders.put(mId, null); 375 destroy(); 376 installLoader(pending); 377 return; 378 } 379 380 // Notify of the new data so the app can switch out the old data before 381 // we try to destroy it. 382 if (mData != data || !mHaveData) { 383 mData = data; 384 mHaveData = true; 385 if (mStarted) { 386 callOnLoadFinished(loader, data); 387 } 388 } 389 390 //if (DEBUG) Log.v(TAG, " onLoadFinished returned: " + this); 391 392 // We have now given the application the new loader with its 393 // loaded data, so it should have stopped using the previous 394 // loader. If there is a previous loader on the inactive list, 395 // clean it up. 396 LoaderInfo info = mInactiveLoaders.get(mId); 397 if (info != null && info != this) { 398 info.mDeliveredData = false; 399 info.destroy(); 400 mInactiveLoaders.remove(mId); 401 } 402 } 403 404 void callOnLoadFinished(Loader<Object> loader, Object data) { 405 if (mCallbacks != null) { 406 String lastBecause = null; 407 if (mActivity != null) { 408 lastBecause = mActivity.mFragments.mNoTransactionsBecause; 409 mActivity.mFragments.mNoTransactionsBecause = "onLoadFinished"; 410 } 411 try { 412 if (DEBUG) Log.v(TAG, " onLoadFinished in " + loader + ": " 413 + loader.dataToString(data)); 414 mCallbacks.onLoadFinished(loader, data); 415 } finally { 416 if (mActivity != null) { 417 mActivity.mFragments.mNoTransactionsBecause = lastBecause; 418 } 419 } 420 mDeliveredData = true; 421 } 422 } 423 424 @Override 425 public String toString() { 426 StringBuilder sb = new StringBuilder(64); 427 sb.append("LoaderInfo{"); 428 sb.append(Integer.toHexString(System.identityHashCode(this))); 429 sb.append(" #"); 430 sb.append(mId); 431 sb.append(" : "); 432 DebugUtils.buildShortClassTag(mLoader, sb); 433 sb.append("}}"); 434 return sb.toString(); 435 } 436 437 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 438 writer.print(prefix); writer.print("mId="); writer.print(mId); 439 writer.print(" mArgs="); writer.println(mArgs); 440 writer.print(prefix); writer.print("mCallbacks="); writer.println(mCallbacks); 441 writer.print(prefix); writer.print("mLoader="); writer.println(mLoader); 442 if (mLoader != null) { 443 mLoader.dump(prefix + " ", fd, writer, args); 444 } 445 if (mHaveData || mDeliveredData) { 446 writer.print(prefix); writer.print("mHaveData="); writer.print(mHaveData); 447 writer.print(" mDeliveredData="); writer.println(mDeliveredData); 448 writer.print(prefix); writer.print("mData="); writer.println(mData); 449 } 450 writer.print(prefix); writer.print("mStarted="); writer.print(mStarted); 451 writer.print(" mReportNextStart="); writer.print(mReportNextStart); 452 writer.print(" mDestroyed="); writer.println(mDestroyed); 453 writer.print(prefix); writer.print("mRetaining="); writer.print(mRetaining); 454 writer.print(" mRetainingStarted="); writer.print(mRetainingStarted); 455 writer.print(" mListenerRegistered="); writer.println(mListenerRegistered); 456 if (mPendingLoader != null) { 457 writer.print(prefix); writer.println("Pending Loader "); 458 writer.print(mPendingLoader); writer.println(":"); 459 mPendingLoader.dump(prefix + " ", fd, writer, args); 460 } 461 } 462 } 463 464 LoaderManagerImpl(FragmentActivity activity, boolean started) { 465 mActivity = activity; 466 mStarted = started; 467 } 468 469 void updateActivity(FragmentActivity activity) { 470 mActivity = activity; 471 } 472 473 private LoaderInfo createLoader(int id, Bundle args, 474 LoaderManager.LoaderCallbacks<Object> callback) { 475 LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 476 Loader<Object> loader = callback.onCreateLoader(id, args); 477 info.mLoader = (Loader<Object>)loader; 478 return info; 479 } 480 481 private LoaderInfo createAndInstallLoader(int id, Bundle args, 482 LoaderManager.LoaderCallbacks<Object> callback) { 483 try { 484 mCreatingLoader = true; 485 LoaderInfo info = createLoader(id, args, callback); 486 installLoader(info); 487 return info; 488 } finally { 489 mCreatingLoader = false; 490 } 491 } 492 493 void installLoader(LoaderInfo info) { 494 mLoaders.put(info.mId, info); 495 if (mStarted) { 496 // The activity will start all existing loaders in it's onStart(), 497 // so only start them here if we're past that point of the activitiy's 498 // life cycle 499 info.start(); 500 } 501 } 502 503 /** 504 * Call to initialize a particular ID with a Loader. If this ID already 505 * has a Loader associated with it, it is left unchanged and any previous 506 * callbacks replaced with the newly provided ones. If there is not currently 507 * a Loader for the ID, a new one is created and started. 508 * 509 * <p>This function should generally be used when a component is initializing, 510 * to ensure that a Loader it relies on is created. This allows it to re-use 511 * an existing Loader's data if there already is one, so that for example 512 * when an {@link Activity} is re-created after a configuration change it 513 * does not need to re-create its loaders. 514 * 515 * <p>Note that in the case where an existing Loader is re-used, the 516 * <var>args</var> given here <em>will be ignored</em> because you will 517 * continue using the previous Loader. 518 * 519 * @param id A unique (to this LoaderManager instance) identifier under 520 * which to manage the new Loader. 521 * @param args Optional arguments that will be propagated to 522 * {@link LoaderCallbacks#onCreateLoader(int, Bundle) LoaderCallbacks.onCreateLoader()}. 523 * @param callback Interface implementing management of this Loader. Required. 524 * Its onCreateLoader() method will be called while inside of the function to 525 * instantiate the Loader object. 526 */ 527 @SuppressWarnings("unchecked") 528 public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { 529 if (mCreatingLoader) { 530 throw new IllegalStateException("Called while creating a loader"); 531 } 532 533 LoaderInfo info = mLoaders.get(id); 534 535 if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args); 536 537 if (info == null) { 538 // Loader doesn't already exist; create. 539 info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 540 if (DEBUG) Log.v(TAG, " Created new loader " + info); 541 } else { 542 if (DEBUG) Log.v(TAG, " Re-using existing loader " + info); 543 info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback; 544 } 545 546 if (info.mHaveData && mStarted) { 547 // If the loader has already generated its data, report it now. 548 info.callOnLoadFinished(info.mLoader, info.mData); 549 } 550 551 return (Loader<D>)info.mLoader; 552 } 553 554 /** 555 * Call to re-create the Loader associated with a particular ID. If there 556 * is currently a Loader associated with this ID, it will be 557 * canceled/stopped/destroyed as appropriate. A new Loader with the given 558 * arguments will be created and its data delivered to you once available. 559 * 560 * <p>This function does some throttling of Loaders. If too many Loaders 561 * have been created for the given ID but not yet generated their data, 562 * new calls to this function will create and return a new Loader but not 563 * actually start it until some previous loaders have completed. 564 * 565 * <p>After calling this function, any previous Loaders associated with 566 * this ID will be considered invalid, and you will receive no further 567 * data updates from them. 568 * 569 * @param id A unique (to this LoaderManager instance) identifier under 570 * which to manage the new Loader. 571 * @param args Optional arguments that will be propagated to 572 * {@link LoaderCallbacks#onCreateLoader(int, Bundle) LoaderCallbacks.onCreateLoader()}. 573 * @param callback Interface implementing management of this Loader. Required. 574 * Its onCreateLoader() method will be called while inside of the function to 575 * instantiate the Loader object. 576 */ 577 @SuppressWarnings("unchecked") 578 public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { 579 if (mCreatingLoader) { 580 throw new IllegalStateException("Called while creating a loader"); 581 } 582 583 LoaderInfo info = mLoaders.get(id); 584 if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": args=" + args); 585 if (info != null) { 586 LoaderInfo inactive = mInactiveLoaders.get(id); 587 if (inactive != null) { 588 if (info.mHaveData) { 589 // This loader now has data... we are probably being 590 // called from within onLoadComplete, where we haven't 591 // yet destroyed the last inactive loader. So just do 592 // that now. 593 if (DEBUG) Log.v(TAG, " Removing last inactive loader: " + info); 594 inactive.mDeliveredData = false; 595 inactive.destroy(); 596 info.mLoader.abandon(); 597 mInactiveLoaders.put(id, info); 598 } else { 599 // We already have an inactive loader for this ID that we are 600 // waiting for! What to do, what to do... 601 if (!info.mStarted) { 602 // The current Loader has not been started... we thus 603 // have no reason to keep it around, so bam, slam, 604 // thank-you-ma'am. 605 if (DEBUG) Log.v(TAG, " Current loader is stopped; replacing"); 606 mLoaders.put(id, null); 607 info.destroy(); 608 } else { 609 // Now we have three active loaders... we'll queue 610 // up this request to be processed once one of the other loaders 611 // finishes. 612 if (info.mPendingLoader != null) { 613 if (DEBUG) Log.v(TAG, " Removing pending loader: " + info.mPendingLoader); 614 info.mPendingLoader.destroy(); 615 info.mPendingLoader = null; 616 } 617 if (DEBUG) Log.v(TAG, " Enqueuing as new pending loader"); 618 info.mPendingLoader = createLoader(id, args, 619 (LoaderManager.LoaderCallbacks<Object>)callback); 620 return (Loader<D>)info.mPendingLoader.mLoader; 621 } 622 } 623 } else { 624 // Keep track of the previous instance of this loader so we can destroy 625 // it when the new one completes. 626 if (DEBUG) Log.v(TAG, " Making last loader inactive: " + info); 627 info.mLoader.abandon(); 628 mInactiveLoaders.put(id, info); 629 } 630 } 631 632 info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 633 return (Loader<D>)info.mLoader; 634 } 635 636 /** 637 * Rip down, tear apart, shred to pieces a current Loader ID. After returning 638 * from this function, any Loader objects associated with this ID are 639 * destroyed. Any data associated with them is destroyed. You better not 640 * be using it when you do this. 641 * @param id Identifier of the Loader to be destroyed. 642 */ 643 public void destroyLoader(int id) { 644 if (mCreatingLoader) { 645 throw new IllegalStateException("Called while creating a loader"); 646 } 647 648 if (DEBUG) Log.v(TAG, "destroyLoader in " + this + " of " + id); 649 int idx = mLoaders.indexOfKey(id); 650 if (idx >= 0) { 651 LoaderInfo info = mLoaders.valueAt(idx); 652 mLoaders.removeAt(idx); 653 info.destroy(); 654 } 655 idx = mInactiveLoaders.indexOfKey(id); 656 if (idx >= 0) { 657 LoaderInfo info = mInactiveLoaders.valueAt(idx); 658 mInactiveLoaders.removeAt(idx); 659 info.destroy(); 660 } 661 } 662 663 /** 664 * Return the most recent Loader object associated with the 665 * given ID. 666 */ 667 @SuppressWarnings("unchecked") 668 public <D> Loader<D> getLoader(int id) { 669 if (mCreatingLoader) { 670 throw new IllegalStateException("Called while creating a loader"); 671 } 672 673 LoaderInfo loaderInfo = mLoaders.get(id); 674 if (loaderInfo != null) { 675 if (loaderInfo.mPendingLoader != null) { 676 return (Loader<D>)loaderInfo.mPendingLoader.mLoader; 677 } 678 return (Loader<D>)loaderInfo.mLoader; 679 } 680 return null; 681 } 682 683 void doStart() { 684 if (DEBUG) Log.v(TAG, "Starting in " + this); 685 if (mStarted) { 686 RuntimeException e = new RuntimeException("here"); 687 e.fillInStackTrace(); 688 Log.w(TAG, "Called doStart when already started: " + this, e); 689 return; 690 } 691 692 mStarted = true; 693 694 // Call out to sub classes so they can start their loaders 695 // Let the existing loaders know that we want to be notified when a load is complete 696 for (int i = mLoaders.size()-1; i >= 0; i--) { 697 mLoaders.valueAt(i).start(); 698 } 699 } 700 701 void doStop() { 702 if (DEBUG) Log.v(TAG, "Stopping in " + this); 703 if (!mStarted) { 704 RuntimeException e = new RuntimeException("here"); 705 e.fillInStackTrace(); 706 Log.w(TAG, "Called doStop when not started: " + this, e); 707 return; 708 } 709 710 for (int i = mLoaders.size()-1; i >= 0; i--) { 711 mLoaders.valueAt(i).stop(); 712 } 713 mStarted = false; 714 } 715 716 void doRetain() { 717 if (DEBUG) Log.v(TAG, "Retaining in " + this); 718 if (!mStarted) { 719 RuntimeException e = new RuntimeException("here"); 720 e.fillInStackTrace(); 721 Log.w(TAG, "Called doRetain when not started: " + this, e); 722 return; 723 } 724 725 mRetaining = true; 726 mStarted = false; 727 for (int i = mLoaders.size()-1; i >= 0; i--) { 728 mLoaders.valueAt(i).retain(); 729 } 730 } 731 732 void finishRetain() { 733 if (mRetaining) { 734 if (DEBUG) Log.v(TAG, "Finished Retaining in " + this); 735 736 mRetaining = false; 737 for (int i = mLoaders.size()-1; i >= 0; i--) { 738 mLoaders.valueAt(i).finishRetain(); 739 } 740 } 741 } 742 743 void doReportNextStart() { 744 for (int i = mLoaders.size()-1; i >= 0; i--) { 745 mLoaders.valueAt(i).mReportNextStart = true; 746 } 747 } 748 749 void doReportStart() { 750 for (int i = mLoaders.size()-1; i >= 0; i--) { 751 mLoaders.valueAt(i).reportStart(); 752 } 753 } 754 755 void doDestroy() { 756 if (!mRetaining) { 757 if (DEBUG) Log.v(TAG, "Destroying Active in " + this); 758 for (int i = mLoaders.size()-1; i >= 0; i--) { 759 mLoaders.valueAt(i).destroy(); 760 } 761 } 762 763 if (DEBUG) Log.v(TAG, "Destroying Inactive in " + this); 764 for (int i = mInactiveLoaders.size()-1; i >= 0; i--) { 765 mInactiveLoaders.valueAt(i).destroy(); 766 } 767 mInactiveLoaders.clear(); 768 } 769 770 @Override 771 public String toString() { 772 StringBuilder sb = new StringBuilder(128); 773 sb.append("LoaderManager{"); 774 sb.append(Integer.toHexString(System.identityHashCode(this))); 775 sb.append(" in "); 776 DebugUtils.buildShortClassTag(mActivity, sb); 777 sb.append("}}"); 778 return sb.toString(); 779 } 780 781 @Override 782 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 783 if (mLoaders.size() > 0) { 784 writer.print(prefix); writer.println("Active Loaders:"); 785 String innerPrefix = prefix + " "; 786 for (int i=0; i < mLoaders.size(); i++) { 787 LoaderInfo li = mLoaders.valueAt(i); 788 writer.print(prefix); writer.print(" #"); writer.print(mLoaders.keyAt(i)); 789 writer.print(": "); writer.println(li.toString()); 790 li.dump(innerPrefix, fd, writer, args); 791 } 792 } 793 if (mInactiveLoaders.size() > 0) { 794 writer.print(prefix); writer.println("Inactive Loaders:"); 795 String innerPrefix = prefix + " "; 796 for (int i=0; i < mInactiveLoaders.size(); i++) { 797 LoaderInfo li = mInactiveLoaders.valueAt(i); 798 writer.print(prefix); writer.print(" #"); writer.print(mInactiveLoaders.keyAt(i)); 799 writer.print(": "); writer.println(li.toString()); 800 li.dump(innerPrefix, fd, writer, args); 801 } 802 } 803 } 804} 805