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