1 package com.bumptech.glide.request; 2 3 import android.content.Context; 4 import android.graphics.drawable.Drawable; 5 import android.util.Log; 6 7 import com.bumptech.glide.Priority; 8 import com.bumptech.glide.load.Key; 9 import com.bumptech.glide.load.Transformation; 10 import com.bumptech.glide.load.data.DataFetcher; 11 import com.bumptech.glide.load.engine.DiskCacheStrategy; 12 import com.bumptech.glide.load.engine.Engine; 13 import com.bumptech.glide.load.engine.Resource; 14 import com.bumptech.glide.load.model.ModelLoader; 15 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; 16 import com.bumptech.glide.provider.LoadProvider; 17 import com.bumptech.glide.request.animation.GlideAnimation; 18 import com.bumptech.glide.request.animation.GlideAnimationFactory; 19 import com.bumptech.glide.request.target.SizeReadyCallback; 20 import com.bumptech.glide.request.target.Target; 21 import com.bumptech.glide.util.LogTime; 22 import com.bumptech.glide.util.Util; 23 24 import java.util.Queue; 25 26 /** 27 * A {@link Request} that loads a {@link com.bumptech.glide.load.engine.Resource} into a given {@link Target}. 28 * 29 * @param <A> The type of the model that the resource will be loaded from. 30 * @param <T> The type of the data that the resource will be loaded from. 31 * @param <Z> The type of the resource that will be loaded. 32 * @param <R> The type of the resource that will be transcoded from the loaded resource. 33 */ 34 public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback, 35 ResourceCallback { 36 private static final String TAG = "GenericRequest"; 37 private static final Queue<GenericRequest<?, ?, ?, ?>> REQUEST_POOL = Util.createQueue(0); 38 private static final double TO_MEGABYTE = 1d / (1024d * 1024d); 39 40 private enum Status { 41 /** Created but not yet running. */ 42 PENDING, 43 /** In the process of fetching media. */ 44 RUNNING, 45 /** Waiting for a callback given to the Target to be called to determine target dimensions. */ 46 WAITING_FOR_SIZE, 47 /** Finished loading media successfully. */ 48 COMPLETE, 49 /** Failed to load media. */ 50 FAILED, 51 /** Cancelled by the user, may not be restarted. */ 52 CANCELLED, 53 /** Temporarily paused by the system, may be restarted. */ 54 PAUSED, 55 } 56 57 private final String tag = String.valueOf(hashCode()); 58 59 private Key signature; 60 private int placeholderResourceId; 61 private int errorResourceId; 62 private Context context; 63 private Transformation<Z> transformation; 64 private LoadProvider<A, T, Z, R> loadProvider; 65 private RequestCoordinator requestCoordinator; 66 private A model; 67 private Class<R> transcodeClass; 68 private boolean isMemoryCacheable; 69 private Priority priority; 70 private Target<R> target; 71 private RequestListener<? super A, R> requestListener; 72 private float sizeMultiplier; 73 private Engine engine; 74 private GlideAnimationFactory<R> animationFactory; 75 private int overrideWidth; 76 private int overrideHeight; 77 private DiskCacheStrategy diskCacheStrategy; 78 79 private Drawable placeholderDrawable; 80 private Drawable errorDrawable; 81 private boolean loadedFromMemoryCache; 82 // doing our own type check 83 private Resource<?> resource; 84 private Engine.LoadStatus loadStatus; 85 private long startTime; 86 private Status status; 87 88 public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain( 89 LoadProvider<A, T, Z, R> loadProvider, 90 A model, 91 Key signature, 92 Context context, 93 Priority priority, 94 Target<R> target, 95 float sizeMultiplier, 96 Drawable placeholderDrawable, 97 int placeholderResourceId, 98 Drawable errorDrawable, 99 int errorResourceId, 100 RequestListener<? super A, R> requestListener, 101 RequestCoordinator requestCoordinator, 102 Engine engine, 103 Transformation<Z> transformation, 104 Class<R> transcodeClass, 105 boolean isMemoryCacheable, 106 GlideAnimationFactory<R> animationFactory, 107 int overrideWidth, 108 int overrideHeight, 109 DiskCacheStrategy diskCacheStrategy) { 110 @SuppressWarnings("unchecked") 111 GenericRequest<A, T, Z, R> request = (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll(); 112 if (request == null) { 113 request = new GenericRequest<A, T, Z, R>(); 114 } 115 request.init(loadProvider, 116 model, 117 signature, 118 context, 119 priority, 120 target, 121 sizeMultiplier, 122 placeholderDrawable, 123 placeholderResourceId, 124 errorDrawable, 125 errorResourceId, 126 requestListener, 127 requestCoordinator, 128 engine, 129 transformation, 130 transcodeClass, 131 isMemoryCacheable, 132 animationFactory, 133 overrideWidth, 134 overrideHeight, 135 diskCacheStrategy); 136 return request; 137 } 138 139 private GenericRequest() { 140 // just create, instances are reused with recycle/init 141 } 142 143 @Override 144 public void recycle() { 145 loadProvider = null; 146 model = null; 147 context = null; 148 target = null; 149 placeholderDrawable = null; 150 errorDrawable = null; 151 requestListener = null; 152 requestCoordinator = null; 153 transformation = null; 154 animationFactory = null; 155 loadedFromMemoryCache = false; 156 loadStatus = null; 157 REQUEST_POOL.offer(this); 158 } 159 160 private void init( 161 LoadProvider<A, T, Z, R> loadProvider, 162 A model, 163 Key signature, 164 Context context, 165 Priority priority, 166 Target<R> target, 167 float sizeMultiplier, 168 Drawable placeholderDrawable, 169 int placeholderResourceId, 170 Drawable errorDrawable, 171 int errorResourceId, 172 RequestListener<? super A, R> requestListener, 173 RequestCoordinator requestCoordinator, 174 Engine engine, 175 Transformation<Z> transformation, 176 Class<R> transcodeClass, 177 boolean isMemoryCacheable, 178 GlideAnimationFactory<R> animationFactory, 179 int overrideWidth, 180 int overrideHeight, 181 DiskCacheStrategy diskCacheStrategy) { 182 this.loadProvider = loadProvider; 183 this.model = model; 184 this.signature = signature; 185 this.context = context.getApplicationContext(); 186 this.priority = priority; 187 this.target = target; 188 this.sizeMultiplier = sizeMultiplier; 189 this.placeholderDrawable = placeholderDrawable; 190 this.placeholderResourceId = placeholderResourceId; 191 this.errorDrawable = errorDrawable; 192 this.errorResourceId = errorResourceId; 193 this.requestListener = requestListener; 194 this.requestCoordinator = requestCoordinator; 195 this.engine = engine; 196 this.transformation = transformation; 197 this.transcodeClass = transcodeClass; 198 this.isMemoryCacheable = isMemoryCacheable; 199 this.animationFactory = animationFactory; 200 this.overrideWidth = overrideWidth; 201 this.overrideHeight = overrideHeight; 202 this.diskCacheStrategy = diskCacheStrategy; 203 status = Status.PENDING; 204 205 // We allow null models by just setting an error drawable. Null models will always have empty providers, we 206 // simply skip our sanity checks in that unusual case. 207 if (model != null) { 208 check("ModelLoader", loadProvider.getModelLoader(), "try .using(ModelLoader)"); 209 check("Transcoder", loadProvider.getTranscoder(), "try .as*(Class).transcode(ResourceTranscoder)"); 210 check("Transformation", transformation, "try .transform(UnitTransformation.get())"); 211 if (diskCacheStrategy.cacheSource()) { 212 check("SourceEncoder", loadProvider.getSourceEncoder(), 213 "try .sourceEncoder(Encoder) or .diskCacheStrategy(NONE/RESULT)"); 214 } else { 215 check("SourceDecoder", loadProvider.getSourceDecoder(), 216 "try .decoder/.imageDecoder/.videoDecoder(ResourceDecoder) or .diskCacheStrategy(ALL/SOURCE)"); 217 } 218 if (diskCacheStrategy.cacheSource() || diskCacheStrategy.cacheResult()) { 219 // TODO if(resourceClass.isAssignableFrom(InputStream.class) it is possible to wrap sourceDecoder 220 // and use it instead of cacheDecoder: new FileToStreamDecoder<Z>(sourceDecoder) 221 // in that case this shouldn't throw 222 check("CacheDecoder", loadProvider.getCacheDecoder(), 223 "try .cacheDecoder(ResouceDecoder) or .diskCacheStrategy(NONE)"); 224 } 225 if (diskCacheStrategy.cacheResult()) { 226 check("Encoder", loadProvider.getEncoder(), 227 "try .encode(ResourceEncoder) or .diskCacheStrategy(NONE/SOURCE)"); 228 } 229 } 230 } 231 232 private static void check(String name, Object object, String suggestion) { 233 if (object == null) { 234 StringBuilder message = new StringBuilder(name); 235 message.append(" must not be null"); 236 if (suggestion != null) { 237 message.append(", "); 238 message.append(suggestion); 239 } 240 throw new NullPointerException(message.toString()); 241 } 242 } 243 244 /** 245 * {@inheritDoc} 246 */ 247 @Override 248 public void begin() { 249 startTime = LogTime.getLogTime(); 250 if (model == null) { 251 onException(null); 252 return; 253 } 254 255 status = Status.WAITING_FOR_SIZE; 256 if (overrideWidth > 0 && overrideHeight > 0) { 257 onSizeReady(overrideWidth, overrideHeight); 258 } else { 259 target.getSize(this); 260 } 261 262 if (!isComplete() && !isFailed() && canNotifyStatusChanged()) { 263 target.onLoadStarted(getPlaceholderDrawable()); 264 } 265 if (Log.isLoggable(TAG, Log.VERBOSE)) { 266 logV("finished run method in " + LogTime.getElapsedMillis(startTime)); 267 } 268 } 269 270 /** 271 * Cancels the current load but does not release any resources held by the request and continues to display 272 * the loaded resource if the load completed before the call to cancel. 273 * 274 * <p> 275 * Cancelled requests can be restarted with a subsequent call to {@link #begin()}. 276 * </p> 277 * 278 * @see #clear() 279 */ 280 void cancel() { 281 status = Status.CANCELLED; 282 if (loadStatus != null) { 283 loadStatus.cancel(); 284 loadStatus = null; 285 } 286 } 287 288 /** 289 * Cancels the current load if it is in progress, clears any resources held onto by the request and replaces 290 * the loaded resource if the load completed with the placeholder. 291 * 292 * <p> 293 * Cleared requests can be restarted with a subsequent call to {@link #begin()} 294 * </p> 295 * 296 * @see #cancel() 297 */ 298 @Override 299 public void clear() { 300 Util.assertMainThread(); 301 cancel(); 302 // Resource must be released before canNotifyStatusChanged is called. 303 if (resource != null) { 304 releaseResource(resource); 305 } 306 if (canNotifyStatusChanged()) { 307 target.onLoadCleared(getPlaceholderDrawable()); 308 } 309 } 310 311 @Override 312 public boolean isPaused() { 313 return status == Status.PAUSED; 314 } 315 316 @Override 317 public void pause() { 318 clear(); 319 status = Status.PAUSED; 320 } 321 322 private void releaseResource(Resource resource) { 323 engine.release(resource); 324 this.resource = null; 325 } 326 327 /** 328 * {@inheritDoc} 329 */ 330 @Override 331 public boolean isRunning() { 332 return status == Status.RUNNING || status == Status.WAITING_FOR_SIZE; 333 } 334 335 /** 336 * {@inheritDoc} 337 */ 338 @Override 339 public boolean isComplete() { 340 return status == Status.COMPLETE; 341 } 342 343 /** 344 * {@inheritDoc} 345 */ 346 @Override 347 public boolean isResourceSet() { 348 return isComplete(); 349 } 350 351 /** 352 * {@inheritDoc} 353 */ 354 @Override 355 public boolean isCancelled() { 356 return status == Status.CANCELLED; 357 } 358 359 /** 360 * {@inheritDoc} 361 */ 362 @Override 363 public boolean isFailed() { 364 return status == Status.FAILED; 365 } 366 367 private void setErrorPlaceholder(Exception e) { 368 if (!canNotifyStatusChanged()) { 369 return; 370 } 371 372 Drawable error = getErrorDrawable(); 373 if (error == null) { 374 error = getPlaceholderDrawable(); 375 } 376 target.onLoadFailed(e, error); 377 } 378 379 private Drawable getErrorDrawable() { 380 if (errorDrawable == null && errorResourceId > 0) { 381 errorDrawable = context.getResources().getDrawable(errorResourceId); 382 } 383 return errorDrawable; 384 } 385 386 private Drawable getPlaceholderDrawable() { 387 if (placeholderDrawable == null && placeholderResourceId > 0) { 388 placeholderDrawable = context.getResources().getDrawable(placeholderResourceId); 389 } 390 return placeholderDrawable; 391 } 392 393 /** 394 * A callback method that should never be invoked directly. 395 */ 396 @Override 397 public void onSizeReady(int width, int height) { 398 if (Log.isLoggable(TAG, Log.VERBOSE)) { 399 logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime)); 400 } 401 if (status != Status.WAITING_FOR_SIZE) { 402 return; 403 } 404 status = Status.RUNNING; 405 406 width = Math.round(sizeMultiplier * width); 407 height = Math.round(sizeMultiplier * height); 408 409 ModelLoader<A, T> modelLoader = loadProvider.getModelLoader(); 410 final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height); 411 412 if (dataFetcher == null) { 413 onException(new Exception("Got null fetcher from model loader")); 414 return; 415 } 416 ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder(); 417 if (Log.isLoggable(TAG, Log.VERBOSE)) { 418 logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime)); 419 } 420 loadedFromMemoryCache = true; 421 loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder, 422 priority, isMemoryCacheable, diskCacheStrategy, this); 423 loadedFromMemoryCache = resource != null; 424 if (Log.isLoggable(TAG, Log.VERBOSE)) { 425 logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime)); 426 } 427 } 428 429 private boolean canSetResource() { 430 return requestCoordinator == null || requestCoordinator.canSetImage(this); 431 } 432 433 private boolean canNotifyStatusChanged() { 434 return requestCoordinator == null || requestCoordinator.canNotifyStatusChanged(this); 435 } 436 437 private boolean isFirstReadyResource() { 438 return requestCoordinator == null || !requestCoordinator.isAnyResourceSet(); 439 } 440 441 /** 442 * A callback method that should never be invoked directly. 443 */ 444 @SuppressWarnings("unchecked") 445 @Override 446 public void onResourceReady(Resource<?> resource) { 447 if (resource == null) { 448 onException(new Exception("Expected to receive a Resource<R> with an object of " + transcodeClass 449 + " inside, but instead got null.")); 450 return; 451 } 452 453 Object received = resource.get(); 454 if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) { 455 releaseResource(resource); 456 onException(new Exception("Expected to receive an object of " + transcodeClass 457 + " but instead got " + (received != null ? received.getClass() : "") + "{" + received + "}" 458 + " inside Resource{" + resource + "}." 459 + (received != null ? "" : " " 460 + "To indicate failure return a null Resource object, " 461 + "rather than a Resource object containing null data.") 462 )); 463 return; 464 } 465 466 if (!canSetResource()) { 467 releaseResource(resource); 468 // We can't set the status to complete before asking canSetResource(). 469 status = Status.COMPLETE; 470 return; 471 } 472 473 onResourceReady(resource, (R) received); 474 } 475 476 /** 477 * Internal {@link #onResourceReady(Resource)} where arguments are known to be safe. 478 * 479 * @param resource original {@link Resource}, never <code>null</code> 480 * @param result object returned by {@link Resource#get()}, checked for type and never <code>null</code> 481 */ 482 private void onResourceReady(Resource<?> resource, R result) { 483 if (requestListener == null || !requestListener.onResourceReady(result, model, target, loadedFromMemoryCache, 484 isFirstReadyResource())) { 485 GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstReadyResource()); 486 target.onResourceReady(result, animation); 487 } 488 489 status = Status.COMPLETE; 490 this.resource = resource; 491 492 if (Log.isLoggable(TAG, Log.VERBOSE)) { 493 logV("Resource ready in " + LogTime.getElapsedMillis(startTime) + " size: " 494 + (resource.getSize() * TO_MEGABYTE) + " fromCache: " + loadedFromMemoryCache); 495 } 496 } 497 498 /** 499 * A callback method that should never be invoked directly. 500 */ 501 @Override 502 public void onException(Exception e) { 503 if (Log.isLoggable(TAG, Log.DEBUG)) { 504 Log.d(TAG, "load failed", e); 505 } 506 507 status = Status.FAILED; 508 //TODO: what if this is a thumbnail request? 509 if (requestListener == null || !requestListener.onException(e, model, target, isFirstReadyResource())) { 510 setErrorPlaceholder(e); 511 } 512 } 513 514 private void logV(String message) { 515 Log.v(TAG, message + " this: " + tag); 516 } 517 } 518