LoaderManagerImpl.java revision 0f4d973e9bb5a00727068f6a76338908c21902fd
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 android.support.v4.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.support.annotation.MainThread; 28import android.support.annotation.NonNull; 29import android.support.annotation.Nullable; 30import android.support.v4.content.Loader; 31import android.support.v4.util.DebugUtils; 32import android.support.v4.util.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 removeObserver(observer); 114 observe(lifecycleOwner, observer); 115 } 116 } 117 118 boolean isCallbackWaitingForData() { 119 //noinspection SimplifiableIfStatement 120 if (!hasActiveObservers()) { 121 // No active observers means no one is waiting for data 122 return false; 123 } 124 return mObserver != null && !mObserver.hasDeliveredData(); 125 } 126 127 @Override 128 public void removeObserver(@NonNull Observer<D> observer) { 129 super.removeObserver(observer); 130 // Clear out our references when the observer is removed to avoid leaking 131 mLifecycleOwner = null; 132 mObserver = null; 133 } 134 135 @MainThread 136 void destroy() { 137 if (DEBUG) Log.v(TAG, " Destroying: " + this); 138 // First tell the Loader that we don't need it anymore 139 mLoader.cancelLoad(); 140 mLoader.abandon(); 141 // Then clean up the LoaderObserver 142 LoaderObserver<D> observer = mObserver; 143 if (observer != null) { 144 removeObserver(observer); 145 observer.reset(); 146 } 147 // Finally, send the reset to the Loader 148 mLoader.unregisterListener(this); 149 mLoader.reset(); 150 } 151 152 @Override 153 public void onLoadComplete(@NonNull Loader<D> loader, @Nullable D data) { 154 if (DEBUG) Log.v(TAG, "onLoadComplete: " + this); 155 postValue(data); 156 } 157 158 @Override 159 public String toString() { 160 StringBuilder sb = new StringBuilder(64); 161 sb.append("LoaderInfo{"); 162 sb.append(Integer.toHexString(System.identityHashCode(this))); 163 sb.append(" #"); 164 sb.append(mId); 165 sb.append(" : "); 166 DebugUtils.buildShortClassTag(mLoader, sb); 167 sb.append("}}"); 168 return sb.toString(); 169 } 170 171 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 172 writer.print(prefix); writer.print("mId="); writer.print(mId); 173 writer.print(" mArgs="); writer.println(mArgs); 174 writer.print(prefix); writer.print("mLoader="); writer.println(mLoader); 175 mLoader.dump(prefix + " ", fd, writer, args); 176 if (mObserver != null) { 177 writer.print(prefix); writer.print("mCallbacks="); writer.println(mObserver); 178 mObserver.dump(prefix + " ", writer); 179 } 180 writer.print(prefix); writer.print("mData="); writer.println( 181 getLoader().dataToString(getValue())); 182 writer.print(prefix); writer.print("mStarted="); writer.println( 183 hasActiveObservers()); 184 } 185 } 186 187 /** 188 * Encapsulates the {@link LoaderCallbacks} as a {@link Observer}. 189 * 190 * @param <D> Type of data the LoaderCallbacks handles 191 */ 192 static class LoaderObserver<D> implements Observer<D> { 193 194 private final @NonNull Loader<D> mLoader; 195 private final @NonNull LoaderCallbacks<D> mCallback; 196 197 private boolean mDeliveredData = false; 198 199 LoaderObserver(@NonNull Loader<D> loader, @NonNull LoaderCallbacks<D> callback) { 200 mLoader = loader; 201 mCallback = callback; 202 } 203 204 @Override 205 public void onChanged(@Nullable D data) { 206 if (DEBUG) { 207 Log.v(TAG, " onLoadFinished in " + mLoader + ": " 208 + mLoader.dataToString(data)); 209 } 210 mCallback.onLoadFinished(mLoader, data); 211 mDeliveredData = true; 212 } 213 214 boolean hasDeliveredData() { 215 return mDeliveredData; 216 } 217 218 @MainThread 219 void reset() { 220 if (mDeliveredData) { 221 if (DEBUG) Log.v(TAG, " Resetting: " + mLoader); 222 mCallback.onLoaderReset(mLoader); 223 } 224 } 225 226 @Override 227 public String toString() { 228 return mCallback.toString(); 229 } 230 231 public void dump(String prefix, PrintWriter writer) { 232 writer.print(prefix); writer.print("mDeliveredData="); writer.println( 233 mDeliveredData); 234 } 235 } 236 237 /** 238 * ViewModel responsible for retaining {@link LoaderInfo} instances across configuration changes 239 */ 240 static class LoaderViewModel extends ViewModel { 241 private static final ViewModelProvider.Factory FACTORY = new ViewModelProvider.Factory() { 242 @NonNull 243 @Override 244 @SuppressWarnings("unchecked") 245 public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { 246 return (T) new LoaderViewModel(); 247 } 248 }; 249 250 @NonNull 251 static LoaderViewModel getInstance(ViewModelStore viewModelStore) { 252 return new ViewModelProvider(viewModelStore, FACTORY).get(LoaderViewModel.class); 253 } 254 255 private SparseArrayCompat<LoaderInfo> mLoaders = new SparseArrayCompat<>(); 256 257 void putLoader(int id, @NonNull LoaderInfo info) { 258 mLoaders.put(id, info); 259 } 260 261 @SuppressWarnings("unchecked") 262 <D> LoaderInfo<D> getLoader(int id) { 263 return mLoaders.get(id); 264 } 265 266 void removeLoader(int id) { 267 mLoaders.remove(id); 268 } 269 270 boolean hasRunningLoaders() { 271 int size = mLoaders.size(); 272 for (int index = 0; index < size; index++) { 273 LoaderInfo info = mLoaders.valueAt(index); 274 if (info.isCallbackWaitingForData()) { 275 return true; 276 } 277 } 278 return false; 279 } 280 281 void markForRedelivery() { 282 int size = mLoaders.size(); 283 for (int index = 0; index < size; index++) { 284 LoaderInfo info = mLoaders.valueAt(index); 285 info.markForRedelivery(); 286 } 287 } 288 289 @Override 290 protected void onCleared() { 291 super.onCleared(); 292 int size = mLoaders.size(); 293 for (int index = 0; index < size; index++) { 294 LoaderInfo info = mLoaders.valueAt(index); 295 info.destroy(); 296 } 297 mLoaders.clear(); 298 } 299 300 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 301 if (mLoaders.size() > 0) { 302 writer.print(prefix); writer.println("Loaders:"); 303 String innerPrefix = prefix + " "; 304 for (int i = 0; i < mLoaders.size(); i++) { 305 LoaderInfo info = mLoaders.valueAt(i); 306 writer.print(prefix); writer.print(" #"); writer.print(mLoaders.keyAt(i)); 307 writer.print(": "); writer.println(info.toString()); 308 info.dump(innerPrefix, fd, writer, args); 309 } 310 } 311 } 312 } 313 314 private final @NonNull LifecycleOwner mLifecycleOwner; 315 private final @NonNull LoaderViewModel mLoaderViewModel; 316 317 private boolean mCreatingLoader; 318 319 LoaderManagerImpl(@NonNull LifecycleOwner lifecycleOwner, 320 @NonNull ViewModelStore viewModelStore) { 321 mLifecycleOwner = lifecycleOwner; 322 mLoaderViewModel = LoaderViewModel.getInstance(viewModelStore); 323 } 324 325 @MainThread 326 @NonNull 327 private <D> Loader<D> createAndInstallLoader(int id, @Nullable Bundle args, 328 @NonNull LoaderCallbacks<D> callback) { 329 try { 330 mCreatingLoader = true; 331 Loader<D> loader = callback.onCreateLoader(id, args); 332 if (loader.getClass().isMemberClass() 333 && !Modifier.isStatic(loader.getClass().getModifiers())) { 334 throw new IllegalArgumentException("Object returned from onCreateLoader " 335 + "must not be a non-static inner member class: " 336 + loader); 337 } 338 LoaderInfo<D> info = new LoaderInfo<>(id, args, loader); 339 if (DEBUG) Log.v(TAG, " Created new loader " + info); 340 mLoaderViewModel.putLoader(id, info); 341 return info.setCallback(mLifecycleOwner, callback); 342 } finally { 343 mCreatingLoader = false; 344 } 345 } 346 347 @MainThread 348 @NonNull 349 @Override 350 public <D> Loader<D> initLoader(int id, @Nullable Bundle args, 351 @NonNull LoaderCallbacks<D> callback) { 352 if (mCreatingLoader) { 353 throw new IllegalStateException("Called while creating a loader"); 354 } 355 if (Looper.getMainLooper() != Looper.myLooper()) { 356 throw new IllegalStateException("initLoader must be called on the main thread"); 357 } 358 359 LoaderInfo<D> info = mLoaderViewModel.getLoader(id); 360 361 if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args); 362 363 if (info == null) { 364 // Loader doesn't already exist; create. 365 return createAndInstallLoader(id, args, callback); 366 } else { 367 if (DEBUG) Log.v(TAG, " Re-using existing loader " + info); 368 return info.setCallback(mLifecycleOwner, callback); 369 } 370 } 371 372 @MainThread 373 @NonNull 374 @Override 375 public <D> Loader<D> restartLoader(int id, @Nullable Bundle args, 376 @NonNull LoaderCallbacks<D> callback) { 377 if (mCreatingLoader) { 378 throw new IllegalStateException("Called while creating a loader"); 379 } 380 if (Looper.getMainLooper() != Looper.myLooper()) { 381 throw new IllegalStateException("restartLoader must be called on the main thread"); 382 } 383 384 if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": args=" + args); 385 // Destroy any existing Loader 386 destroyLoader(id); 387 // And create a new Loader 388 return createAndInstallLoader(id, args, callback); 389 } 390 391 @MainThread 392 @Override 393 public void destroyLoader(int id) { 394 if (mCreatingLoader) { 395 throw new IllegalStateException("Called while creating a loader"); 396 } 397 if (Looper.getMainLooper() != Looper.myLooper()) { 398 throw new IllegalStateException("destroyLoader must be called on the main thread"); 399 } 400 401 if (DEBUG) Log.v(TAG, "destroyLoader in " + this + " of " + id); 402 LoaderInfo info = mLoaderViewModel.getLoader(id); 403 if (info != null) { 404 info.destroy(); 405 mLoaderViewModel.removeLoader(id); 406 } 407 } 408 409 @Nullable 410 @Override 411 public <D> Loader<D> getLoader(int id) { 412 if (mCreatingLoader) { 413 throw new IllegalStateException("Called while creating a loader"); 414 } 415 416 LoaderInfo<D> info = mLoaderViewModel.getLoader(id); 417 return info != null ? info.getLoader() : null; 418 } 419 420 /** 421 * Mark all Loaders associated with this LoaderManager for redelivery of their current 422 * data (if any) the next time the LifecycleOwner is started. In cases where no data has 423 * yet been delivered, this is effectively a no-op. In cases where data has already been 424 * delivered via {@link LoaderCallbacks#onLoadFinished(Loader, Object)}, this will ensure 425 * that {@link LoaderCallbacks#onLoadFinished(Loader, Object)} is called again with the 426 * same data. 427 */ 428 void markForRedelivery() { 429 mLoaderViewModel.markForRedelivery(); 430 } 431 432 @Override 433 public String toString() { 434 StringBuilder sb = new StringBuilder(128); 435 sb.append("LoaderManager{"); 436 sb.append(Integer.toHexString(System.identityHashCode(this))); 437 sb.append(" in "); 438 DebugUtils.buildShortClassTag(mLifecycleOwner, sb); 439 sb.append("}}"); 440 return sb.toString(); 441 } 442 443 @Override 444 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 445 mLoaderViewModel.dump(prefix, fd, writer, args); 446 } 447 448 @Override 449 public boolean hasRunningLoaders() { 450 return mLoaderViewModel.hasRunningLoaders(); 451 } 452} 453