Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* |
michael@0 | 2 | * Copyright (C) 2013 Square, Inc. |
michael@0 | 3 | * |
michael@0 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
michael@0 | 5 | * you may not use this file except in compliance with the License. |
michael@0 | 6 | * You may obtain a copy of the License at |
michael@0 | 7 | * |
michael@0 | 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 9 | * |
michael@0 | 10 | * Unless required by applicable law or agreed to in writing, software |
michael@0 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
michael@0 | 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
michael@0 | 13 | * See the License for the specific language governing permissions and |
michael@0 | 14 | * limitations under the License. |
michael@0 | 15 | */ |
michael@0 | 16 | package com.squareup.picasso; |
michael@0 | 17 | |
michael@0 | 18 | import android.content.Context; |
michael@0 | 19 | import android.graphics.Bitmap; |
michael@0 | 20 | import android.graphics.Color; |
michael@0 | 21 | import android.net.Uri; |
michael@0 | 22 | import android.os.Handler; |
michael@0 | 23 | import android.os.Looper; |
michael@0 | 24 | import android.os.Message; |
michael@0 | 25 | import android.os.Process; |
michael@0 | 26 | import android.widget.ImageView; |
michael@0 | 27 | import java.io.File; |
michael@0 | 28 | import java.lang.ref.ReferenceQueue; |
michael@0 | 29 | import java.util.List; |
michael@0 | 30 | import java.util.Map; |
michael@0 | 31 | import java.util.WeakHashMap; |
michael@0 | 32 | import java.util.concurrent.ExecutorService; |
michael@0 | 33 | |
michael@0 | 34 | import static android.os.Process.THREAD_PRIORITY_BACKGROUND; |
michael@0 | 35 | import static com.squareup.picasso.Action.RequestWeakReference; |
michael@0 | 36 | import static com.squareup.picasso.Dispatcher.HUNTER_BATCH_COMPLETE; |
michael@0 | 37 | import static com.squareup.picasso.Dispatcher.REQUEST_GCED; |
michael@0 | 38 | import static com.squareup.picasso.Utils.THREAD_PREFIX; |
michael@0 | 39 | |
michael@0 | 40 | /** |
michael@0 | 41 | * Image downloading, transformation, and caching manager. |
michael@0 | 42 | * <p/> |
michael@0 | 43 | * Use {@link #with(android.content.Context)} for the global singleton instance or construct your |
michael@0 | 44 | * own instance with {@link Builder}. |
michael@0 | 45 | */ |
michael@0 | 46 | public class Picasso { |
michael@0 | 47 | |
michael@0 | 48 | /** Callbacks for Picasso events. */ |
michael@0 | 49 | public interface Listener { |
michael@0 | 50 | /** |
michael@0 | 51 | * Invoked when an image has failed to load. This is useful for reporting image failures to a |
michael@0 | 52 | * remote analytics service, for example. |
michael@0 | 53 | */ |
michael@0 | 54 | void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception); |
michael@0 | 55 | } |
michael@0 | 56 | |
michael@0 | 57 | /** |
michael@0 | 58 | * A transformer that is called immediately before every request is submitted. This can be used to |
michael@0 | 59 | * modify any information about a request. |
michael@0 | 60 | * <p> |
michael@0 | 61 | * For example, if you use a CDN you can change the hostname for the image based on the current |
michael@0 | 62 | * location of the user in order to get faster download speeds. |
michael@0 | 63 | * <p> |
michael@0 | 64 | * <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible |
michael@0 | 65 | * way at any time. |
michael@0 | 66 | */ |
michael@0 | 67 | public interface RequestTransformer { |
michael@0 | 68 | /** |
michael@0 | 69 | * Transform a request before it is submitted to be processed. |
michael@0 | 70 | * |
michael@0 | 71 | * @return The original request or a new request to replace it. Must not be null. |
michael@0 | 72 | */ |
michael@0 | 73 | Request transformRequest(Request request); |
michael@0 | 74 | |
michael@0 | 75 | /** A {@link RequestTransformer} which returns the original request. */ |
michael@0 | 76 | RequestTransformer IDENTITY = new RequestTransformer() { |
michael@0 | 77 | @Override public Request transformRequest(Request request) { |
michael@0 | 78 | return request; |
michael@0 | 79 | } |
michael@0 | 80 | }; |
michael@0 | 81 | } |
michael@0 | 82 | |
michael@0 | 83 | static final Handler HANDLER = new Handler(Looper.getMainLooper()) { |
michael@0 | 84 | @Override public void handleMessage(Message msg) { |
michael@0 | 85 | switch (msg.what) { |
michael@0 | 86 | case HUNTER_BATCH_COMPLETE: { |
michael@0 | 87 | @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj; |
michael@0 | 88 | for (BitmapHunter hunter : batch) { |
michael@0 | 89 | hunter.picasso.complete(hunter); |
michael@0 | 90 | } |
michael@0 | 91 | break; |
michael@0 | 92 | } |
michael@0 | 93 | case REQUEST_GCED: { |
michael@0 | 94 | Action action = (Action) msg.obj; |
michael@0 | 95 | action.picasso.cancelExistingRequest(action.getTarget()); |
michael@0 | 96 | break; |
michael@0 | 97 | } |
michael@0 | 98 | default: |
michael@0 | 99 | throw new AssertionError("Unknown handler message received: " + msg.what); |
michael@0 | 100 | } |
michael@0 | 101 | } |
michael@0 | 102 | }; |
michael@0 | 103 | |
michael@0 | 104 | static Picasso singleton = null; |
michael@0 | 105 | |
michael@0 | 106 | private final Listener listener; |
michael@0 | 107 | private final RequestTransformer requestTransformer; |
michael@0 | 108 | private final CleanupThread cleanupThread; |
michael@0 | 109 | |
michael@0 | 110 | final Context context; |
michael@0 | 111 | final Dispatcher dispatcher; |
michael@0 | 112 | final Cache cache; |
michael@0 | 113 | final Stats stats; |
michael@0 | 114 | final Map<Object, Action> targetToAction; |
michael@0 | 115 | final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator; |
michael@0 | 116 | final ReferenceQueue<Object> referenceQueue; |
michael@0 | 117 | |
michael@0 | 118 | boolean debugging; |
michael@0 | 119 | boolean shutdown; |
michael@0 | 120 | |
michael@0 | 121 | Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener, |
michael@0 | 122 | RequestTransformer requestTransformer, Stats stats, boolean debugging) { |
michael@0 | 123 | this.context = context; |
michael@0 | 124 | this.dispatcher = dispatcher; |
michael@0 | 125 | this.cache = cache; |
michael@0 | 126 | this.listener = listener; |
michael@0 | 127 | this.requestTransformer = requestTransformer; |
michael@0 | 128 | this.stats = stats; |
michael@0 | 129 | this.targetToAction = new WeakHashMap<Object, Action>(); |
michael@0 | 130 | this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>(); |
michael@0 | 131 | this.debugging = debugging; |
michael@0 | 132 | this.referenceQueue = new ReferenceQueue<Object>(); |
michael@0 | 133 | this.cleanupThread = new CleanupThread(referenceQueue, HANDLER); |
michael@0 | 134 | this.cleanupThread.start(); |
michael@0 | 135 | } |
michael@0 | 136 | |
michael@0 | 137 | /** Cancel any existing requests for the specified target {@link ImageView}. */ |
michael@0 | 138 | public void cancelRequest(ImageView view) { |
michael@0 | 139 | cancelExistingRequest(view); |
michael@0 | 140 | } |
michael@0 | 141 | |
michael@0 | 142 | /** Cancel any existing requests for the specified {@link Target} instance. */ |
michael@0 | 143 | public void cancelRequest(Target target) { |
michael@0 | 144 | cancelExistingRequest(target); |
michael@0 | 145 | } |
michael@0 | 146 | |
michael@0 | 147 | /** |
michael@0 | 148 | * Start an image request using the specified URI. |
michael@0 | 149 | * <p> |
michael@0 | 150 | * Passing {@code null} as a {@code uri} will not trigger any request but will set a placeholder, |
michael@0 | 151 | * if one is specified. |
michael@0 | 152 | * |
michael@0 | 153 | * @see #load(File) |
michael@0 | 154 | * @see #load(String) |
michael@0 | 155 | * @see #load(int) |
michael@0 | 156 | */ |
michael@0 | 157 | public RequestCreator load(Uri uri) { |
michael@0 | 158 | return new RequestCreator(this, uri, 0); |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | /** |
michael@0 | 162 | * Start an image request using the specified path. This is a convenience method for calling |
michael@0 | 163 | * {@link #load(Uri)}. |
michael@0 | 164 | * <p> |
michael@0 | 165 | * This path may be a remote URL, file resource (prefixed with {@code file:}), content resource |
michael@0 | 166 | * (prefixed with {@code content:}), or android resource (prefixed with {@code |
michael@0 | 167 | * android.resource:}. |
michael@0 | 168 | * <p> |
michael@0 | 169 | * Passing {@code null} as a {@code path} will not trigger any request but will set a |
michael@0 | 170 | * placeholder, if one is specified. |
michael@0 | 171 | * |
michael@0 | 172 | * @see #load(Uri) |
michael@0 | 173 | * @see #load(File) |
michael@0 | 174 | * @see #load(int) |
michael@0 | 175 | */ |
michael@0 | 176 | public RequestCreator load(String path) { |
michael@0 | 177 | if (path == null) { |
michael@0 | 178 | return new RequestCreator(this, null, 0); |
michael@0 | 179 | } |
michael@0 | 180 | if (path.trim().length() == 0) { |
michael@0 | 181 | throw new IllegalArgumentException("Path must not be empty."); |
michael@0 | 182 | } |
michael@0 | 183 | return load(Uri.parse(path)); |
michael@0 | 184 | } |
michael@0 | 185 | |
michael@0 | 186 | /** |
michael@0 | 187 | * Start an image request using the specified image file. This is a convenience method for |
michael@0 | 188 | * calling {@link #load(Uri)}. |
michael@0 | 189 | * <p> |
michael@0 | 190 | * Passing {@code null} as a {@code file} will not trigger any request but will set a |
michael@0 | 191 | * placeholder, if one is specified. |
michael@0 | 192 | * |
michael@0 | 193 | * @see #load(Uri) |
michael@0 | 194 | * @see #load(String) |
michael@0 | 195 | * @see #load(int) |
michael@0 | 196 | */ |
michael@0 | 197 | public RequestCreator load(File file) { |
michael@0 | 198 | if (file == null) { |
michael@0 | 199 | return new RequestCreator(this, null, 0); |
michael@0 | 200 | } |
michael@0 | 201 | return load(Uri.fromFile(file)); |
michael@0 | 202 | } |
michael@0 | 203 | |
michael@0 | 204 | /** |
michael@0 | 205 | * Start an image request using the specified drawable resource ID. |
michael@0 | 206 | * |
michael@0 | 207 | * @see #load(Uri) |
michael@0 | 208 | * @see #load(String) |
michael@0 | 209 | * @see #load(File) |
michael@0 | 210 | */ |
michael@0 | 211 | public RequestCreator load(int resourceId) { |
michael@0 | 212 | if (resourceId == 0) { |
michael@0 | 213 | throw new IllegalArgumentException("Resource ID must not be zero."); |
michael@0 | 214 | } |
michael@0 | 215 | return new RequestCreator(this, null, resourceId); |
michael@0 | 216 | } |
michael@0 | 217 | |
michael@0 | 218 | /** {@code true} if debug display, logging, and statistics are enabled. */ |
michael@0 | 219 | @SuppressWarnings("UnusedDeclaration") public boolean isDebugging() { |
michael@0 | 220 | return debugging; |
michael@0 | 221 | } |
michael@0 | 222 | |
michael@0 | 223 | /** Toggle whether debug display, logging, and statistics are enabled. */ |
michael@0 | 224 | @SuppressWarnings("UnusedDeclaration") public void setDebugging(boolean debugging) { |
michael@0 | 225 | this.debugging = debugging; |
michael@0 | 226 | } |
michael@0 | 227 | |
michael@0 | 228 | /** Creates a {@link StatsSnapshot} of the current stats for this instance. */ |
michael@0 | 229 | @SuppressWarnings("UnusedDeclaration") public StatsSnapshot getSnapshot() { |
michael@0 | 230 | return stats.createSnapshot(); |
michael@0 | 231 | } |
michael@0 | 232 | |
michael@0 | 233 | /** Stops this instance from accepting further requests. */ |
michael@0 | 234 | public void shutdown() { |
michael@0 | 235 | if (this == singleton) { |
michael@0 | 236 | throw new UnsupportedOperationException("Default singleton instance cannot be shutdown."); |
michael@0 | 237 | } |
michael@0 | 238 | if (shutdown) { |
michael@0 | 239 | return; |
michael@0 | 240 | } |
michael@0 | 241 | cache.clear(); |
michael@0 | 242 | cleanupThread.shutdown(); |
michael@0 | 243 | stats.shutdown(); |
michael@0 | 244 | dispatcher.shutdown(); |
michael@0 | 245 | for (DeferredRequestCreator deferredRequestCreator : targetToDeferredRequestCreator.values()) { |
michael@0 | 246 | deferredRequestCreator.cancel(); |
michael@0 | 247 | } |
michael@0 | 248 | targetToDeferredRequestCreator.clear(); |
michael@0 | 249 | shutdown = true; |
michael@0 | 250 | } |
michael@0 | 251 | |
michael@0 | 252 | Request transformRequest(Request request) { |
michael@0 | 253 | Request transformed = requestTransformer.transformRequest(request); |
michael@0 | 254 | if (transformed == null) { |
michael@0 | 255 | throw new IllegalStateException("Request transformer " |
michael@0 | 256 | + requestTransformer.getClass().getCanonicalName() |
michael@0 | 257 | + " returned null for " |
michael@0 | 258 | + request); |
michael@0 | 259 | } |
michael@0 | 260 | return transformed; |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | void defer(ImageView view, DeferredRequestCreator request) { |
michael@0 | 264 | targetToDeferredRequestCreator.put(view, request); |
michael@0 | 265 | } |
michael@0 | 266 | |
michael@0 | 267 | void enqueueAndSubmit(Action action) { |
michael@0 | 268 | Object target = action.getTarget(); |
michael@0 | 269 | if (target != null) { |
michael@0 | 270 | cancelExistingRequest(target); |
michael@0 | 271 | targetToAction.put(target, action); |
michael@0 | 272 | } |
michael@0 | 273 | submit(action); |
michael@0 | 274 | } |
michael@0 | 275 | |
michael@0 | 276 | void submit(Action action) { |
michael@0 | 277 | dispatcher.dispatchSubmit(action); |
michael@0 | 278 | } |
michael@0 | 279 | |
michael@0 | 280 | Bitmap quickMemoryCacheCheck(String key) { |
michael@0 | 281 | Bitmap cached = cache.get(key); |
michael@0 | 282 | if (cached != null) { |
michael@0 | 283 | stats.dispatchCacheHit(); |
michael@0 | 284 | } else { |
michael@0 | 285 | stats.dispatchCacheMiss(); |
michael@0 | 286 | } |
michael@0 | 287 | return cached; |
michael@0 | 288 | } |
michael@0 | 289 | |
michael@0 | 290 | void complete(BitmapHunter hunter) { |
michael@0 | 291 | List<Action> joined = hunter.getActions(); |
michael@0 | 292 | if (joined.isEmpty()) { |
michael@0 | 293 | return; |
michael@0 | 294 | } |
michael@0 | 295 | |
michael@0 | 296 | Uri uri = hunter.getData().uri; |
michael@0 | 297 | Exception exception = hunter.getException(); |
michael@0 | 298 | Bitmap result = hunter.getResult(); |
michael@0 | 299 | LoadedFrom from = hunter.getLoadedFrom(); |
michael@0 | 300 | |
michael@0 | 301 | for (Action join : joined) { |
michael@0 | 302 | if (join.isCancelled()) { |
michael@0 | 303 | continue; |
michael@0 | 304 | } |
michael@0 | 305 | targetToAction.remove(join.getTarget()); |
michael@0 | 306 | if (result != null) { |
michael@0 | 307 | if (from == null) { |
michael@0 | 308 | throw new AssertionError("LoadedFrom cannot be null."); |
michael@0 | 309 | } |
michael@0 | 310 | join.complete(result, from); |
michael@0 | 311 | } else { |
michael@0 | 312 | join.error(); |
michael@0 | 313 | } |
michael@0 | 314 | } |
michael@0 | 315 | |
michael@0 | 316 | if (listener != null && exception != null) { |
michael@0 | 317 | listener.onImageLoadFailed(this, uri, exception); |
michael@0 | 318 | } |
michael@0 | 319 | } |
michael@0 | 320 | |
michael@0 | 321 | private void cancelExistingRequest(Object target) { |
michael@0 | 322 | Action action = targetToAction.remove(target); |
michael@0 | 323 | if (action != null) { |
michael@0 | 324 | action.cancel(); |
michael@0 | 325 | dispatcher.dispatchCancel(action); |
michael@0 | 326 | } |
michael@0 | 327 | if (target instanceof ImageView) { |
michael@0 | 328 | ImageView targetImageView = (ImageView) target; |
michael@0 | 329 | DeferredRequestCreator deferredRequestCreator = |
michael@0 | 330 | targetToDeferredRequestCreator.remove(targetImageView); |
michael@0 | 331 | if (deferredRequestCreator != null) { |
michael@0 | 332 | deferredRequestCreator.cancel(); |
michael@0 | 333 | } |
michael@0 | 334 | } |
michael@0 | 335 | } |
michael@0 | 336 | |
michael@0 | 337 | private static class CleanupThread extends Thread { |
michael@0 | 338 | private final ReferenceQueue<?> referenceQueue; |
michael@0 | 339 | private final Handler handler; |
michael@0 | 340 | |
michael@0 | 341 | CleanupThread(ReferenceQueue<?> referenceQueue, Handler handler) { |
michael@0 | 342 | this.referenceQueue = referenceQueue; |
michael@0 | 343 | this.handler = handler; |
michael@0 | 344 | setDaemon(true); |
michael@0 | 345 | setName(THREAD_PREFIX + "refQueue"); |
michael@0 | 346 | } |
michael@0 | 347 | |
michael@0 | 348 | @Override public void run() { |
michael@0 | 349 | Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND); |
michael@0 | 350 | while (true) { |
michael@0 | 351 | try { |
michael@0 | 352 | RequestWeakReference<?> remove = (RequestWeakReference<?>) referenceQueue.remove(); |
michael@0 | 353 | handler.sendMessage(handler.obtainMessage(REQUEST_GCED, remove.action)); |
michael@0 | 354 | } catch (InterruptedException e) { |
michael@0 | 355 | break; |
michael@0 | 356 | } catch (final Exception e) { |
michael@0 | 357 | handler.post(new Runnable() { |
michael@0 | 358 | @Override public void run() { |
michael@0 | 359 | throw new RuntimeException(e); |
michael@0 | 360 | } |
michael@0 | 361 | }); |
michael@0 | 362 | break; |
michael@0 | 363 | } |
michael@0 | 364 | } |
michael@0 | 365 | } |
michael@0 | 366 | |
michael@0 | 367 | void shutdown() { |
michael@0 | 368 | interrupt(); |
michael@0 | 369 | } |
michael@0 | 370 | } |
michael@0 | 371 | |
michael@0 | 372 | /** |
michael@0 | 373 | * The global default {@link Picasso} instance. |
michael@0 | 374 | * <p> |
michael@0 | 375 | * This instance is automatically initialized with defaults that are suitable to most |
michael@0 | 376 | * implementations. |
michael@0 | 377 | * <ul> |
michael@0 | 378 | * <li>LRU memory cache of 15% the available application RAM</li> |
michael@0 | 379 | * <li>Disk cache of 2% storage space up to 50MB but no less than 5MB. (Note: this is only |
michael@0 | 380 | * available on API 14+ <em>or</em> if you are using a standalone library that provides a disk |
michael@0 | 381 | * cache on all API levels like OkHttp)</li> |
michael@0 | 382 | * <li>Three download threads for disk and network access.</li> |
michael@0 | 383 | * </ul> |
michael@0 | 384 | * <p> |
michael@0 | 385 | * If these settings do not meet the requirements of your application you can construct your own |
michael@0 | 386 | * instance with full control over the configuration by using {@link Picasso.Builder}. |
michael@0 | 387 | */ |
michael@0 | 388 | public static Picasso with(Context context) { |
michael@0 | 389 | if (singleton == null) { |
michael@0 | 390 | singleton = new Builder(context).build(); |
michael@0 | 391 | } |
michael@0 | 392 | return singleton; |
michael@0 | 393 | } |
michael@0 | 394 | |
michael@0 | 395 | /** Fluent API for creating {@link Picasso} instances. */ |
michael@0 | 396 | @SuppressWarnings("UnusedDeclaration") // Public API. |
michael@0 | 397 | public static class Builder { |
michael@0 | 398 | private final Context context; |
michael@0 | 399 | private Downloader downloader; |
michael@0 | 400 | private ExecutorService service; |
michael@0 | 401 | private Cache cache; |
michael@0 | 402 | private Listener listener; |
michael@0 | 403 | private RequestTransformer transformer; |
michael@0 | 404 | private boolean debugging; |
michael@0 | 405 | |
michael@0 | 406 | /** Start building a new {@link Picasso} instance. */ |
michael@0 | 407 | public Builder(Context context) { |
michael@0 | 408 | if (context == null) { |
michael@0 | 409 | throw new IllegalArgumentException("Context must not be null."); |
michael@0 | 410 | } |
michael@0 | 411 | this.context = context.getApplicationContext(); |
michael@0 | 412 | } |
michael@0 | 413 | |
michael@0 | 414 | /** Specify the {@link Downloader} that will be used for downloading images. */ |
michael@0 | 415 | public Builder downloader(Downloader downloader) { |
michael@0 | 416 | if (downloader == null) { |
michael@0 | 417 | throw new IllegalArgumentException("Downloader must not be null."); |
michael@0 | 418 | } |
michael@0 | 419 | if (this.downloader != null) { |
michael@0 | 420 | throw new IllegalStateException("Downloader already set."); |
michael@0 | 421 | } |
michael@0 | 422 | this.downloader = downloader; |
michael@0 | 423 | return this; |
michael@0 | 424 | } |
michael@0 | 425 | |
michael@0 | 426 | /** Specify the executor service for loading images in the background. */ |
michael@0 | 427 | public Builder executor(ExecutorService executorService) { |
michael@0 | 428 | if (executorService == null) { |
michael@0 | 429 | throw new IllegalArgumentException("Executor service must not be null."); |
michael@0 | 430 | } |
michael@0 | 431 | if (this.service != null) { |
michael@0 | 432 | throw new IllegalStateException("Executor service already set."); |
michael@0 | 433 | } |
michael@0 | 434 | this.service = executorService; |
michael@0 | 435 | return this; |
michael@0 | 436 | } |
michael@0 | 437 | |
michael@0 | 438 | /** Specify the memory cache used for the most recent images. */ |
michael@0 | 439 | public Builder memoryCache(Cache memoryCache) { |
michael@0 | 440 | if (memoryCache == null) { |
michael@0 | 441 | throw new IllegalArgumentException("Memory cache must not be null."); |
michael@0 | 442 | } |
michael@0 | 443 | if (this.cache != null) { |
michael@0 | 444 | throw new IllegalStateException("Memory cache already set."); |
michael@0 | 445 | } |
michael@0 | 446 | this.cache = memoryCache; |
michael@0 | 447 | return this; |
michael@0 | 448 | } |
michael@0 | 449 | |
michael@0 | 450 | /** Specify a listener for interesting events. */ |
michael@0 | 451 | public Builder listener(Listener listener) { |
michael@0 | 452 | if (listener == null) { |
michael@0 | 453 | throw new IllegalArgumentException("Listener must not be null."); |
michael@0 | 454 | } |
michael@0 | 455 | if (this.listener != null) { |
michael@0 | 456 | throw new IllegalStateException("Listener already set."); |
michael@0 | 457 | } |
michael@0 | 458 | this.listener = listener; |
michael@0 | 459 | return this; |
michael@0 | 460 | } |
michael@0 | 461 | |
michael@0 | 462 | /** |
michael@0 | 463 | * Specify a transformer for all incoming requests. |
michael@0 | 464 | * <p> |
michael@0 | 465 | * <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible |
michael@0 | 466 | * way at any time. |
michael@0 | 467 | */ |
michael@0 | 468 | public Builder requestTransformer(RequestTransformer transformer) { |
michael@0 | 469 | if (transformer == null) { |
michael@0 | 470 | throw new IllegalArgumentException("Transformer must not be null."); |
michael@0 | 471 | } |
michael@0 | 472 | if (this.transformer != null) { |
michael@0 | 473 | throw new IllegalStateException("Transformer already set."); |
michael@0 | 474 | } |
michael@0 | 475 | this.transformer = transformer; |
michael@0 | 476 | return this; |
michael@0 | 477 | } |
michael@0 | 478 | |
michael@0 | 479 | /** Whether debugging is enabled or not. */ |
michael@0 | 480 | public Builder debugging(boolean debugging) { |
michael@0 | 481 | this.debugging = debugging; |
michael@0 | 482 | return this; |
michael@0 | 483 | } |
michael@0 | 484 | |
michael@0 | 485 | /** Create the {@link Picasso} instance. */ |
michael@0 | 486 | public Picasso build() { |
michael@0 | 487 | Context context = this.context; |
michael@0 | 488 | |
michael@0 | 489 | if (downloader == null) { |
michael@0 | 490 | downloader = Utils.createDefaultDownloader(context); |
michael@0 | 491 | } |
michael@0 | 492 | if (cache == null) { |
michael@0 | 493 | cache = new LruCache(context); |
michael@0 | 494 | } |
michael@0 | 495 | if (service == null) { |
michael@0 | 496 | service = new PicassoExecutorService(); |
michael@0 | 497 | } |
michael@0 | 498 | if (transformer == null) { |
michael@0 | 499 | transformer = RequestTransformer.IDENTITY; |
michael@0 | 500 | } |
michael@0 | 501 | |
michael@0 | 502 | Stats stats = new Stats(cache); |
michael@0 | 503 | |
michael@0 | 504 | Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats); |
michael@0 | 505 | |
michael@0 | 506 | return new Picasso(context, dispatcher, cache, listener, transformer, stats, debugging); |
michael@0 | 507 | } |
michael@0 | 508 | } |
michael@0 | 509 | |
michael@0 | 510 | /** Describes where the image was loaded from. */ |
michael@0 | 511 | public enum LoadedFrom { |
michael@0 | 512 | MEMORY(Color.GREEN), |
michael@0 | 513 | DISK(Color.YELLOW), |
michael@0 | 514 | NETWORK(Color.RED); |
michael@0 | 515 | |
michael@0 | 516 | final int debugColor; |
michael@0 | 517 | |
michael@0 | 518 | private LoadedFrom(int debugColor) { |
michael@0 | 519 | this.debugColor = debugColor; |
michael@0 | 520 | } |
michael@0 | 521 | } |
michael@0 | 522 | } |