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