Home | History | Annotate | Download | only in app
      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 
     17 package androidx.loader.app;
     18 
     19 import android.os.Bundle;
     20 import android.os.Looper;
     21 import android.util.Log;
     22 
     23 import androidx.annotation.MainThread;
     24 import androidx.annotation.NonNull;
     25 import androidx.annotation.Nullable;
     26 import androidx.collection.SparseArrayCompat;
     27 import androidx.core.util.DebugUtils;
     28 import androidx.lifecycle.LifecycleOwner;
     29 import androidx.lifecycle.MutableLiveData;
     30 import androidx.lifecycle.Observer;
     31 import androidx.lifecycle.ViewModel;
     32 import androidx.lifecycle.ViewModelProvider;
     33 import androidx.lifecycle.ViewModelStore;
     34 import androidx.loader.content.Loader;
     35 
     36 import java.io.FileDescriptor;
     37 import java.io.PrintWriter;
     38 import java.lang.reflect.Modifier;
     39 
     40 class 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