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.os.Bundle; 20import android.os.Looper; 21import android.util.Log; 22 23import androidx.annotation.MainThread; 24import androidx.annotation.NonNull; 25import androidx.annotation.Nullable; 26import androidx.collection.SparseArrayCompat; 27import androidx.core.util.DebugUtils; 28import androidx.lifecycle.LifecycleOwner; 29import androidx.lifecycle.MutableLiveData; 30import androidx.lifecycle.Observer; 31import androidx.lifecycle.ViewModel; 32import androidx.lifecycle.ViewModelProvider; 33import androidx.lifecycle.ViewModelStore; 34import androidx.loader.content.Loader; 35 36import java.io.FileDescriptor; 37import java.io.PrintWriter; 38import java.lang.reflect.Modifier; 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<? super 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 @SuppressWarnings("deprecation") 211 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 212 writer.print(prefix); writer.print("mId="); writer.print(mId); 213 writer.print(" mArgs="); writer.println(mArgs); 214 writer.print(prefix); writer.print("mLoader="); writer.println(mLoader); 215 mLoader.dump(prefix + " ", fd, writer, args); 216 if (mObserver != null) { 217 writer.print(prefix); writer.print("mCallbacks="); writer.println(mObserver); 218 mObserver.dump(prefix + " ", writer); 219 } 220 writer.print(prefix); writer.print("mData="); writer.println( 221 getLoader().dataToString(getValue())); 222 writer.print(prefix); writer.print("mStarted="); writer.println( 223 hasActiveObservers()); 224 } 225 } 226 227 /** 228 * Encapsulates the {@link LoaderCallbacks} as a {@link Observer}. 229 * 230 * @param <D> Type of data the LoaderCallbacks handles 231 */ 232 static class LoaderObserver<D> implements Observer<D> { 233 234 private final @NonNull Loader<D> mLoader; 235 private final @NonNull LoaderCallbacks<D> mCallback; 236 237 private boolean mDeliveredData = false; 238 239 LoaderObserver(@NonNull Loader<D> loader, @NonNull LoaderCallbacks<D> callback) { 240 mLoader = loader; 241 mCallback = callback; 242 } 243 244 @Override 245 public void onChanged(@Nullable D data) { 246 if (DEBUG) { 247 Log.v(TAG, " onLoadFinished in " + mLoader + ": " 248 + mLoader.dataToString(data)); 249 } 250 mCallback.onLoadFinished(mLoader, data); 251 mDeliveredData = true; 252 } 253 254 boolean hasDeliveredData() { 255 return mDeliveredData; 256 } 257 258 @MainThread 259 void reset() { 260 if (mDeliveredData) { 261 if (DEBUG) Log.v(TAG, " Resetting: " + mLoader); 262 mCallback.onLoaderReset(mLoader); 263 } 264 } 265 266 @Override 267 public String toString() { 268 return mCallback.toString(); 269 } 270 271 public void dump(String prefix, PrintWriter writer) { 272 writer.print(prefix); writer.print("mDeliveredData="); writer.println( 273 mDeliveredData); 274 } 275 } 276 277 /** 278 * ViewModel responsible for retaining {@link LoaderInfo} instances across configuration changes 279 */ 280 static class LoaderViewModel extends ViewModel { 281 private static final ViewModelProvider.Factory FACTORY = new ViewModelProvider.Factory() { 282 @NonNull 283 @Override 284 @SuppressWarnings("unchecked") 285 public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { 286 return (T) new LoaderViewModel(); 287 } 288 }; 289 290 @NonNull 291 static LoaderViewModel getInstance(ViewModelStore viewModelStore) { 292 return new ViewModelProvider(viewModelStore, FACTORY).get(LoaderViewModel.class); 293 } 294 295 private SparseArrayCompat<LoaderInfo> mLoaders = new SparseArrayCompat<>(); 296 private boolean mCreatingLoader = false; 297 298 void startCreatingLoader() { 299 mCreatingLoader = true; 300 } 301 302 boolean isCreatingLoader() { 303 return mCreatingLoader; 304 } 305 306 void finishCreatingLoader() { 307 mCreatingLoader = false; 308 } 309 310 void putLoader(int id, @NonNull LoaderInfo info) { 311 mLoaders.put(id, info); 312 } 313 314 @SuppressWarnings("unchecked") 315 <D> LoaderInfo<D> getLoader(int id) { 316 return mLoaders.get(id); 317 } 318 319 void removeLoader(int id) { 320 mLoaders.remove(id); 321 } 322 323 boolean hasRunningLoaders() { 324 int size = mLoaders.size(); 325 for (int index = 0; index < size; index++) { 326 LoaderInfo info = mLoaders.valueAt(index); 327 if (info.isCallbackWaitingForData()) { 328 return true; 329 } 330 } 331 return false; 332 } 333 334 void markForRedelivery() { 335 int size = mLoaders.size(); 336 for (int index = 0; index < size; index++) { 337 LoaderInfo info = mLoaders.valueAt(index); 338 info.markForRedelivery(); 339 } 340 } 341 342 @Override 343 protected void onCleared() { 344 super.onCleared(); 345 int size = mLoaders.size(); 346 for (int index = 0; index < size; index++) { 347 LoaderInfo info = mLoaders.valueAt(index); 348 info.destroy(true); 349 } 350 mLoaders.clear(); 351 } 352 353 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 354 if (mLoaders.size() > 0) { 355 writer.print(prefix); writer.println("Loaders:"); 356 String innerPrefix = prefix + " "; 357 for (int i = 0; i < mLoaders.size(); i++) { 358 LoaderInfo info = mLoaders.valueAt(i); 359 writer.print(prefix); writer.print(" #"); writer.print(mLoaders.keyAt(i)); 360 writer.print(": "); writer.println(info.toString()); 361 info.dump(innerPrefix, fd, writer, args); 362 } 363 } 364 } 365 } 366 367 private final @NonNull LifecycleOwner mLifecycleOwner; 368 private final @NonNull LoaderViewModel mLoaderViewModel; 369 370 LoaderManagerImpl(@NonNull LifecycleOwner lifecycleOwner, 371 @NonNull ViewModelStore viewModelStore) { 372 mLifecycleOwner = lifecycleOwner; 373 mLoaderViewModel = LoaderViewModel.getInstance(viewModelStore); 374 } 375 376 @MainThread 377 @NonNull 378 private <D> Loader<D> createAndInstallLoader(int id, @Nullable Bundle args, 379 @NonNull LoaderCallbacks<D> callback, @Nullable Loader<D> priorLoader) { 380 LoaderInfo<D> info; 381 try { 382 mLoaderViewModel.startCreatingLoader(); 383 Loader<D> loader = callback.onCreateLoader(id, args); 384 if (loader == null) { 385 throw new IllegalArgumentException("Object returned from onCreateLoader " 386 + "must not be null"); 387 } 388 if (loader.getClass().isMemberClass() 389 && !Modifier.isStatic(loader.getClass().getModifiers())) { 390 throw new IllegalArgumentException("Object returned from onCreateLoader " 391 + "must not be a non-static inner member class: " 392 + loader); 393 } 394 info = new LoaderInfo<>(id, args, loader, priorLoader); 395 if (DEBUG) Log.v(TAG, " Created new loader " + info); 396 mLoaderViewModel.putLoader(id, info); 397 } finally { 398 mLoaderViewModel.finishCreatingLoader(); 399 } 400 return info.setCallback(mLifecycleOwner, callback); 401 } 402 403 @MainThread 404 @NonNull 405 @Override 406 public <D> Loader<D> initLoader(int id, @Nullable Bundle args, 407 @NonNull LoaderCallbacks<D> callback) { 408 if (mLoaderViewModel.isCreatingLoader()) { 409 throw new IllegalStateException("Called while creating a loader"); 410 } 411 if (Looper.getMainLooper() != Looper.myLooper()) { 412 throw new IllegalStateException("initLoader must be called on the main thread"); 413 } 414 415 LoaderInfo<D> info = mLoaderViewModel.getLoader(id); 416 417 if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args); 418 419 if (info == null) { 420 // Loader doesn't already exist; create. 421 return createAndInstallLoader(id, args, callback, null); 422 } else { 423 if (DEBUG) Log.v(TAG, " Re-using existing loader " + info); 424 return info.setCallback(mLifecycleOwner, callback); 425 } 426 } 427 428 @MainThread 429 @NonNull 430 @Override 431 public <D> Loader<D> restartLoader(int id, @Nullable Bundle args, 432 @NonNull LoaderCallbacks<D> callback) { 433 if (mLoaderViewModel.isCreatingLoader()) { 434 throw new IllegalStateException("Called while creating a loader"); 435 } 436 if (Looper.getMainLooper() != Looper.myLooper()) { 437 throw new IllegalStateException("restartLoader must be called on the main thread"); 438 } 439 440 if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": args=" + args); 441 LoaderInfo<D> info = mLoaderViewModel.getLoader(id); 442 Loader<D> priorLoader = null; 443 if (info != null) { 444 priorLoader = info.destroy(false); 445 } 446 // And create a new Loader 447 return createAndInstallLoader(id, args, callback, priorLoader); 448 } 449 450 @MainThread 451 @Override 452 public void destroyLoader(int id) { 453 if (mLoaderViewModel.isCreatingLoader()) { 454 throw new IllegalStateException("Called while creating a loader"); 455 } 456 if (Looper.getMainLooper() != Looper.myLooper()) { 457 throw new IllegalStateException("destroyLoader must be called on the main thread"); 458 } 459 460 if (DEBUG) Log.v(TAG, "destroyLoader in " + this + " of " + id); 461 LoaderInfo info = mLoaderViewModel.getLoader(id); 462 if (info != null) { 463 info.destroy(true); 464 mLoaderViewModel.removeLoader(id); 465 } 466 } 467 468 @Nullable 469 @Override 470 public <D> Loader<D> getLoader(int id) { 471 if (mLoaderViewModel.isCreatingLoader()) { 472 throw new IllegalStateException("Called while creating a loader"); 473 } 474 475 LoaderInfo<D> info = mLoaderViewModel.getLoader(id); 476 return info != null ? info.getLoader() : null; 477 } 478 479 @Override 480 public void markForRedelivery() { 481 mLoaderViewModel.markForRedelivery(); 482 } 483 484 @Override 485 public String toString() { 486 StringBuilder sb = new StringBuilder(128); 487 sb.append("LoaderManager{"); 488 sb.append(Integer.toHexString(System.identityHashCode(this))); 489 sb.append(" in "); 490 DebugUtils.buildShortClassTag(mLifecycleOwner, sb); 491 sb.append("}}"); 492 return sb.toString(); 493 } 494 495 @Deprecated 496 @Override 497 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 498 mLoaderViewModel.dump(prefix, fd, writer, args); 499 } 500 501 @Override 502 public boolean hasRunningLoaders() { 503 return mLoaderViewModel.hasRunningLoaders(); 504 } 505} 506