LoaderManager.java revision 622b3dc4a902bdc9262981c0a566a70b9a4490b3
1/* 2 * Copyright (C) 2010 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.app; 18 19import android.content.Loader; 20import android.content.Loader.OnLoadCompleteListener; 21import android.os.Bundle; 22import android.util.SparseArray; 23 24/** 25 * Object associated with an {@link Activity} or {@link Fragment} for managing 26 * one or more {@link android.content.Loader} instances associated with it. 27 */ 28public class LoaderManager { 29 final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>(); 30 final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>(); 31 boolean mStarted; 32 boolean mRetaining; 33 boolean mRetainingStarted; 34 35 /** 36 * Callback interface for a client to interact with the manager. 37 */ 38 public interface LoaderCallbacks<D> { 39 public Loader<D> onCreateLoader(int id, Bundle args); 40 public void onLoadFinished(Loader<D> loader, D data); 41 } 42 43 final class LoaderInfo implements Loader.OnLoadCompleteListener<Object> { 44 final int mId; 45 final Bundle mArgs; 46 LoaderManager.LoaderCallbacks<Object> mCallbacks; 47 Loader<Object> mLoader; 48 Object mData; 49 boolean mStarted; 50 boolean mRetaining; 51 boolean mRetainingStarted; 52 boolean mDestroyed; 53 boolean mListenerRegistered; 54 55 public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) { 56 mId = id; 57 mArgs = args; 58 mCallbacks = callbacks; 59 } 60 61 void start() { 62 if (mRetaining && mRetainingStarted) { 63 // Our owner is started, but we were being retained from a 64 // previous instance in the started state... so there is really 65 // nothing to do here, since the loaders are still started. 66 mStarted = true; 67 return; 68 } 69 70 if (mLoader == null && mCallbacks != null) { 71 mLoader = mCallbacks.onCreateLoader(mId, mArgs); 72 } 73 if (mLoader != null) { 74 mLoader.registerListener(mId, this); 75 mListenerRegistered = true; 76 mLoader.startLoading(); 77 mStarted = true; 78 } 79 } 80 81 void retain() { 82 mRetaining = true; 83 mRetainingStarted = mStarted; 84 mStarted = false; 85 mCallbacks = null; 86 } 87 88 void finishRetain() { 89 if (mRetaining) { 90 mRetaining = false; 91 if (mStarted != mRetainingStarted) { 92 if (!mStarted) { 93 // This loader was retained in a started state, but 94 // at the end of retaining everything our owner is 95 // no longer started... so make it stop. 96 stop(); 97 } 98 } 99 if (mStarted && mData != null && mCallbacks != null) { 100 // This loader was retained, and now at the point of 101 // finishing the retain we find we remain started, have 102 // our data, and the owner has a new callback... so 103 // let's deliver the data now. 104 mCallbacks.onLoadFinished(mLoader, mData); 105 } 106 } 107 } 108 109 void stop() { 110 mStarted = false; 111 if (mLoader != null && mListenerRegistered) { 112 // Let the loader know we're done with it 113 mListenerRegistered = false; 114 mLoader.unregisterListener(this); 115 } 116 } 117 118 void destroy() { 119 mDestroyed = true; 120 mCallbacks = null; 121 if (mLoader != null) { 122 if (mListenerRegistered) { 123 mListenerRegistered = false; 124 mLoader.unregisterListener(this); 125 } 126 mLoader.destroy(); 127 } 128 } 129 130 @Override public void onLoadComplete(Loader<Object> loader, Object data) { 131 if (mDestroyed) { 132 return; 133 } 134 135 // Notify of the new data so the app can switch out the old data before 136 // we try to destroy it. 137 mData = data; 138 if (mCallbacks != null) { 139 mCallbacks.onLoadFinished(loader, data); 140 } 141 142 // Look for an inactive loader and destroy it if found 143 LoaderInfo info = mInactiveLoaders.get(mId); 144 if (info != null) { 145 Loader<Object> oldLoader = info.mLoader; 146 if (oldLoader != null) { 147 oldLoader.unregisterListener(info); 148 oldLoader.destroy(); 149 } 150 mInactiveLoaders.remove(mId); 151 } 152 } 153 } 154 155 LoaderManager(boolean started) { 156 mStarted = started; 157 } 158 159 private LoaderInfo createLoader(int id, Bundle args, 160 LoaderManager.LoaderCallbacks<Object> callback) { 161 LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 162 mLoaders.put(id, info); 163 Loader<Object> loader = callback.onCreateLoader(id, args); 164 info.mLoader = (Loader<Object>)loader; 165 if (mStarted) { 166 // The activity will start all existing loaders in it's onStart(), so only start them 167 // here if we're past that point of the activitiy's life cycle 168 loader.registerListener(id, info); 169 loader.startLoading(); 170 } 171 return info; 172 } 173 174 /** 175 * Ensures a loader is initialized an active. If the loader doesn't 176 * already exist, one is created and started. Otherwise the last created 177 * loader is re-used. 178 * 179 * <p>In either case, the given callback is associated with the loader, and 180 * will be called as the loader state changes. If at the point of call 181 * the caller is in its started state, and the requested loader 182 * already exists and has generated its data, then 183 * callback. {@link LoaderCallbacks#onLoadFinished} will 184 * be called immediately (inside of this function), so you must be prepared 185 * for this to happen. 186 */ 187 @SuppressWarnings("unchecked") 188 public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { 189 LoaderInfo info = mLoaders.get(id); 190 191 if (info == null) { 192 // Loader doesn't already exist; create. 193 info = createLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 194 } else { 195 info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback; 196 } 197 198 if (info.mData != null && mStarted) { 199 // If the loader has already generated its data, report it now. 200 info.mCallbacks.onLoadFinished(info.mLoader, info.mData); 201 } 202 203 return (Loader<D>)info.mLoader; 204 } 205 206 /** 207 * Create a new loader in this manager, registers the callbacks to it, 208 * and starts it loading. If a loader with the same id has previously been 209 * started it will automatically be destroyed when the new loader completes 210 * its work. The callback will be delivered before the old loader 211 * is destroyed. 212 */ 213 @SuppressWarnings("unchecked") 214 public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { 215 LoaderInfo info = mLoaders.get(id); 216 if (info != null) { 217 if (mInactiveLoaders.get(id) != null) { 218 // We already have an inactive loader for this ID that we are 219 // waiting for! Now we have three active loaders... let's just 220 // drop the one in the middle, since we are still waiting for 221 // its result but that result is already out of date. 222 info.destroy(); 223 } else { 224 // Keep track of the previous instance of this loader so we can destroy 225 // it when the new one completes. 226 mInactiveLoaders.put(id, info); 227 } 228 } 229 230 info = createLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 231 return (Loader<D>)info.mLoader; 232 } 233 234 /** 235 * Stops and removes the loader with the given ID. 236 */ 237 public void stopLoader(int id) { 238 int idx = mLoaders.indexOfKey(id); 239 if (idx >= 0) { 240 LoaderInfo info = mLoaders.valueAt(idx); 241 mLoaders.removeAt(idx); 242 Loader<Object> loader = info.mLoader; 243 if (loader != null) { 244 loader.unregisterListener(info); 245 loader.destroy(); 246 } 247 } 248 } 249 250 /** 251 * Return the Loader with the given id or null if no matching Loader 252 * is found. 253 */ 254 @SuppressWarnings("unchecked") 255 public <D> Loader<D> getLoader(int id) { 256 LoaderInfo loaderInfo = mLoaders.get(id); 257 if (loaderInfo != null) { 258 return (Loader<D>)mLoaders.get(id).mLoader; 259 } 260 return null; 261 } 262 263 void doStart() { 264 // Call out to sub classes so they can start their loaders 265 // Let the existing loaders know that we want to be notified when a load is complete 266 for (int i = mLoaders.size()-1; i >= 0; i--) { 267 mLoaders.valueAt(i).start(); 268 } 269 mStarted = true; 270 } 271 272 void doStop() { 273 for (int i = mLoaders.size()-1; i >= 0; i--) { 274 mLoaders.valueAt(i).stop(); 275 } 276 mStarted = false; 277 } 278 279 void doRetain() { 280 mRetaining = true; 281 mStarted = false; 282 for (int i = mLoaders.size()-1; i >= 0; i--) { 283 mLoaders.valueAt(i).retain(); 284 } 285 } 286 287 void finishRetain() { 288 mRetaining = false; 289 for (int i = mLoaders.size()-1; i >= 0; i--) { 290 mLoaders.valueAt(i).finishRetain(); 291 } 292 } 293 294 void doDestroy() { 295 if (!mRetaining) { 296 for (int i = mLoaders.size()-1; i >= 0; i--) { 297 mLoaders.valueAt(i).destroy(); 298 } 299 } 300 301 for (int i = mInactiveLoaders.size()-1; i >= 0; i--) { 302 mInactiveLoaders.valueAt(i).destroy(); 303 } 304 mInactiveLoaders.clear(); 305 } 306} 307