LoaderManager.java revision f5100a7823a60f369a6c0014b1b80ca945128e4a
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 mLoader.stopLoading(); 176 } 177 } 178 179 void destroy() { 180 mDestroyed = true; 181 mCallbacks = null; 182 if (mLoader != null) { 183 if (mListenerRegistered) { 184 mListenerRegistered = false; 185 mLoader.unregisterListener(this); 186 } 187 mLoader.destroy(); 188 } 189 } 190 191 @Override public void onLoadComplete(Loader<Object> loader, Object data) { 192 if (mDestroyed) { 193 return; 194 } 195 196 // Notify of the new data so the app can switch out the old data before 197 // we try to destroy it. 198 mData = data; 199 if (mCallbacks != null) { 200 mCallbacks.onLoadFinished(loader, data); 201 } 202 203 // Look for an inactive loader and destroy it if found 204 LoaderInfo info = mInactiveLoaders.get(mId); 205 if (info != null) { 206 Loader<Object> oldLoader = info.mLoader; 207 if (oldLoader != null) { 208 if (info.mListenerRegistered) { 209 oldLoader.unregisterListener(info); 210 } 211 oldLoader.destroy(); 212 } 213 mInactiveLoaders.remove(mId); 214 } 215 } 216 } 217 218 LoaderManagerImpl(boolean started) { 219 mStarted = started; 220 } 221 222 private LoaderInfo createLoader(int id, Bundle args, 223 LoaderManager.LoaderCallbacks<Object> callback) { 224 LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 225 mLoaders.put(id, info); 226 Loader<Object> loader = callback.onCreateLoader(id, args); 227 info.mLoader = (Loader<Object>)loader; 228 if (mStarted) { 229 // The activity will start all existing loaders in it's onStart(), 230 // so only start them here if we're past that point of the activitiy's 231 // life cycle 232 info.start(); 233 } 234 return info; 235 } 236 237 @SuppressWarnings("unchecked") 238 public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { 239 LoaderInfo info = mLoaders.get(id); 240 241 if (info == null) { 242 // Loader doesn't already exist; create. 243 info = createLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 244 } else { 245 info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback; 246 } 247 248 if (info.mData != null && mStarted) { 249 // If the loader has already generated its data, report it now. 250 info.mCallbacks.onLoadFinished(info.mLoader, info.mData); 251 } 252 253 return (Loader<D>)info.mLoader; 254 } 255 256 @SuppressWarnings("unchecked") 257 public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { 258 LoaderInfo info = mLoaders.get(id); 259 if (info != null) { 260 if (mInactiveLoaders.get(id) != null) { 261 // We already have an inactive loader for this ID that we are 262 // waiting for! Now we have three active loaders... let's just 263 // drop the one in the middle, since we are still waiting for 264 // its result but that result is already out of date. 265 info.destroy(); 266 } else { 267 // Keep track of the previous instance of this loader so we can destroy 268 // it when the new one completes. 269 mInactiveLoaders.put(id, info); 270 } 271 } 272 273 info = createLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 274 return (Loader<D>)info.mLoader; 275 } 276 277 public void stopLoader(int id) { 278 int idx = mLoaders.indexOfKey(id); 279 if (idx >= 0) { 280 LoaderInfo info = mLoaders.valueAt(idx); 281 mLoaders.removeAt(idx); 282 info.destroy(); 283 } 284 } 285 286 @SuppressWarnings("unchecked") 287 public <D> Loader<D> getLoader(int id) { 288 LoaderInfo loaderInfo = mLoaders.get(id); 289 if (loaderInfo != null) { 290 return (Loader<D>)mLoaders.get(id).mLoader; 291 } 292 return null; 293 } 294 295 void doStart() { 296 // Call out to sub classes so they can start their loaders 297 // Let the existing loaders know that we want to be notified when a load is complete 298 for (int i = mLoaders.size()-1; i >= 0; i--) { 299 mLoaders.valueAt(i).start(); 300 } 301 mStarted = true; 302 } 303 304 void doStop() { 305 for (int i = mLoaders.size()-1; i >= 0; i--) { 306 mLoaders.valueAt(i).stop(); 307 } 308 mStarted = false; 309 } 310 311 void doRetain() { 312 mRetaining = true; 313 mStarted = false; 314 for (int i = mLoaders.size()-1; i >= 0; i--) { 315 mLoaders.valueAt(i).retain(); 316 } 317 } 318 319 void finishRetain() { 320 mRetaining = false; 321 for (int i = mLoaders.size()-1; i >= 0; i--) { 322 mLoaders.valueAt(i).finishRetain(); 323 } 324 } 325 326 void doDestroy() { 327 if (!mRetaining) { 328 for (int i = mLoaders.size()-1; i >= 0; i--) { 329 mLoaders.valueAt(i).destroy(); 330 } 331 } 332 333 for (int i = mInactiveLoaders.size()-1; i >= 0; i--) { 334 mInactiveLoaders.valueAt(i).destroy(); 335 } 336 mInactiveLoaders.clear(); 337 } 338} 339