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