@NonNull publicstatic RequestManager with(@NonNull FragmentActivity activity){ return getRetriever(activity).get(activity); } @NonNull publicstatic RequestManager with(@NonNull Fragment fragment){ return getRetriever(fragment.getContext()).get(fragment); } @Deprecated @NonNull publicstatic RequestManager with(@NonNull android.app.Fragment fragment){ return getRetriever(fragment.getActivity()).get(fragment); } @NonNull publicstatic RequestManager with(@NonNull View view){ return getRetriever(view.getContext()).get(view); } @NonNull privatestatic RequestManagerRetriever getRetriever(@Nullable Context context){ // Context could be null for other reasons (ie the user passes in null), but in practice it will // only occur due to errors with the Fragment lifecycle. Preconditions.checkNotNull( context, "You cannot start a load on a not yet attached View or a Fragment where getActivity() " + "returns null (which usually occurs when getActivity() is called before the Fragment " + "is attached or after the Fragment is destroyed)."); return Glide.get(context).getRequestManagerRetriever(); } // ... }
publicclassRequestManagerRetrieverimplementsHandler.Callback{ // ... @NonNull public RequestManager get(@NonNull Context context){ if (context == null) { thrownew IllegalArgumentException("You cannot start a load on a null Context"); } elseif (Util.isOnMainThread() && !(context instanceof Application)) { if (context instanceof FragmentActivity) { return get((FragmentActivity) context); } elseif (context instanceof Activity) { return get((Activity) context); } elseif (context instanceof ContextWrapper // Only unwrap a ContextWrapper if the baseContext has a non-null application context. // Context#createPackageContext may return a Context without an Application instance, // in which case a ContextWrapper may be used to attach one. && ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) { return get(((ContextWrapper) context).getBaseContext()); } }
return getApplicationManager(context); } @NonNull public RequestManager get(@NonNull FragmentActivity activity){ if (Util.isOnBackgroundThread()) { return get(activity.getApplicationContext()); } else { assertNotDestroyed(activity); FragmentManager fm = activity.getSupportFragmentManager(); return supportFragmentGet(activity, fm, /*parentHint=*/null, isActivityVisible(activity)); } }
@NonNull public RequestManager get(@NonNull Fragment fragment){ Preconditions.checkNotNull( fragment.getContext(), "You cannot start a load on a fragment before it is attached or after it is destroyed"); if (Util.isOnBackgroundThread()) { return get(fragment.getContext().getApplicationContext()); } else { FragmentManager fm = fragment.getChildFragmentManager(); return supportFragmentGet(fragment.getContext(), fm, fragment, fragment.isVisible()); } }
@SuppressWarnings("deprecation") @NonNull public RequestManager get(@NonNull Activity activity){ if (Util.isOnBackgroundThread()) { return get(activity.getApplicationContext()); } else { assertNotDestroyed(activity); android.app.FragmentManager fm = activity.getFragmentManager(); return fragmentGet(activity, fm, /*parentHint=*/null, isActivityVisible(activity)); } }
@SuppressWarnings("deprecation") @NonNull public RequestManager get(@NonNull View view){ if (Util.isOnBackgroundThread()) { return get(view.getContext().getApplicationContext()); }
Preconditions.checkNotNull(view); Preconditions.checkNotNull( view.getContext(), "Unable to obtain a request manager for a view without a Context"); Activity activity = findActivity(view.getContext()); // The view might be somewhere else, like a service. if (activity == null) { return get(view.getContext().getApplicationContext()); }
// Support Fragments. // Although the user might have non-support Fragments attached to FragmentActivity, searching // for non-support Fragments is so expensive pre O and that should be rare enough that we // prefer to just fall back to the Activity directly. if (activity instanceof FragmentActivity) { Fragment fragment = findSupportFragment(view, (FragmentActivity) activity); return fragment != null ? get(fragment) : get(activity); }
// Standard Fragments. android.app.Fragment fragment = findFragment(view, activity); if (fragment == null) { return get(activity); } return get(fragment); } // ... }
get(Context context) :
非主线程的话,当 Application 处理,Glide 加载图片的生命周期是和 with 里的参数保持一致,而对于 Application 类型,只有当应用程序被杀掉的时候,图片加载才会停止
BaseRequestOptions<?> requestOptions = this; if (!requestOptions.isTransformationSet() && requestOptions.isTransformationAllowed() && view.getScaleType() != null) { // Clone in this method so that if we use this RequestBuilder to load into a View and then // into a different target, we don't retain the transformation applied based on the previous // View's scale type. switch (view.getScaleType()) { case CENTER_CROP: requestOptions = requestOptions.clone().optionalCenterCrop(); break; case CENTER_INSIDE: requestOptions = requestOptions.clone().optionalCenterInside(); break; case FIT_CENTER: case FIT_START: case FIT_END: requestOptions = requestOptions.clone().optionalFitCenter(); break; case FIT_XY: requestOptions = requestOptions.clone().optionalCenterInside(); break; case CENTER: case MATRIX: default: // Do nothing. } }
Request previous = target.getRequest(); if (request.isEquivalentTo(previous) && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) { request.recycle(); // If the request is completed, beginning again will ensure the result is re-delivered, // triggering RequestListeners and Targets. If the request is failed, beginning again will // restart the request, giving it another chance to complete. If the request is already // running, we can let it continue running without interruption. if (!Preconditions.checkNotNull(previous).isRunning()) { // Use the previous request rather than the new one to allow for optimizations like skipping // setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions // that are done in the individual Request. previous.begin(); } return target; }
publicfinalclassSingleRequest<R> implementsRequest, SizeReadyCallback, ResourceCallback, FactoryPools.Poolable{ // ... @Override publicsynchronizedvoidbegin(){ assertNotCallingCallbacks(); stateVerifier.throwIfRecycled(); startTime = LogTime.getLogTime(); if (model == null) { if (Util.isValidDimensions(overrideWidth, overrideHeight)) { width = overrideWidth; height = overrideHeight; } // Only log at more verbose log levels if the user has set a fallback drawable, because // fallback Drawables indicate the user expects null models occasionally. int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG; onLoadFailed(new GlideException("Received null model"), logLevel); return; }
if (status == Status.RUNNING) { thrownew IllegalArgumentException("Cannot restart a running request"); }
// If we're restarted after we're complete (usually via something like a notifyDataSetChanged // that starts an identical request into the same Target or View), we can simply use the // resource and size we retrieved the last time around and skip obtaining a new size, starting a // new load etc. This does mean that users who want to restart a load because they expect that // the view size has changed will need to explicitly clear the View or Target before starting // the new load. if (status == Status.COMPLETE) { onResourceReady(resource, DataSource.MEMORY_CACHE); return; }
// Restarts for requests that are neither complete nor running can be treated as new requests // and can run again from the beginning.
status = Status.WAITING_FOR_SIZE; if (Util.isValidDimensions(overrideWidth, overrideHeight)) { onSizeReady(overrideWidth, overrideHeight); } else { target.getSize(this); }
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE) && canNotifyStatusChanged()) { target.onLoadStarted(getPlaceholderDrawable()); } if (IS_VERBOSE_LOGGABLE) { logV("finished run method in " + LogTime.getElapsedMillis(startTime)); } } publicsynchronizedvoidonSizeReady(int width, int height){ stateVerifier.throwIfRecycled(); if (IS_VERBOSE_LOGGABLE) { logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime)); } if (status != Status.WAITING_FOR_SIZE) { return; } status = Status.RUNNING;
// This is a hack that's only useful for testing right now where loads complete synchronously // even though under any executor running on any thread but the main thread, the load would // have completed asynchronously. if (status != Status.RUNNING) { loadStatus = null; } if (IS_VERBOSE_LOGGABLE) { logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime)); } } // ... }
// Avoid calling back while holding the engine lock, doing so makes it easier for callers to // deadlock. cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE); returnnull; } @Nullable private EngineResource<?> loadFromMemory( EngineKey key, boolean isMemoryCacheable, long startTime) { if (!isMemoryCacheable) { returnnull; }
EngineResource<?> active = loadFromActiveResources(key); if (active != null) { if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return active; }
EngineResource<?> cached = loadFromCache(key); if (cached != null) { if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return cached; }
returnnull; } @Nullable private EngineResource<?> loadFromActiveResources(Key key) { EngineResource<?> active = activeResources.get(key); if (active != null) { active.acquire(); }
final EngineResource<?> result; if (cached == null) { result = null; } elseif (cached instanceof EngineResource) { // Save an object allocation if we've cached an EngineResource (the typical case). result = (EngineResource<?>) cached; } else { result = new EngineResource<>( cached, /*isMemoryCacheable=*/true, /*isRecyclable=*/true, key, /*listener=*/this); } return result; } // ... }
classDecodeJob<R> implementsDataFetcherGenerator.FetcherReadyCallback, Runnable, Comparable<DecodeJob<?>>, Poolable{ // ... @Override publicvoidrun(){ // This should be much more fine grained, but since Java's thread pool implementation silently // swallows all otherwise fatal exceptions, this will at least make it obvious to developers // that something is failing. GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model); // Methods in the try statement can invalidate currentFetcher, so set a local variable here to // ensure that the fetcher is cleaned up either way. DataFetcher<?> localFetcher = currentFetcher; try { if (isCancelled) { notifyFailed(); return; } runWrapped(); } catch (CallbackException e) { // If a callback not controlled by Glide throws an exception, we should avoid the Glide // specific debug logic below. throw e; } catch (Throwable t) { // Catch Throwable and not Exception to handle OOMs. Throwables are swallowed by our // usage of .submit() in GlideExecutor so we're not silently hiding crashes by doing this. We // are however ensuring that our callbacks are always notified when a load fails. Without this // notification, uncaught throwables never notify the corresponding callbacks, which can cause // loads to silently hang forever, a case that's especially bad for users using Futures on // background threads. if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d( TAG, "DecodeJob threw unexpectedly" + ", isCancelled: " + isCancelled + ", stage: " + stage, t); } // When we're encoding we've already notified our callback and it isn't safe to do so again. if (stage != Stage.ENCODE) { throwables.add(t); notifyFailed(); } if (!isCancelled) { throw t; } throw t; } finally { // Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call // close in all cases anyway. if (localFetcher != null) { localFetcher.cleanup(); } GlideTrace.endSection(); } } privatevoidrunWrapped(){ switch (runReason) { case INITIALIZE: stage = getNextStage(Stage.INITIALIZE); currentGenerator = getNextGenerator(); runGenerators(); break; case SWITCH_TO_SOURCE_SERVICE: runGenerators(); break; case DECODE_DATA: decodeFromRetrievedData(); break; default: thrownew IllegalStateException("Unrecognized run reason: " + runReason); } } private Stage getNextStage(Stage current){ switch (current) { case INITIALIZE: return diskCacheStrategy.decodeCachedResource() ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE); case RESOURCE_CACHE: return diskCacheStrategy.decodeCachedData() ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE); case DATA_CACHE: // Skip loading from source if the user opted to only retrieve the resource from cache. return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE; case SOURCE: case FINISHED: return Stage.FINISHED; default: thrownew IllegalArgumentException("Unrecognized stage: " + current); } } private DataFetcherGenerator getNextGenerator(){ switch (stage) { case RESOURCE_CACHE: // 产生含有降低采样/转换资源数据缓存文件的 DataFetcher returnnew ResourceCacheGenerator(decodeHelper, this); case DATA_CACHE: // 产生包含原始未修改的源数据缓存文件的 DataFetcher returnnew DataCacheGenerator(decodeHelper, this); case SOURCE: // 生成使用注册的 ModelLoader 和加载时提供的 Model 获取源数据规定的 DataFetcher // 根据不同的磁盘缓存策略,源数据可首先被写入到磁盘,然后从缓存文件中加载,而不是直接返回 returnnew SourceGenerator(decodeHelper, this); case FINISHED: returnnull; default: thrownew IllegalStateException("Unrecognized stage: " + stage); } } privatevoidrunGenerators(){ currentThread = Thread.currentThread(); startFetchTime = LogTime.getLogTime(); boolean isStarted = false; while (!isCancelled && currentGenerator != null && !(isStarted = currentGenerator.startNext())) { stage = getNextStage(stage); currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) { reschedule(); return; } } // We've run out of stages and generators, give up. if ((stage == Stage.FINISHED || isCancelled) && !isStarted) { notifyFailed(); }
// Otherwise a generator started a new load and we expect to be called back in // onDataFetcherReady. } // ... }
// Stop the urlConnection instance of HttpUrlConnection from following redirects so that // redirects will be handled by recursive calls to this method, loadDataWithRedirects. urlConnection.setInstanceFollowRedirects(false);
// Connect explicitly to avoid errors in decoders if connection fails. urlConnection.connect(); // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352. stream = urlConnection.getInputStream(); if (isCancelled) { returnnull; } finalint statusCode = urlConnection.getResponseCode(); if (isHttpOk(statusCode)) { return getStreamForSuccessfulRequest(urlConnection); } elseif (isHttpRedirect(statusCode)) { String redirectUrlString = urlConnection.getHeaderField("Location"); if (TextUtils.isEmpty(redirectUrlString)) { thrownew HttpException("Received empty or null redirect url"); } URL redirectUrl = new URL(url, redirectUrlString); // Closing the stream specifically is required to avoid leaking ResponseBodys in addition // to disconnecting the url connection below. See #2352. cleanup(); return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers); } elseif (statusCode == INVALID_STATUS_CODE) { thrownew HttpException(statusCode); } else { thrownew HttpException(urlConnection.getResponseMessage(), statusCode); } } // ... }
classSourceGeneratorimplementsDataFetcherGenerator, DataFetcher.DataCallback<Object>, DataFetcherGenerator.FetcherReadyCallback{ // ... @Override publicvoidonDataReady(Object data){ DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy(); if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) { dataToCache = data; // We might be being called back on someone else's thread. Before doing anything, we should // reschedule to get back onto Glide's thread. cb.reschedule(); } else { cb.onDataFetcherReady( loadData.sourceKey, data, loadData.fetcher, loadData.fetcher.getDataSource(), originalKey); } } // ... }
@NonNull private Resource<ResourceType> decodeResourceWithList( DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options, List<Throwable> exceptions) throws GlideException { Resource<ResourceType> result = null; //noinspection ForLoopReplaceableByForEach to improve perf for (int i = 0, size = decoders.size(); i < size; i++) { ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i); try { DataType data = rewinder.rewindAndGet(); if (decoder.handles(data, options)) { data = rewinder.rewindAndGet(); result = decoder.decode(data, width, height, options); } // Some decoders throw unexpectedly. If they do, we shouldn't fail the entire load path, but // instead log and continue. See #2406 for an example. } catch (IOException | RuntimeException | OutOfMemoryError e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Failed to decode data for " + decoder, e); } exceptions.add(e); }
if (result != null) { break; } }
if (result == null) { thrownew GlideException(failureMessage, new ArrayList<>(exceptions)); } return result; } // ... }
publicclassStreamBitmapDecoderimplementsResourceDecoder<InputStream, Bitmap> { // ... @Override public Resource<Bitmap> decode( @NonNull InputStream source, int width, int height, @NonNull Options options) throws IOException {
// Use to fix the mark limit to avoid allocating buffers that fit entire images. final RecyclableBufferedInputStream bufferedStream; finalboolean ownsBufferedStream; if (source instanceof RecyclableBufferedInputStream) { bufferedStream = (RecyclableBufferedInputStream) source; ownsBufferedStream = false; } else { bufferedStream = new RecyclableBufferedInputStream(source, byteArrayPool); ownsBufferedStream = true; }
// Use to retrieve exceptions thrown while reading. // TODO(#126): when the framework no longer returns partially decoded Bitmaps or provides a // way to determine if a Bitmap is partially decoded, consider removing. ExceptionCatchingInputStream exceptionStream = ExceptionCatchingInputStream.obtain(bufferedStream);
// Use to read data. // Ensures that we can always reset after reading an image header so that we can still // attempt to decode the full image even when the header decode fails and/or overflows our read // buffer. See #283. MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream); UntrustedCallbacks callbacks = new UntrustedCallbacks(bufferedStream, exceptionStream); try { return downsampler.decode(invalidatingStream, width, height, options, callbacks); } finally { exceptionStream.release(); if (ownsBufferedStream) { bufferedStream.release(); } } } // ... }
最后调用到的是 downsampler 的 decode()
Downsampler#decode(InputStream is, int requestedWidth, int requestedHeight, Options options, DecodeCallbacks callbacks)
public Resource<Bitmap> decode( InputStream is, int requestedWidth, int requestedHeight, Options options, DecodeCallbacks callbacks) throws IOException { Preconditions.checkArgument( is.markSupported(), "You must provide an InputStream that supports" + " mark()");
classEngineJob<R> implementsDecodeJob.Callback<R>, Poolable{ // ... @Override publicvoidonResourceReady(Resource<R> resource, DataSource dataSource){ synchronized (this) { this.resource = resource; this.dataSource = dataSource; } notifyCallbacksOfResult(); } voidnotifyCallbacksOfResult(){ ResourceCallbacksAndExecutors copy; Key localKey; EngineResource<?> localResource; synchronized (this) { stateVerifier.throwIfRecycled(); if (isCancelled) { // TODO: Seems like we might as well put this in the memory cache instead of just recycling // it since we've gotten this far... resource.recycle(); release(); return; } elseif (cbs.isEmpty()) { thrownew IllegalStateException("Received a resource without any callbacks to notify"); } elseif (hasResource) { thrownew IllegalStateException("Already have resource"); } engineResource = engineResourceFactory.build(resource, isCacheable, key, resourceListener); // Hold on to resource for duration of our callbacks below so we don't recycle it in the // middle of notifying if it synchronously released by one of the callbacks. Acquire it under // a lock here so that any newly added callback that executes before the next locked section // below can't recycle the resource before we call the callbacks. hasResource = true; copy = cbs.copy(); incrementPendingCallbacks(copy.size() + 1);
@Override publicvoidrun(){ // Make sure we always acquire the request lock, then the EngineJob lock to avoid deadlock // (b/136032534). synchronized (cb) { synchronized (EngineJob.this) { if (cbs.contains(cb)) { // Acquire for this particular callback. engineResource.acquire(); callCallbackOnResourceReady(cb); removeCallback(cb); } decrementPendingCallbacks(); } } } } voidcallCallbackOnResourceReady(ResourceCallback cb){ try { // This is overly broad, some Glide code is actually called here, but it's much // simpler to encapsulate here than to do so at the actual call point in the // Request implementation. cb.onResourceReady(engineResource, dataSource); } catch (Throwable t) { thrownew CallbackException(t); } } // ... }
publicfinalclassSingleRequest<R> implementsRequest, SizeReadyCallback, ResourceCallback, FactoryPools.Poolable{ // ... publicsynchronizedvoidonResourceReady(Resource<?> resource, DataSource dataSource){ Resource<?> toRelease = null; try { synchronized (this) { stateVerifier.throwIfRecycled(); loadStatus = null; if (resource == null) { GlideException exception = new GlideException( "Expected to receive a Resource<R> with an " + "object of " + transcodeClass + " inside, but instead got null."); onLoadFailed(exception); return; }
Object received = resource.get(); if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) { toRelease = resource; this.resource = null; GlideException exception = new GlideException( "Expected to receive an object of " + transcodeClass + " but instead" + " got " + (received != null ? received.getClass() : "") + "{" + received + "} inside" + " " + "Resource{" + resource + "}." + (received != null ? "" : " " + "To indicate failure return a null Resource " + "object, rather than a Resource object containing null data.")); onLoadFailed(exception); return; }
if (!canSetResource()) { toRelease = resource; this.resource = null; // We can't put the status to complete before asking canSetResource(). status = Status.COMPLETE; return; }
onResourceReady((Resource<R>) resource, (R) received, dataSource); } } finally { if (toRelease != null) { engine.release(toRelease); } } } privatesynchronizedvoidonResourceReady(Resource<R> resource, R result, DataSource dataSource){ // We must call isFirstReadyResource before setting status. boolean isFirstResource = isFirstReadyResource(); status = Status.COMPLETE; this.resource = resource;
if (glideContext.getLogLevel() <= Log.DEBUG) { Log.d( GLIDE_TAG, "Finished loading " + result.getClass().getSimpleName() + " from " + dataSource + " for " + model + " with size [" + width + "x" + height + "] in " + LogTime.getElapsedMillis(startTime) + " ms"); }
publicabstractclassImageViewTarget<Z> extendsViewTarget<ImageView, Z> implementsTransition.ViewAdapter{ // ... @Override publicvoidonResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition){ if (transition == null || !transition.transition(resource, this)) { setResourceInternal(resource); } else { maybeUpdateAnimatable(resource); } } privatevoidsetResourceInternal(@Nullable Z resource){ // Order matters here. Set the resource first to make sure that the Drawable has a valid and // non-null Callback before starting it. setResource(resource); maybeUpdateAnimatable(resource); } protectedabstractvoidsetResource(@Nullable Z resource); // ... }