LoaderManagerImpl.java revision 01b544c7352c805d3c18f023b2eaeecb924ee1b3
1/* 2 * Copyright 2018 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 androidx.loader.app; 18 19import android.arch.lifecycle.LifecycleOwner; 20import android.arch.lifecycle.MutableLiveData; 21import android.arch.lifecycle.Observer; 22import android.arch.lifecycle.ViewModel; 23import android.arch.lifecycle.ViewModelProvider; 24import android.arch.lifecycle.ViewModelStore; 25import android.os.Bundle; 26import android.os.Looper; 27import android.util.Log; 28 29import java.io.FileDescriptor; 30import java.io.PrintWriter; 31import java.lang.reflect.Modifier; 32 33import androidx.annotation.MainThread; 34import androidx.annotation.NonNull; 35import androidx.annotation.Nullable; 36import androidx.collection.SparseArrayCompat; 37import androidx.core.util.DebugUtils; 38import androidx.loader.content.Loader; 39 40class LoaderManagerImpl extends LoaderManager { 41 static final String TAG = "LoaderManager"; 42 static boolean DEBUG = false; 43 44 /** 45 * Class which manages the state of a {@link Loader} and its associated 46 * {@link LoaderCallbacks} 47 * 48 * @param <D> Type of data the Loader handles 49 */ 50 public static class LoaderInfo<D> extends MutableLiveData<D> 51 implements Loader.OnLoadCompleteListener<D> { 52 53 private final int mId; 54 private final @Nullable Bundle mArgs; 55 private final @NonNull Loader<D> mLoader; 56 private LifecycleOwner mLifecycleOwner; 57 private LoaderObserver<D> mObserver; 58 private Loader<D> mPriorLoader; 59 60 LoaderInfo(int id, @Nullable Bundle args, @NonNull Loader<D> loader, 61 @Nullable Loader<D> priorLoader) { 62 mId = id; 63 mArgs = args; 64 mLoader = loader; 65 mPriorLoader = priorLoader; 66 mLoader.registerListener(id, this); 67 } 68 69 @NonNull 70 Loader<D> getLoader() { 71 return mLoader; 72 } 73 74 @Override 75 protected void onActive() { 76 if (DEBUG) Log.v(TAG, " Starting: " + LoaderInfo.this); 77 mLoader.startLoading(); 78 } 79 80 @Override 81 protected void onInactive() { 82 if (DEBUG) Log.v(TAG, " Stopping: " + LoaderInfo.this); 83 mLoader.stopLoading(); 84 } 85 86 /** 87 * Set the {@link LoaderCallbacks} to associate with this {@link Loader}. This 88 * removes any existing {@link LoaderCallbacks}. 89 * 90 * @param owner The lifecycle that should be used to start and stop the {@link Loader} 91 * @param callback The new {@link LoaderCallbacks} to use 92 * @return The {@link Loader} associated with this LoaderInfo 93 */ 94 @MainThread 95 @NonNull 96 Loader<D> setCallback(@NonNull LifecycleOwner owner, 97 @NonNull LoaderCallbacks<D> callback) { 98 LoaderObserver<D> observer = new LoaderObserver<>(mLoader, callback); 99 // Add the new observer 100 observe(owner, observer); 101 // Loaders only support one observer at a time, so remove the current observer, if any 102 if (mObserver != null) { 103 removeObserver(mObserver); 104 } 105 mLifecycleOwner = owner; 106 mObserver = observer; 107 return mLoader; 108 } 109 110 void markForRedelivery() { 111 LifecycleOwner lifecycleOwner = mLifecycleOwner; 112 LoaderObserver<D> observer = mObserver; 113 if (lifecycleOwner != null && observer != null) { 114 // Removing and re-adding the observer ensures that the 115 // observer is called again, even if they had already 116 // received the current data 117 // Use super.removeObserver to avoid nulling out mLifecycleOwner & mObserver 118 super.removeObserver(observer); 119 observe(lifecycleOwner, observer); 120 } 121 } 122 123 boolean isCallbackWaitingForData() { 124 //noinspection SimplifiableIfStatement 125 if (!hasActiveObservers()) { 126 // No active observers means no one is waiting for data 127 return false; 128 } 129 return mObserver != null && !mObserver.hasDeliveredData(); 130 } 131 132 @Override 133 public void removeObserver(@NonNull Observer<D> observer) { 134 super.removeObserver(observer); 135 // Clear out our references when the observer is removed to avoid leaking 136 mLifecycleOwner = null; 137 mObserver = null; 138 } 139 140 /** 141 * Destroys this LoaderInfo, its underlying {@link #getLoader() Loader}, and removes any 142 * existing {@link androidx.loader.app.LoaderManager.LoaderCallbacks}. 143 * 144 * @param reset Whether the LoaderCallbacks and Loader should be reset. 145 * @return When reset is false, returns any Loader that still needs to be reset 146 */ 147 @MainThread 148 Loader<D> destroy(boolean reset) { 149 if (DEBUG) Log.v(TAG, " Destroying: " + this); 150 // First tell the Loader that we don't need it anymore 151 mLoader.cancelLoad(); 152 mLoader.abandon(); 153 // Then clean up the LoaderObserver 154 LoaderObserver<D> observer = mObserver; 155 if (observer != null) { 156 removeObserver(observer); 157 if (reset) { 158 observer.reset(); 159 } 160 } 161 // Finally, clean up the Loader 162 mLoader.unregisterListener(this); 163 if ((observer != null && !observer.hasDeliveredData()) || reset) { 164 mLoader.reset(); 165 return mPriorLoader; 166 } 167 return mLoader; 168 } 169 170 @Override 171 public void onLoadComplete(@NonNull Loader<D> loader, @Nullable D data) { 172 if (DEBUG) Log.v(TAG, "onLoadComplete: " + this); 173 if (Looper.myLooper() == Looper.getMainLooper()) { 174 setValue(data); 175 } else { 176 // The Loader#deliverResult method that calls this should 177 // only be called on the main thread, so this should never 178 // happen, but we don't want to lose the data 179 if (DEBUG) { 180 Log.w(TAG, "onLoadComplete was incorrectly called on a " 181 + "background thread"); 182 } 183 postValue(data); 184 } 185 } 186 187 @Override 188 public void setValue(D value) { 189 super.setValue(value); 190 // Now that the new data has arrived, we can reset any prior Loader 191 if (mPriorLoader != null) { 192 mPriorLoader.reset(); 193 mPriorLoader = null; 194 } 195 } 196 197 @Override 198 public String toString() { 199 StringBuilder sb = new StringBuilder(64); 200 sb.append("LoaderInfo{"); 201 sb.append(Integer.toHexString(System.identityHashCode(this))); 202 sb.append(" #"); 203 sb.append(mId); 204 sb.append(" : "); 205 DebugUtils.buildShortClassTag(mLoader, sb); 206 sb.append("}}"); 207 return sb.toString(); 208 } 209 210 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 211 writer.print(prefix); writer.print("mId="); writer.print(mId); 212 writer.print(" mArgs="); writer.println(mArgs); 213 writer.print(prefix); writer.print("mLoader="); writer.println(mLoader); 214 mLoader.dump(prefix + " ", fd, writer, args); 215 if (mObserver != null) { 216 writer.print(prefix); writer.print("mCallbacks="); writer.println(mObserver); 217 mObserver.dump(prefix + " ", writer); 218 } 219 writer.print(prefix); writer.print("mData="); writer.println( 220 getLoader().dataToString(getValue())); 221 writer.print(prefix); writer.print("mStarted="); writer.println( 222 hasActiveObservers()); 223 } 224 } 225 226 /** 227 * Encapsulates the {@link LoaderCallbacks} as a {@link Observer}. 228 * 229 * @param <D> Type of data the LoaderCallbacks handles 230 */ 231 static class LoaderObserver<D> implements Observer<D> { 232 233 private final @NonNull Loader<D> mLoader; 234 private final @NonNull LoaderCallbacks<D> mCallback; 235 236 private boolean mDeliveredData = false; 237 238 LoaderObserver(@NonNull Loader<D> loader, @NonNull LoaderCallbacks<D> callback) { 239 mLoader = loader; 240 mCallback = callback; 241 } 242 243 @Override 244 public void onChanged(@Nullable D data) { 245 if (DEBUG) { 246 Log.v(TAG, " onLoadFinished in " + mLoader + ": " 247 + mLoader.dataToString(data)); 248 } 249 mCallback.onLoadFinished(mLoader, data); 250 mDeliveredData = true; 251 } 252 253 boolean hasDeliveredData() { 254 return mDeliveredData; 255 } 256 257 @MainThread 258 void reset() { 259 if (mDeliveredData) { 260 if (DEBUG) Log.v(TAG, " Resetting: " + mLoader); 261 mCallback.onLoaderReset(mLoader); 262 } 263 } 264 265 @Override 266 public String toString() { 267 return mCallback.toString(); 268 } 269 270 public void dump(String prefix, PrintWriter writer) { 271 writer.print(prefix); writer.print("mDeliveredData="); writer.println( 272 mDeliveredData); 273 } 274 } 275 276 /** 277 * ViewModel responsible for retaining {@link LoaderInfo} instances across configuration changes 278 */ 279 static class LoaderViewModel extends ViewModel { 280 private static final ViewModelProvider.Factory FACTORY = new ViewModelProvider.Factory() { 281 @NonNull 282 @Override 283 @SuppressWarnings("unchecked") 284 public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { 285 return (T) new LoaderViewModel(); 286 } 287 }; 288 289 @NonNull 290 static LoaderViewModel getInstance(ViewModelStore viewModelStore) { 291 return new ViewModelProvider(viewModelStore, FACTORY).get(LoaderViewModel.class); 292 } 293 294 private SparseArrayCompat<LoaderInfo> mLoaders = new SparseArrayCompat<>(); 295 private boolean mCreatingLoader = false; 296 297 void startCreatingLoader() { 298 mCreatingLoader = true; 299 } 300 301 boolean isCreatingLoader() { 302 return mCreatingLoader; 303 } 304 305 void finishCreatingLoader() { 306 mCreatingLoader = false; 307 } 308 309 void putLoader(int id, @NonNull LoaderInfo info) { 310 mLoaders.put(id, info); 311 } 312 313 @SuppressWarnings("unchecked") 314 <D> LoaderInfo<D> getLoader(int id) { 315 return mLoaders.get(id); 316 } 317 318 void removeLoader(int id) { 319 mLoaders.remove(id); 320 } 321 322 boolean hasRunningLoaders() { 323 int size = mLoaders.size(); 324 for (int index = 0; index < size; index++) { 325 LoaderInfo info = mLoaders.valueAt(index); 326 if (info.isCallbackWaitingForData()) { 327 return true; 328 } 329 } 330 return false; 331 } 332 333 void markForRedelivery() { 334 int size = mLoaders.size(); 335 for (int index = 0; index < size; index++) { 336 LoaderInfo info = mLoaders.valueAt(index); 337 info.markForRedelivery(); 338 } 339 } 340 341 @Override 342 protected void onCleared() { 343 super.onCleared(); 344 int size = mLoaders.size(); 345 for (int index = 0; index < size; index++) { 346 LoaderInfo info = mLoaders.valueAt(index); 347 info.destroy(true); 348 } 349 mLoaders.clear(); 350 } 351 352 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 353 if (mLoaders.size() > 0) { 354 writer.print(prefix); writer.println("Loaders:"); 355 String innerPrefix = prefix + " "; 356 for (int i = 0; i < mLoaders.size(); i++) { 357 LoaderInfo info = mLoaders.valueAt(i); 358 writer.print(prefix); writer.print(" #"); writer.print(mLoaders.keyAt(i)); 359 writer.print(": "); writer.println(info.toString()); 360 info.dump(innerPrefix, fd, writer, args); 361 } 362 } 363 } 364 } 365 366 private final @NonNull LifecycleOwner mLifecycleOwner; 367 private final @NonNull LoaderViewModel mLoaderViewModel; 368 369 LoaderManagerImpl(@NonNull LifecycleOwner lifecycleOwner, 370 @NonNull ViewModelStore viewModelStore) { 371 mLifecycleOwner = lifecycleOwner; 372 mLoaderViewModel = LoaderViewModel.getInstance(viewModelStore); 373 } 374 375 @MainThread 376 @NonNull 377 private <D> Loader<D> createAndInstallLoader(int id, @Nullable Bundle args, 378 @NonNull LoaderCallbacks<D> callback, @Nullable Loader<D> priorLoader) { 379 LoaderInfo<D> info; 380 try { 381 mLoaderViewModel.startCreatingLoader(); 382 Loader<D> loader = callback.onCreateLoader(id, args); 383 if (loader.getClass().isMemberClass() 384 && !Modifier.isStatic(loader.getClass().getModifiers())) { 385 throw new IllegalArgumentException("Object returned from onCreateLoader " 386 + "must not be a non-static inner member class: " 387 + loader); 388 } 389 info = new LoaderInfo<>(id, args, loader, priorLoader); 390 if (DEBUG) Log.v(TAG, " Created new loader " + info); 391 mLoaderViewModel.putLoader(id, info); 392 } finally { 393 mLoaderViewModel.finishCreatingLoader(); 394 } 395 return info.setCallback(mLifecycleOwner, callback); 396 } 397 398 @MainThread 399 @NonNull 400 @Override 401 public <D> Loader<D> initLoader(int id, @Nullable Bundle args, 402 @NonNull LoaderCallbacks<D> callback) { 403 if (mLoaderViewModel.isCreatingLoader()) { 404 throw new IllegalStateException("Called while creating a loader"); 405 } 406 if (Looper.getMainLooper() != Looper.myLooper()) { 407 throw new IllegalStateException("initLoader must be called on the main thread"); 408 } 409 410 LoaderInfo<D> info = mLoaderViewModel.getLoader(id); 411 412 if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args); 413 414 if (info == null) { 415 // Loader doesn't already exist; create. 416 return createAndInstallLoader(id, args, callback, null); 417 } else { 418 if (DEBUG) Log.v(TAG, " Re-using existing loader " + info); 419 return info.setCallback(mLifecycleOwner, callback); 420 } 421 } 422 423 @MainThread 424 @NonNull 425 @Override 426 public <D> Loader<D> restartLoader(int id, @Nullable Bundle args, 427 @NonNull LoaderCallbacks<D> callback) { 428 if (mLoaderViewModel.isCreatingLoader()) { 429 throw new IllegalStateException("Called while creating a loader"); 430 } 431 if (Looper.getMainLooper() != Looper.myLooper()) { 432 throw new IllegalStateException("restartLoader must be called on the main thread"); 433 } 434 435 if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": args=" + args); 436 LoaderInfo<D> info = mLoaderViewModel.getLoader(id); 437 Loader<D> priorLoader = null; 438 if (info != null) { 439 priorLoader = info.destroy(false); 440 } 441 // And create a new Loader 442 return createAndInstallLoader(id, args, callback, priorLoader); 443 } 444 445 @MainThread 446 @Override 447 public void destroyLoader(int id) { 448 if (mLoaderViewModel.isCreatingLoader()) { 449 throw new IllegalStateException("Called while creating a loader"); 450 } 451 if (Looper.getMainLooper() != Looper.myLooper()) { 452 throw new IllegalStateException("destroyLoader must be called on the main thread"); 453 } 454 455 if (DEBUG) Log.v(TAG, "destroyLoader in " + this + " of " + id); 456 LoaderInfo info = mLoaderViewModel.getLoader(id); 457 if (info != null) { 458 info.destroy(true); 459 mLoaderViewModel.removeLoader(id); 460 } 461 } 462 463 @Nullable 464 @Override 465 public <D> Loader<D> getLoader(int id) { 466 if (mLoaderViewModel.isCreatingLoader()) { 467 throw new IllegalStateException("Called while creating a loader"); 468 } 469 470 LoaderInfo<D> info = mLoaderViewModel.getLoader(id); 471 return info != null ? info.getLoader() : null; 472 } 473 474 @Override 475 public void markForRedelivery() { 476 mLoaderViewModel.markForRedelivery(); 477 } 478 479 @Override 480 public String toString() { 481 StringBuilder sb = new StringBuilder(128); 482 sb.append("LoaderManager{"); 483 sb.append(Integer.toHexString(System.identityHashCode(this))); 484 sb.append(" in "); 485 DebugUtils.buildShortClassTag(mLifecycleOwner, sb); 486 sb.append("}}"); 487 return sb.toString(); 488 } 489 490 @Override 491 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 492 mLoaderViewModel.dump(prefix, fd, writer, args); 493 } 494 495 @Override 496 public boolean hasRunningLoaders() { 497 return mLoaderViewModel.hasRunningLoaders(); 498 } 499} 500