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