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