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