LoaderManager.java revision c91893511dc1b9e634648406c9ae61b15476e65d
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.Log; 22import android.util.SparseArray; 23 24import java.io.FileDescriptor; 25import java.io.PrintWriter; 26 27/** 28 * Interface associated with an {@link Activity} or {@link Fragment} for managing 29 * one or more {@link android.content.Loader} instances associated with it. This 30 * helps an application manage longer-running operations in conjunction with the 31 * Activity or Fragment lifecycle; the most common use of this is with a 32 * {@link android.content.CursorLoader}, however applications are free to write 33 * their own loaders for loading other types of data. 34 * 35 * <p>As an example, here is the full implementation of a {@link Fragment} 36 * that displays a {@link android.widget.ListView} containing the results of 37 * a query against the contacts content provider. It uses a 38 * {@link android.content.CursorLoader} to manage the query on the provider. 39 * 40 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentListCursorLoader.java 41 * fragment_cursor} 42 */ 43public abstract class LoaderManager { 44 /** 45 * Callback interface for a client to interact with the manager. 46 */ 47 public interface LoaderCallbacks<D> { 48 /** 49 * Instantiate and return a new Loader for the given ID. 50 * 51 * @param id The ID whose loader is to be created. 52 * @param args Any arguments supplied by the caller. 53 * @return Return a new Loader instance that is ready to start loading. 54 */ 55 public Loader<D> onCreateLoader(int id, Bundle args); 56 57 /** 58 * Called when a previously created loader has finished its load. Note 59 * that normally an application is <em>not</em> allowed to commit fragment 60 * transactions while in this call, since it can happen after an 61 * activity's state is saved. See {@link FragmentManager#openTransaction() 62 * FragmentManager.openTransaction()} for further discussion on this. 63 * 64 * <p>This function is guaranteed to be called prior to the release of 65 * the last data that was supplied for this Loader. At this point 66 * you should remove all use of the old data (since it will be released 67 * soon), but should not do your own release of the data since its Loader 68 * owns it and will take care of that. The Loader will take care of 69 * management of its data so you don't have to. In particular: 70 * 71 * <ul> 72 * <li> <p>The Loader will monitor for changes to the data, and report 73 * them to you through new calls here. You should not monitor the 74 * data yourself. For example, if the data is a {@link android.database.Cursor} 75 * and you place it in a {@link android.widget.CursorAdapter}, use 76 * the {@link android.widget.CursorAdapter#CursorAdapter(android.content.Context, 77 * android.database.Cursor, int)} constructor <em>without</em> passing 78 * in either {@link android.widget.CursorAdapter#FLAG_AUTO_REQUERY} 79 * or {@link android.widget.CursorAdapter#FLAG_REGISTER_CONTENT_OBSERVER} 80 * (that is, use 0 for the flags argument). This prevents the CursorAdapter 81 * from doing its own observing of the Cursor, which is not needed since 82 * when a change happens you will get a new Cursor throw another call 83 * here. 84 * <li> The Loader will release the data once it knows the application 85 * is no longer using it. For example, if the data is 86 * a {@link android.database.Cursor} from a {@link android.content.CursorLoader}, 87 * you should not call close() on it yourself. If the Cursor is being placed in a 88 * {@link android.widget.CursorAdapter}, you should use the 89 * {@link android.widget.CursorAdapter#swapCursor(android.database.Cursor)} 90 * method so that the old Cursor is not closed. 91 * </ul> 92 * 93 * @param loader The Loader that has finished. 94 * @param data The data generated by the Loader. 95 */ 96 public void onLoadFinished(Loader<D> loader, D data); 97 98 /** 99 * Called when a previously created loader is being reset, and thus 100 * making its data unavailable. The application should at this point 101 * remove any references it has to the Loader's data. 102 * 103 * @param loader The Loader that is being reset. 104 */ 105 public void onLoaderReset(Loader<D> loader); 106 } 107 108 /** 109 * Ensures a loader is initialized and active. If the loader doesn't 110 * already exist, one is created and (if the activity/fragment is currently 111 * started) starts the loader. Otherwise the last created 112 * loader is re-used. 113 * 114 * <p>In either case, the given callback is associated with the loader, and 115 * will be called as the loader state changes. If at the point of call 116 * the caller is in its started state, and the requested loader 117 * already exists and has generated its data, then 118 * callback {@link LoaderCallbacks#onLoadFinished} will 119 * be called immediately (inside of this function), so you must be prepared 120 * for this to happen. 121 * 122 * @param id A unique identifier for this loader. Can be whatever you want. 123 * Identifiers are scoped to a particular LoaderManager instance. 124 * @param args Optional arguments to supply to the loader at construction. 125 * @param callback Interface the LoaderManager will call to report about 126 * changes in the state of the loader. Required. 127 */ 128 public abstract <D> Loader<D> initLoader(int id, Bundle args, 129 LoaderManager.LoaderCallbacks<D> callback); 130 131 /** 132 * Starts a new or restarts an existing {@link android.content.Loader} in 133 * this manager, registers the callbacks to it, 134 * and (if the activity/fragment is currently started) starts loading it. 135 * If a loader with the same id has previously been 136 * started it will automatically be destroyed when the new loader completes 137 * its work. The callback will be delivered before the old loader 138 * is destroyed. 139 * 140 * @param id A unique identifier for this loader. Can be whatever you want. 141 * Identifiers are scoped to a particular LoaderManager instance. 142 * @param args Optional arguments to supply to the loader at construction. 143 * @param callback Interface the LoaderManager will call to report about 144 * changes in the state of the loader. Required. 145 */ 146 public abstract <D> Loader<D> restartLoader(int id, Bundle args, 147 LoaderManager.LoaderCallbacks<D> callback); 148 149 /** 150 * Stops and removes the loader with the given ID. 151 */ 152 public abstract void destroyLoader(int id); 153 154 /** 155 * @deprecated Renamed to {@link #destroyLoader}. 156 */ 157 @Deprecated 158 public void stopLoader(int id) { 159 destroyLoader(id); 160 } 161 162 /** 163 * Return the Loader with the given id or null if no matching Loader 164 * is found. 165 */ 166 public abstract <D> Loader<D> getLoader(int id); 167 168 /** 169 * Print the LoaderManager's state into the given stream. 170 * 171 * @param prefix Text to print at the front of each line. 172 * @param fd The raw file descriptor that the dump is being sent to. 173 * @param writer A PrintWriter to which the dump is to be set. 174 * @param args Additional arguments to the dump request. 175 */ 176 public abstract void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args); 177} 178 179class LoaderManagerImpl extends LoaderManager { 180 static final String TAG = "LoaderManagerImpl"; 181 static final boolean DEBUG = true; 182 183 // These are the currently active loaders. A loader is here 184 // from the time its load is started until it has been explicitly 185 // stopped or restarted by the application. 186 final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>(); 187 188 // These are previously run loaders. This list is maintained internally 189 // to avoid destroying a loader while an application is still using it. 190 // It allows an application to restart a loader, but continue using its 191 // previously run loader until the new loader's data is available. 192 final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>(); 193 194 Activity mActivity; 195 boolean mStarted; 196 boolean mRetaining; 197 boolean mRetainingStarted; 198 199 final class LoaderInfo implements Loader.OnLoadCompleteListener<Object> { 200 final int mId; 201 final Bundle mArgs; 202 LoaderManager.LoaderCallbacks<Object> mCallbacks; 203 Loader<Object> mLoader; 204 Object mData; 205 boolean mStarted; 206 boolean mRetaining; 207 boolean mRetainingStarted; 208 boolean mDestroyed; 209 boolean mListenerRegistered; 210 211 public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) { 212 mId = id; 213 mArgs = args; 214 mCallbacks = callbacks; 215 } 216 217 void start() { 218 if (mRetaining && mRetainingStarted) { 219 // Our owner is started, but we were being retained from a 220 // previous instance in the started state... so there is really 221 // nothing to do here, since the loaders are still started. 222 mStarted = true; 223 return; 224 } 225 226 if (mStarted) { 227 // If loader already started, don't restart. 228 return; 229 } 230 231 if (DEBUG) Log.v(TAG, " Starting: " + this); 232 if (mLoader == null && mCallbacks != null) { 233 mLoader = mCallbacks.onCreateLoader(mId, mArgs); 234 } 235 if (mLoader != null) { 236 if (!mListenerRegistered) { 237 mLoader.registerListener(mId, this); 238 mListenerRegistered = true; 239 } 240 mLoader.startLoading(); 241 mStarted = true; 242 } 243 } 244 245 void retain() { 246 if (DEBUG) Log.v(TAG, " Retaining: " + this); 247 mRetaining = true; 248 mRetainingStarted = mStarted; 249 mStarted = false; 250 mCallbacks = null; 251 } 252 253 void finishRetain() { 254 if (mRetaining) { 255 if (DEBUG) Log.v(TAG, " Finished Retaining: " + this); 256 mRetaining = false; 257 if (mStarted != mRetainingStarted) { 258 if (!mStarted) { 259 // This loader was retained in a started state, but 260 // at the end of retaining everything our owner is 261 // no longer started... so make it stop. 262 stop(); 263 } 264 } 265 } 266 267 if (mStarted && mData != null) { 268 // This loader has retained its data, either completely across 269 // a configuration change or just whatever the last data set 270 // was after being restarted from a stop, and now at the point of 271 // finishing the retain we find we remain started, have 272 // our data, and the owner has a new callback... so 273 // let's deliver the data now. 274 callOnLoadFinished(mLoader, mData); 275 } 276 } 277 278 void stop() { 279 if (DEBUG) Log.v(TAG, " Stopping: " + this); 280 mStarted = false; 281 if (!mRetaining) { 282 if (mLoader != null && mListenerRegistered) { 283 // Let the loader know we're done with it 284 mListenerRegistered = false; 285 mLoader.unregisterListener(this); 286 mLoader.stopLoading(); 287 } 288 } 289 } 290 291 void destroy() { 292 if (DEBUG) Log.v(TAG, " Destroying: " + this); 293 mDestroyed = true; 294 if (mCallbacks != null && mLoader != null && mData != null) { 295 String lastBecause = null; 296 if (mActivity != null) { 297 lastBecause = mActivity.mFragments.mNoTransactionsBecause; 298 mActivity.mFragments.mNoTransactionsBecause = "onLoaderReset"; 299 } 300 try { 301 mCallbacks.onLoaderReset(mLoader); 302 } finally { 303 if (mActivity != null) { 304 mActivity.mFragments.mNoTransactionsBecause = lastBecause; 305 } 306 } 307 } 308 mCallbacks = null; 309 mData = null; 310 if (mLoader != null) { 311 if (mListenerRegistered) { 312 mListenerRegistered = false; 313 mLoader.unregisterListener(this); 314 } 315 mLoader.reset(); 316 } 317 } 318 319 @Override public void onLoadComplete(Loader<Object> loader, Object data) { 320 if (DEBUG) Log.v(TAG, "onLoadComplete: " + this + " mDestroyed=" + mDestroyed); 321 322 if (mDestroyed) { 323 return; 324 } 325 326 // Notify of the new data so the app can switch out the old data before 327 // we try to destroy it. 328 mData = data; 329 if (mStarted) { 330 callOnLoadFinished(loader, data); 331 } 332 333 if (DEBUG) Log.v(TAG, "onLoadFinished returned: " + this); 334 335 // We have now given the application the new loader with its 336 // loaded data, so it should have stopped using the previous 337 // loader. If there is a previous loader on the inactive list, 338 // clean it up. 339 LoaderInfo info = mInactiveLoaders.get(mId); 340 if (info != null && info != this) { 341 info.destroy(); 342 mInactiveLoaders.remove(mId); 343 } 344 } 345 346 void callOnLoadFinished(Loader<Object> loader, Object data) { 347 if (mCallbacks != null) { 348 String lastBecause = null; 349 if (mActivity != null) { 350 lastBecause = mActivity.mFragments.mNoTransactionsBecause; 351 mActivity.mFragments.mNoTransactionsBecause = "onLoadFinished"; 352 } 353 try { 354 mCallbacks.onLoadFinished(loader, data); 355 } finally { 356 if (mActivity != null) { 357 mActivity.mFragments.mNoTransactionsBecause = lastBecause; 358 } 359 } 360 } 361 } 362 363 @Override 364 public String toString() { 365 StringBuilder sb = new StringBuilder(64); 366 sb.append("LoaderInfo{"); 367 sb.append(Integer.toHexString(System.identityHashCode(this))); 368 sb.append(" #"); 369 sb.append(mId); 370 if (mArgs != null) { 371 sb.append(" "); 372 sb.append(mArgs.toString()); 373 } 374 sb.append("}"); 375 return sb.toString(); 376 } 377 378 public String toBasicString() { 379 StringBuilder sb = new StringBuilder(64); 380 sb.append("LoaderInfo{"); 381 sb.append(Integer.toHexString(System.identityHashCode(this))); 382 sb.append(" #"); 383 sb.append(mId); 384 sb.append("}"); 385 return sb.toString(); 386 } 387 388 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 389 writer.print(prefix); writer.print("mId="); writer.print(mId); 390 writer.print(" mArgs="); writer.println(mArgs); 391 writer.print(prefix); writer.print("mCallbacks="); writer.println(mCallbacks); 392 writer.print(prefix); writer.print("mLoader="); writer.println(mLoader); 393 writer.print(prefix); writer.print("mData="); writer.println(mData); 394 writer.print(prefix); writer.print("mStarted="); writer.print(mStarted); 395 writer.print(" mRetaining="); writer.print(mRetaining); 396 writer.print(" mDestroyed="); writer.print(mDestroyed); 397 writer.print(" mListenerRegistered="); writer.println(mListenerRegistered); 398 } 399 } 400 401 LoaderManagerImpl(Activity activity, boolean started) { 402 mActivity = activity; 403 mStarted = started; 404 } 405 406 void updateActivity(Activity activity) { 407 mActivity = activity; 408 } 409 410 private LoaderInfo createLoader(int id, Bundle args, 411 LoaderManager.LoaderCallbacks<Object> callback) { 412 LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 413 mLoaders.put(id, info); 414 Loader<Object> loader = callback.onCreateLoader(id, args); 415 info.mLoader = (Loader<Object>)loader; 416 if (mStarted) { 417 // The activity will start all existing loaders in it's onStart(), 418 // so only start them here if we're past that point of the activitiy's 419 // life cycle 420 info.start(); 421 } 422 return info; 423 } 424 425 @SuppressWarnings("unchecked") 426 public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { 427 LoaderInfo info = mLoaders.get(id); 428 429 if (DEBUG) Log.v(TAG, "initLoader in " + this + ": cur=" + info); 430 431 if (info == null) { 432 // Loader doesn't already exist; create. 433 info = createLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 434 } else { 435 info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback; 436 } 437 438 if (info.mData != null && mStarted) { 439 // If the loader has already generated its data, report it now. 440 info.callOnLoadFinished(info.mLoader, info.mData); 441 } 442 443 return (Loader<D>)info.mLoader; 444 } 445 446 @SuppressWarnings("unchecked") 447 public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { 448 LoaderInfo info = mLoaders.get(id); 449 if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": cur=" + info); 450 if (info != null) { 451 LoaderInfo inactive = mInactiveLoaders.get(id); 452 if (inactive != null) { 453 if (info.mData != null) { 454 // This loader now has data... we are probably being 455 // called from within onLoadComplete, where we haven't 456 // yet destroyed the last inactive loader. So just do 457 // that now. 458 if (DEBUG) Log.v(TAG, " Removing last inactive loader in " + this); 459 inactive.destroy(); 460 mInactiveLoaders.put(id, info); 461 } else { 462 // We already have an inactive loader for this ID that we are 463 // waiting for! Now we have three active loaders... let's just 464 // drop the one in the middle, since we are still waiting for 465 // its result but that result is already out of date. 466 if (DEBUG) Log.v(TAG, " Removing intermediate loader in " + this); 467 info.destroy(); 468 } 469 } else { 470 // Keep track of the previous instance of this loader so we can destroy 471 // it when the new one completes. 472 if (DEBUG) Log.v(TAG, " Making inactive: " + info); 473 mInactiveLoaders.put(id, info); 474 } 475 } 476 477 info = createLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 478 return (Loader<D>)info.mLoader; 479 } 480 481 public void destroyLoader(int id) { 482 if (DEBUG) Log.v(TAG, "stopLoader in " + this + " of " + id); 483 int idx = mLoaders.indexOfKey(id); 484 if (idx >= 0) { 485 LoaderInfo info = mLoaders.valueAt(idx); 486 mLoaders.removeAt(idx); 487 info.destroy(); 488 } 489 } 490 491 @SuppressWarnings("unchecked") 492 public <D> Loader<D> getLoader(int id) { 493 LoaderInfo loaderInfo = mLoaders.get(id); 494 if (loaderInfo != null) { 495 return (Loader<D>)mLoaders.get(id).mLoader; 496 } 497 return null; 498 } 499 500 void doStart() { 501 if (DEBUG) Log.v(TAG, "Starting: " + this); 502 if (mStarted) { 503 RuntimeException e = new RuntimeException("here"); 504 e.fillInStackTrace(); 505 Log.w(TAG, "Called doStart when already started: " + this, e); 506 return; 507 } 508 509 mStarted = true; 510 511 // Call out to sub classes so they can start their loaders 512 // Let the existing loaders know that we want to be notified when a load is complete 513 for (int i = mLoaders.size()-1; i >= 0; i--) { 514 mLoaders.valueAt(i).start(); 515 } 516 } 517 518 void doStop() { 519 if (DEBUG) Log.v(TAG, "Stopping: " + this); 520 if (!mStarted) { 521 RuntimeException e = new RuntimeException("here"); 522 e.fillInStackTrace(); 523 Log.w(TAG, "Called doStop when not started: " + this, e); 524 return; 525 } 526 527 for (int i = mLoaders.size()-1; i >= 0; i--) { 528 mLoaders.valueAt(i).stop(); 529 } 530 mStarted = false; 531 } 532 533 void doRetain() { 534 if (DEBUG) Log.v(TAG, "Retaining: " + this); 535 if (!mStarted) { 536 RuntimeException e = new RuntimeException("here"); 537 e.fillInStackTrace(); 538 Log.w(TAG, "Called doRetain when not started: " + this, e); 539 return; 540 } 541 542 mRetaining = true; 543 mStarted = false; 544 for (int i = mLoaders.size()-1; i >= 0; i--) { 545 mLoaders.valueAt(i).retain(); 546 } 547 } 548 549 void finishRetain() { 550 if (DEBUG) Log.v(TAG, "Finished Retaining: " + this); 551 552 mRetaining = false; 553 for (int i = mLoaders.size()-1; i >= 0; i--) { 554 mLoaders.valueAt(i).finishRetain(); 555 } 556 } 557 558 void doDestroy() { 559 if (!mRetaining) { 560 if (DEBUG) Log.v(TAG, "Destroying Active: " + this); 561 for (int i = mLoaders.size()-1; i >= 0; i--) { 562 mLoaders.valueAt(i).destroy(); 563 } 564 } 565 566 if (DEBUG) Log.v(TAG, "Destroying Inactive: " + this); 567 for (int i = mInactiveLoaders.size()-1; i >= 0; i--) { 568 mInactiveLoaders.valueAt(i).destroy(); 569 } 570 mInactiveLoaders.clear(); 571 } 572 573 @Override 574 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 575 if (mLoaders.size() > 0) { 576 writer.print(prefix); writer.println("Active Loaders:"); 577 String innerPrefix = prefix + " "; 578 for (int i=0; i < mLoaders.size(); i++) { 579 LoaderInfo li = mLoaders.valueAt(i); 580 writer.print(prefix); writer.print(" #"); writer.print(mLoaders.keyAt(i)); 581 writer.print(": "); writer.println(li.toBasicString()); 582 li.dump(innerPrefix, fd, writer, args); 583 } 584 } 585 if (mInactiveLoaders.size() > 0) { 586 writer.print(prefix); writer.println("Inactive Loaders:"); 587 String innerPrefix = prefix + " "; 588 for (int i=0; i < mInactiveLoaders.size(); i++) { 589 LoaderInfo li = mInactiveLoaders.valueAt(i); 590 writer.print(prefix); writer.print(" #"); writer.print(mInactiveLoaders.keyAt(i)); 591 writer.print(": "); writer.println(li.toBasicString()); 592 li.dump(innerPrefix, fd, writer, args); 593 } 594 } 595 } 596} 597