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