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