preface

In the first two articles, we analyzed the core source code of Android’s network infrastructure framework OKHttp and encapsulation framework Retrofit in detail. If you don’t know much about OKHttp or Retrofit’s internal mechanics, check out the main Android tripartite library source code analysis (1) and the main Android tripartite library source code analysis (2). In this article, we will take a closer look at the source code loading process of Glide, the most widely used image loading framework in Android.

1. Basic usage process

Glide’s most basic usage flow is this line of code, and all the additional functions extended by Glide are based on its builder’s chain calls.

GlideApp.with(context).load(url).into(iv);
Copy the code

The GlideApp is automatically generated by the annotation processor. To use GlideApp, you must configure the AppGlideModule module of the application.

@GlideModule public class MyAppGlideModule extends AppGlideModule { @Override public void applyOptions(Context context, GlideBuilder builder) {// Add the following configuration <! --builder.setDefaultRequestOptions(new RequestOptions().format(DecodeFormat.PREFER_RGB_565)); -- > <! --int memoryCacheSizeBytes = 1024 * 1024 * 20; -- > <! --builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes)); -- > <! --int bitmapPoolSizeBytes = 1024 * 1024 * 30; -- > <! --builder.setBitmapPool(new LruBitmapPool(bitmapPoolSizeBytes)); -- > <! --int diskCacheSizeBytes = 1024 * 1024 * 100; -- > <! --builder.setDiskCache(new InternalCacheDiskCacheFactory(context, diskCacheSizeBytes)); -- >}}Copy the code

Next, this article will be aimed at the latest source version of Glide V4.8.0 to Glide load network picture process in detail analysis and explanation, and strive to make readers friends know what is what.

GlideApp. With (context

First, this Glide frame drawing with Gentiana wild dust dream gives us a preliminary understanding of the overall framework of Glide.

Starting with the glideapp. with line, the internal mainline is executed as follows.

1, GlideApp# with

return (GlideRequests) Glide.with(context);
Copy the code

2, Glide# with

return getRetriever(context).get(context); return Glide.get(context).getRequestManagerRetriever(); CheckAndInitializeGlide (context) checkAndInitializeGlide(context) checkAndInitializeGlide(context) initializeGlide(context); InitializeGlide (context, new GlideBuilder())); @SuppressWarnings("deprecation") private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) { Context applicationContext = context.getApplicationContext(); / / 1, to get the front application annotated GlideModule GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules (); // If GlideModule is empty or the manifest flag is true, obtain the manifestModules configured in the manifest. List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList(); if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled( )) { manifestModules = new ManifestParser(applicationContext).parse(); }... RequestManagerRetriever.RequestManagerFactory factory = annotationGeneratedModule ! = null ? annotationGeneratedModule.getRequestManag erFactory() : null; builder.setRequestManagerFactory(factory); for (com.bumptech.glide.module.GlideModule module : manifestModules) { module.applyOptions(applicationContext, builder); } if (annotationGeneratedModule ! = null) { annotationGeneratedModule.applyOptions(applicatio nContext, builder); Build (applicationContext); / / 4, put manifestModules and annotationGeneratedModule configuration information inside builder / / (applyOptions) replace inside glide (registerComponents) for the default components (com.bumptech.glide.module.GlideModule module : manifestModules) { module.registerComponents(applicationContext, glide, glide.registry); } if (annotationGeneratedModule ! = null) { annotationGeneratedModule.registerComponents(appl icationContext, glide, glide.registry); } applicationContext.registerComponentCallbacks(glide ); Glide.glide = glide; }Copy the code

3, GlideBuilder# build

@nonNULL Glide build(@nonNULL Context Context) {// Create a thread pool for the request picture sourceExecutor if (sourceExecutor == null) {sourceExecutor  = GlideExecutor.newSourceExecutor(); } diskCacheExecutor if (diskCacheExecutor == null) {diskCacheExecutor = GlideExecutor.newDiskCacheExecutor(); } / / to create animation thread pool animationExecutor if (animationExecutor = = null) {animationExecutor = GlideExecutor. NewAnimationExecutor ();  } if (memorySizeCalculator == null) { memorySizeCalculator = new MemorySizeCalculator.Builder(context).build(); } if (connectivityMonitorFactory == null) { connectivityMonitorFactory = new DefaultConnectivityMonitorFactory(); } the if (bitmapPool = = null) {/ / based on the device screen density and size set all kinds of the size of the pool int size = memorySizeCalculator. GetBitmapPoolSize (); If (size > 0) {// Create an image thread pool LruBitmapPool to cache all released bitmaps. SizeConfigStrategy HashMap bitmapPool = new LruBitmapPool(size); // SizeConfigStrategy SizeConfigStrategy HashMap bitmapPool = new LruBitmapPool(size); } else { bitmapPool = new BitmapPoolAdapter(); }} // Create an object array cache pool, If the default 4 m (arrayPool = = null) {arrayPool = new LruArrayPool (memorySizeCalculator. GetArrayPoolSiz eInBytes ()); } // create LruResourceCache, Memory cache if (memoryCache = = null) {memoryCache = new LruResourceCache (memorySizeCalculator. GetMemoryCa cheSize ()); } if (diskCacheFactory == null) { diskCacheFactory = new InternalCacheDiskCacheFactory(context); } // Create task and resource management engines (thread pools, MemoryCache and disk cache object) if (engine == null) {engine = new engine (memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor, GlideExecutor.newUnlimitedSourceExecutor( ), GlideExecutor.newAnimationExecutor(), isActiveResourceRetentionAllowed); } RequestManagerRetriever requestManagerRetriever = new RequestManagerRetriever(requestManagerFactory); return new Glide( context, engine, memoryCache, bitmapPool, arrayPool, requestManagerRetriever, connectivityMonitorFactory, logLevel, defaultRequestOptions.lock(), defaultTransitionOptions); }Copy the code

4. Glide#Glide construction method

Glide(...) {... // Registry is a factory in which all registered objects are factory employees. When the task is distributed, according to the nature of the current task, Registry = new registry (); . Registry. Append (bytebuffer. class); // There are about 60 append or Register employee components (parsers, codecs, factory classes, transcoding classes, etc.) new ByteBufferEncoder()) .append(InputStream.class, New StreamEncoder(arrayPool)) // Output the target of the given subclass (BitmapImageViewTarget/DrawableImageViewTarget) ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory(); glideContext = new GlideContext( context, arrayPool, registry, imageViewTargetFactory, defaultRequestOptions, defaultTransitionOptions, engine, logLevel); }Copy the code

5, RequestManagerRetriever# get

@NonNull public RequestManager get(@NonNull Context context) { if (context == null) { throw new IllegalArgumentException("You cannot start a load on a null Context"); } else if (Util.isOnMainThread() && ! (Context instanceof Application)) {// If the current thread is the main thread and the context is not Application go to the corresponding get overload method if (context instanceof) FragmentActivity) { return get((FragmentActivity) context); } else if (context instanceof Activity) { return get((Activity) context); } else if (context instanceof ContextWrapper) { return get(((ContextWrapper) context).getBaseContext()); Return getApplicationManager(context); }Copy the code

To summarize, the lifecycle of the request is associated with ApplicationLifecycle if the context is currently passed as an application or if the current thread is a child thread, otherwise, if the context is FragmentActivity or Fragment, In the current component to add a SupportFragment (SupportRequestManagerFragment), the context is the Activity, in the current component to add a Fragment (RequestManagerFragment).

6, GlideApp#with summary

Initialize various configuration information (including cache, request thread pool, size, image format, etc.) and Glide object.
2, will glide requests and application/SupportFragment/binding in a piece of fragments of life cycle.
Let’s review the execution flow of the with method.

Load (URL) source code

# 1, GlideRequest (RequestManager) load

return (GlideRequest<Drawable>) super.load(string); return asDrawable().load(string); Return (GlideRequest<Drawable>) super.asdrawable (); return as(Drawable.class); Return new GlideRequest<>(Glide, this, resourceClass, context); Return (GlideRequest<TranscodeType>) super.load(string); return loadGeneric(string); @nonnull private RequestBuilder<TranscodeType> loadGeneric(@nullable Object model) {// model specifies the url this.model = model; // Record that the URL is set to isModelSet = true; return this; }Copy the code

GlideRequest (RequestManager) sets the mode (URL) for the request and records the state of the URL.

Here, let’s look at the load method execution flow.

Into (iv) source code details

Warning ahead, where the real complications begin.

1, RequestBuilder. Into

@NonNull public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) { Util.assertMainThread(); Preconditions.checkNotNull(view); RequestOptions requestOptions = this.requestOptions; 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()) { // The RequestOptions contains the scaleType to be set. Glide itself encapsulates CenterCrop, CenterInside, // FitCenter, and CenterInside specifications. 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.}} The transcodeClass refers to the drawable or bitmap return into (glideContext. BuildImageViewTarget (view, transcodeClass), /*targetListener=*/ null, requestOptions); }Copy the code

2, GlideContext# buildImageViewTarget

return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
 
Copy the code

3, ImageViewTargetFactory# buildTarget

@NonNull @SuppressWarnings("unchecked") public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view, If (bitmap.class.equals (clazz)) {return (ViewTarget<ImageView,  Z>) new BitmapImageViewTarget(view); } else if (Drawable.class.isAssignableFrom(clazz)) { return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view); } else { throw new IllegalArgumentException( "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)"); }}Copy the code

So you can see that Glide only maintains two kinds of target internally, a BitmapImageViewTarget and a DrawableImageViewTarget, so let’s go ahead and do that.

4, RequestBuilder# into

private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType>   targetListener,
      @NonNull RequestOptions options) {
    Util.assertMainThread();
    Preconditions.checkNotNull(target);
    if (!isModelSet) {
      throw new IllegalArgumentException("You must call   #load() before calling #into()");
    }

    options = options.autoClone();
    // 分析1.建立请求
    Request request = buildRequest(target,     targetListener, options);

    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousReques    t(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).isRunni  ng()) {
        // 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;
    }
    
    requestManager.clear(target);
    target.setRequest(request);
    // 分析2.真正追踪请求的地方
    requestManager.track(target, request);

    return target;
}

// 分析1
private Request buildRequest(
      Target<TranscodeType> target,
      @Nullable RequestListener<TranscodeType>   targetListener,
      RequestOptions requestOptions) {
    return buildRequestRecursive(
        target,
        targetListener,
        /*parentCoordinator=*/ null,
        transitionOptions,
        requestOptions.getPriority(),
        requestOptions.getOverrideWidth(),
        requestOptions.getOverrideHeight(),
        requestOptions);
}

// 分析1
private Request buildRequestRecursive(
      Target<TranscodeType> target,
      @Nullable RequestListener<TranscodeType>   targetListener,
      @Nullable RequestCoordinator parentCoordinator,
      TransitionOptions<?, ? super TranscodeType>   transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight,
      RequestOptions requestOptions) {

    // Build the ErrorRequestCoordinator first if     necessary so we can update parentCoordinator.
    ErrorRequestCoordinator errorRequestCoordinator =     null;
    if (errorBuilder != null) {
      // 创建errorRequestCoordinator(异常处理对象)
      errorRequestCoordinator = new   ErrorRequestCoordinator(parentCoordinator);
      parentCoordinator = errorRequestCoordinator;
    }

    // 递归建立缩略图请求
    Request mainRequest =
        buildThumbnailRequestRecursive(
            target,
            targetListener,
            parentCoordinator,
            transitionOptions,
            priority,
            overrideWidth,
            overrideHeight,
            requestOptions);

    if (errorRequestCoordinator == null) {
      return mainRequest;
    }

    ...
    
    Request errorRequest =     errorBuilder.buildRequestRecursive(
        target,
        targetListener,
        errorRequestCoordinator,
        errorBuilder.transitionOptions,
        errorBuilder.requestOptions.getPriority(),
        errorOverrideWidth,
        errorOverrideHeight,
        errorBuilder.requestOptions);
    errorRequestCoordinator.setRequests(mainRequest,     errorRequest);
    return errorRequestCoordinator;
}

// 分析1
private Request buildThumbnailRequestRecursive(
      Target<TranscodeType> target,
      RequestListener<TranscodeType> targetListener,
      @Nullable RequestCoordinator parentCoordinator,
      TransitionOptions<?, ? super TranscodeType> transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight,
      RequestOptions requestOptions) {
    if (thumbnailBuilder != null) {
      // Recursive case: contains a potentially recursive thumbnail request builder.
      
      ...

      ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
      // 获取一个正常请求对象
      Request fullRequest =
          obtainRequest(
              target,
              targetListener,
              requestOptions,
              coordinator,
              transitionOptions,
              priority,
              overrideWidth,
              overrideHeight);
      isThumbnailBuilt = true;
      // Recursively generate thumbnail requests.
      // 使用递归的方式建立一个缩略图请求对象
      Request thumbRequest =
          thumbnailBuilder.buildRequestRecursive(
              target,
              targetListener,
              coordinator,
              thumbTransitionOptions,
              thumbPriority,
              thumbOverrideWidth,
              thumbOverrideHeight,
              thumbnailBuilder.requestOptions);
      isThumbnailBuilt = false;
      // coordinator(ThumbnailRequestCoordinator)是作为两者的协调者,
      // 能够同时加载缩略图和正常的图的请求
      coordinator.setRequests(fullRequest, thumbRequest);
      return coordinator;
    } else if (thumbSizeMultiplier != null) {
      // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
      // 当设置了缩略的比例thumbSizeMultiplier(0 ~  1)时,
      // 不需要递归建立缩略图请求
      ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
      Request fullRequest =
          obtainRequest(
              target,
              targetListener,
              requestOptions,
              coordinator,
              transitionOptions,
              priority,
              overrideWidth,
              overrideHeight);
      RequestOptions thumbnailOptions = requestOptions.clone()
          .sizeMultiplier(thumbSizeMultiplier);

      Request thumbnailRequest =
          obtainRequest(
              target,
              targetListener,
              thumbnailOptions,
              coordinator,
              transitionOptions,
              getThumbnailPriority(priority),
              overrideWidth,
              overrideHeight);

      coordinator.setRequests(fullRequest, thumbnailRequest);
      return coordinator;
    } else {
      // Base case: no thumbnail.
      // 没有缩略图请求时,直接获取一个正常图请求
      return obtainRequest(
          target,
          targetListener,
          requestOptions,
          parentCoordinator,
          transitionOptions,
          priority,
          overrideWidth,
          overrideHeight);
    }
}

private Request obtainRequest(
      Target<TranscodeType> target,
      RequestListener<TranscodeType> targetListener,
      RequestOptions requestOptions,
      RequestCoordinator requestCoordinator,
      TransitionOptions<?, ? super TranscodeType>   transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight) {
    // 最终实际返回的是一个SingleRequest对象(将制定的资源加载进对应的Target
    return SingleRequest.obtain(
        context,
        glideContext,
        model,
        transcodeClass,
        requestOptions,
        overrideWidth,
        overrideHeight,
        priority,
        target,
        targetListener,
        requestListeners,
        requestCoordinator,
        glideContext.getEngine(),
        transitionOptions.getTransitionFactory());
}
Copy the code

Requestmanager.track (target, request); buildRequest(); requestManager.track(target, request); And then let’s see what happens inside track.

5, RequestManager# track

2 void track(@nonnull Target<? > target, @nonnull Request Request) {// Add a target target (Set) targettracker.track (target); requestTracker.runRequest(request); }Copy the code

6, RequestTracker# runRequest

/** * Starts tracking the given Request. */ requests.add(request); if (! IsPaused) {// Request request.begin(); } else { request.clear(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Paused, delaying request"); } // Otherwise, empty requests and queue deferred requests. (To maintain a strong reference to these requests, ArrayList is used.) }}Copy the code

7, SingleRequest# begin

2 @override public void begin() {... if (model == null) { ... OnLoadFailed (new GlideException("Received null model"), logLevel); return; } if (status == Status.RUNNING) { throw new IllegalArgumentException("Cannot restart a running request"); } if (status == Status.COMPLETE) { onResourceReady(resource, DataSource.MEMORY_CACHE); return; } status = Status.WAITING_FOR_SIZE; If (util.isValidDimensions (overrideWidth, overrideHeight)) {// If (util.isValidDimensions (overrideWidth, overrideHeight)) {// If (util.isValidDimensions (overrideWidth, overrideHeight)) { // The final core processing is onSizeReady onSizeReady(overrideWidth, overrideHeight); } else {onSizeReady target.getSize(this); } the if ((status = = status. RUNNING | | status = = status. WAITING_FOR_SIZE) && canNotifyStatusChanged ()) {/ / preloaded Settings of thumbnails target.onLoadStarted(getPlaceholderDrawable()); } if (IS_VERBOSE_LOGGABLE) { logV("finished run method in " + LogTime.getElapsedMillis(startTime)); }}Copy the code

Requestmanager.track (target, request) is followed by onSizeReady for SingleRequest# Begin (). As you can guess, the actual request starts here. Let’s go in and find out

8, SingleRequest# onSizeReady

/ / analysis 2 @ Override public void onSizeReady (int width, int height) {stateVerifier. ThrowIfRecycled (); . status = Status.RUNNING; float sizeMultiplier = requestOptions.getSizeMultiplier(); this.width = maybeApplySizeMultiplier(width, sizeMultiplier); this.height = maybeApplySizeMultiplier(height, sizeMultiplier); . // Load according to the given configuration, Engine is a manage loading, active and cache resources engine classes loadStatus = engine. The load (glideContext, model, requestOptions getSignature (), enclosing the width, this.height, requestOptions.getResourceClass(), transcodeClass, priority, requestOptions.getDiskCacheStrategy(), requestOptions.getTransformations(), requestOptions.isTransformationRequired(), requestOptions.isScaleOnlyOrNoTransform(), requestOptions.getOptions(), requestOptions.isMemoryCacheable(), requestOptions.getUseUnlimitedSourceGeneratorsP ool(), requestOptions.getUseAnimationPool(), requestOptions.getOnlyRetrieveFromCache(), this); . }Copy the code

Finally see Engine class, feel not far from success, continue ~

9, Engine# load

public <R> LoadStatus load( GlideContext glideContext, Object model, Key signature, int width, int height, Class<? > resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<? >, Transformation<? >> transformations, boolean isTransformationRequired, boolean isScaleOnlyOrNoTransform, Options options, boolean isMemoryCacheable, boolean useUnlimitedSourceExecutorPool, boolean useAnimationPool, boolean onlyRetrieveFromCache, ResourceCallback cb) { ... // First look for weak references, if any, call onResourceReady and return EngineResource<? > active = loadFromActiveResources(key, isMemoryCacheable); if (active ! = null) { cb.onResourceReady(active, DataSource.MEMORY_CACHE); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return null; } // If there is one, it will be retrieved and put into ActiveResources EngineResource<? > cached = loadFromCache(key, isMemoryCacheable); if (cached ! = null) { cb.onResourceReady(cached, DataSource.MEMORY_CACHE); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return null; } EngineJob<? > current = jobs.get(key, onlyRetrieveFromCache); if (current ! = null) { current.addCallback(cb); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Added to existing load", startTime, key); } return new LoadStatus(cb, current); } // Create engineJob (decodeJob callback class) To manage the download process and state) EngineJob < R > EngineJob = engineJobFactory. Build (key, isMemoryCacheable useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache); DecodeJob<R> DecodeJob = decodeJobFactory. Build (glideContext, model, key, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache, options, engineJob); Jobs.put (key, engineJob); // Register ResourceCallback enginejob.addCallback (cb); DecodeJob (decodeJob); decodeJob (decodeJob); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Started new load", startTime, key); } return new LoadStatus(cb, engineJob); } public void start(DecodeJob<R> decodeJob) { this.decodeJob = decodeJob; // willDecodeFromCache returns true if RESOURCE_CACHE/DATA_CACHE, depending on the stage, diskCacheExecutor, Otherwise call getActiveSourceExecutor, Internal will return according to the corresponding condition sourceUnlimitedExecutor animationExecutor/sourceExecutor GlideExecutor executor = decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor(); executor.execute(decodeJob); }Copy the code

As you can see, eventually the Engine class executes its own start method inside, It will be according to the thread pool of different configuration of different use diskCacheExecutor/sourceUnlimitedExecutor/animationExecutor sourceExecutor decodeJob to perform the final decoding task.

10, DecodeJob# run

runWrapped(); private void runWrapped() { switch (runReason) { case INITIALIZE: stage = getNextStage(Stage.INITIALIZE); CurrentGenerator = getNextGenerator(); // Concern 2 calls startNext() runGenerators() internally; break; case SWITCH_TO_SOURCE_SERVICE: runGenerators(); break; Case DECODE_DATA: // Concern 3 decodes the retrieved data into the corresponding resource decodeFromRetrievedData(); break; default: throw new IllegalStateException("Unrecognized run reason: " + runReason); }} // Concern 1, complete, asynchronously generates the ResourceCacheGenerator, DataCacheGenerator, and SourceGenerator objects here in sequence, StartNext () private DataFetcherGenerator getNextGenerator() {switch (stage) {case RESOURCE_CACHE: return new ResourceCacheGenerator(decodeHelper, this); case DATA_CACHE: return new DataCacheGenerator(decodeHelper, this); case SOURCE: return new SourceGenerator(decodeHelper, this); case FINISHED: return null; default: throw new IllegalStateException("Unrecognized stage: " + stage); }}Copy the code

11, SourceGenerator# startNext

@override public Boolean startNext() {if (dataToCache!) {if (dataToCache! = null) { Object data = dataToCache; dataToCache = null; cacheData(data); } if (sourceCacheGenerator ! = null && sourceCacheGenerator.startNext()) { return true; } sourceCacheGenerator = null; loadData = null; boolean started = false; while (! Started && hasNextModelLoader()) {// Focus 4 getLoadData() will find the ModelLoder object inside modelLoaders (Each Generator corresponds to one ModelLoader), / / and use modelLoader. BuildLoadData method returns a list of the loadData loadData = helper. GetLoadData () get (loadDataListIndex++); if (loadData ! = null && (helper.getDiskCacheStrategy().isDataCache able(loadData.fetcher.getDataSource()) || helper.hasLoadPath(loadData.fetcher.getDat aClass()))) { started = true; // Concern 6 fetches image data through the // loadData method of the fetcher object of loadData loadData.fetcher.loadData(helper.getPriority(), this); } } return started; }Copy the code

12, DecodeHelper# getLoadData

List<LoadData<? >> getLoadData() { if (! isLoadDataSet) { isLoadDataSet = true; loadData.clear(); List<ModelLoader<Object, ? >> modelLoaders = glideContext.getRegistry().getModelLoaders(model) ; //noinspection ForLoopReplaceableByForEach to improve perf for (int i = 0, size = modelLoaders.size(); i < size; i++) { ModelLoader<Object, ? > modelLoader = modelLoaders.get(i); LoadData<? LoadData<? LoadData<? > current = modelLoader.buildLoadData(model, width, height, options); if (current ! = null) { loadData.add(current); } } } return loadData; }Copy the code

13, HttpGlideUrlLoader# buildLoadData

@Override public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height, @NonNull Options options) { // GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time // spent parsing urls. GlideUrl url = model; if (modelCache ! = null) { url = modelCache.get(model, 0, 0); If (url == null) {// concern 5 modelCache.put(model, 0, 0, model); url = model; } } int timeout = options.get(TIMEOUT); HttpUrlFetcher return new LoadData<>(url, new HttpUrlFetcher(url, timeout)); Public void put(A model, int width, int height, B value) {ModelKey<A> key = modelkey. get(model, width, B value) height); Cache. put(key, value); // LruCache is used to cache the corresponding value. }Copy the code

From this analysis, we know that HttpUrlFetcher is actually the ultimate request executor, and we know that Glide uses LruCache to cache the parsed URL so that it doesn’t have to parse the URL later.

14, HttpUrlFetcher# loadData

@Override public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) { long startTime = LogTime.getLogTime(); Try {// concern 6 // loadDataWithRedirects internally requests data through HttpURLConnection InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders()); OnDataReady () callback. OnDataReady (result); } catch (IOException e) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Failed to load data for url", e); } callback.onLoadFailed(e); } finally { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime)); } } } private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException { ... urlConnection.connect(); // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352. stream = urlConnection.getInputStream(); if (isCancelled) { return null; } final int statusCode = urlConnection.getResponseCode(); If (isHttpOk(statusCode)) {// Return the resource stream from urlConnection getStreamForSuccessfulRequest(urlConnection); } else if (isHttpRedirect(statusCode)) { ... Return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers); } else if (statusCode == INVALID_STATUS_CODE) { throw new HttpException(statusCode); } else { throw new HttpException(urlConnection.getResponseMessage(), statusCode); } } private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection) throws IOException { if (TextUtils.isEmpty(urlConnection.getContentEncoding())) { int contentLength = urlConnection.getContentLength(); stream = ContentLengthInputStream.obtain(urlConnection.getInputStr eam(), contentLength); } else { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding()); } stream = urlConnection.getInputStream(); } return stream; }Copy the code

HttpUrlFetcher#loadData loadDataWithRedirects And call the getStreamForSuccessfulRequest flow () method to obtain the final image.

15, DecodeJob# run

After we request the corresponding stream via HtttpUrlFetcher’s loadData() method, we must also process the stream to get the resource we want. Here we return to focus 3 of step 10’s DecodeJob#run method, which decodes the stream.

decodeFromRetrievedData();
Copy the code

Next, take a look at his internal processing.

private void decodeFromRetrievedData() { if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Retrieved data", startFetchTime, "data: " + currentData + ", cache key: " + currentSourceKey + ", fetcher: " + currentFetcher); } Resource<R> resource = null; Resource = decodeFromData(currentFetcher, currentData, currentDataSource); } catch (GlideException e) { e.setLoggingDetails(currentAttemptingKey, currentDataSource); throwables.add(e); } if (resource ! = null) {// concern 8 // Encode and publish the resulting Resource<Bitmap> notifyEncodeAndRelease(Resource, currentDataSource); } else { runGenerators(); } } private <Data> Resource<R> decodeFromData(DataFetcher<? > fetcher, Data data, DataSource dataSource) throws GlideException { try { if (data == null) { return null; } long startTime = LogTime.getLogTime(); Resource<R> result = decodeFromFetcher(data, dataSource); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Decoded result " + result, startTime); } return result; } finally { fetcher.cleanup(); } } @SuppressWarnings("unchecked") private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource) throws GlideException { LoadPath<Data, ? , R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass()); LoadPath return runLoadPath(data, dataSource, path); } private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource, LoadPath<Data, ResourceType, R> path) throws GlideException { Options options = getOptionsWithHardwareConfig(dataSource); DataRewinder<Data> rewinder = glidecontext.getregistry ().getrewinder (Data); // ResourceType in DecodeCallback below is required for compilation to work with gradle LoadPath return path.load(rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource)); } finally { rewinder.cleanup(); }}Copy the code

16, LoadPath# load

public Resource<Transcode> load(DataRewinder<Data> rewinder, @NonNull Options options, int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) throws GlideException { List<Throwable> throwables = Preconditions.checkNotNull(listPool.acquire()); Return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables); } finally { listPool.release(throwables); }Copy the code

}

private Resource<Transcode> loadWithExceptionList(DataRewinder<Data> rewinder, @NonNull Options options, int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback, List<Throwable> exceptions) throws GlideException { Resource<Transcode> result = null; //noinspection ForLoopReplaceableByForEach to improve perf for (int i = 0, size = decodePaths.size(); i < size; i++) { DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i); Decode result = path.decode(rewinder, width, height, options, decodeCallback); } catch (GlideException e) { exceptions.add(e); } if (result ! = null) { break; } } if (result == null) { throw new GlideException(failureMessage, new ArrayList<>(exceptions)); } return result; }Copy the code

17, DecodePath# decode

public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {// Core code // continue calling DecodePath's decodeResource method to parse the data Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options); Resource<ResourceType> transformed = callback.onResourceDecoded(decoded); return transcoder.transcode(transformed, options); } @NonNull private Resource<ResourceType> decodeResource(DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options) throws GlideException { List<Throwable> exceptions = Preconditions.checkNotNull(listPool.acquire()); Return decodeResourceWithList(rewinder, width, height, options, exceptions); } finally { listPool.release(exceptions); } } @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)) {// Get data from the decoder. RewindAndGet (); Decoder result = Decoder. Decode (data, width, height, options); Decoder result = Decoder. Decode (data, width, height, options); } } 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) { throw new GlideException(failureMessage, new ArrayList<>(exceptions)); } return result; }Copy the code

As you can see, after a series of nested calls, decoder.decode() is finally executed. Decode is a ResourceDecoder<DataType, ResourceType> interface (ResourceDecoder), It has different implementation classes based on different DataType and ResourceType. In this case, the implementation class is ByteBufferBitmapDecoder. Let’s look at the decoding process inside the decoder.

In the 18th and ByteBufferBitmapDecoder# decode

/** * Decodes {@link android.graphics.Bitmap Bitmaps} from {@link java.nio.ByteBuffer ByteBuffers}. */ public class ByteBufferBitmapDecoder implements ResourceDecoder<ByteBuffer, Bitmap> { ... @Override public Resource<Bitmap> decode(@NonNull ByteBuffer source, int width, int height, @NonNull Options options) throws IOException { InputStream is = ByteBufferUtil.toStream(source); // Return downsampler. Decode (is, width, height, options); }}Copy the code

As you can see, you end up using a downsampler, which is a compressor that decodes, compresses, fillets, etc.

19, DownSampler# decode

public Resource<Bitmap> decode(InputStream is, int outWidth, int outHeight, Options options) throws IOException { return decode(is, outWidth, outHeight, options, EMPTY_CALLBACKS); } @SuppressWarnings({"resource", "deprecation"}) 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()"); . Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions, downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth, requestedHeight, fixBitmapToRequestedDimensions, callbacks); // Concern 7 // Decodes the Bitmap object, wraps it into a BitmapResource object and returns, // Obtain the Resource<Bitmap> object using the internal get method return BitmapResource. Obtain (result, bitmapPool); } finally { releaseOptions(bitmapFactoryOptions); byteArrayPool.put(bytesForOptions); } } private Bitmap decodeFromWrappedStreams(InputStream is, BitmapFactory.Options options, DownsampleStrategy downsampleStrategy, DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth, int requestedHeight, boolean fixBitmapToRequestedDimensions, DecodeCallbacks callbacks) throws IOException {// save a series of non-core logic such as computing compression ratio... Bitmap down-time = decodeStream(is, options, callbacks, bitmapPool); callbacks.onDecodeComplete(bitmapPool, downsampled); . // Bimtap rotation processing... return rotated; } private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options, DecodeCallbacks callbacks, BitmapPool bitmapPool) throws IOException { ... TransformationUtils.getBitmapDrawableLock().lock(); Result = BitmapFactory. Try {/ / core code decodeStream (is null, the options); } catch (IllegalArgumentException e) { ... } finally { TransformationUtils.getBitmapDrawableLock().unlock(); } if (options.inJustDecodeBounds) { is.reset(); } return result; }Copy the code

We know from the above source process, the final is in DownSampler decodeStream () method is used in the BitmapFactory. DecodeStream () to get the Bitmap object. Then, to analyze how the image is displayed, we go back to the DownSampler#decode method in step 19 and look at focus 7, where we return the Bitmap as a BitmapResource object, and we get the Resource object through the internal get method. Back to the DecodeJob#run method in step 15, which uses the notifyEncodeAndRelease() method to publish the Resource object.

20, DecodeJob# notifyEncodeAndRelease

private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) { ... notifyComplete(result, dataSource); . } private void notifyComplete(Resource<R> resource, DataSource dataSource) { setNotifiedOrThrow(); callback.onResourceReady(resource, dataSource); }Copy the code

It implements DecodeJob.CallBack. DecodeJob.

class EngineJob<R> implements DecodeJob.Callback<R>,
    Poolable {
    ...
}
Copy the code

21, EngineJob# onResourceReady

@Override public void onResourceReady(Resource<R> resource, DataSource dataSource) { this.resource = resource; this.dataSource = dataSource; MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget(); } private static class MainThreadCallback implements Handler.Callback{ ... @Override public boolean handleMessage(Message message) { EngineJob<? > job = (EngineJob<? >) message.obj; The switch (message. What) {case MSG_COMPLETE: / / core code job. HandleResultOnMainThread (); break; . } return true; }}Copy the code

The main thread Handler object is used to switch threads, and the handleResultOnMainThread method is called on the main thread.

@Synthetic void handleResultOnMainThread() { ... //noinspection ForLoopReplaceableByForEach to improve perf for (int i = 0, size = cbs.size(); i < size; i++) { ResourceCallback cb = cbs.get(i); if (! isInIgnoredCallbacks(cb)) { engineResource.acquire(); cb.onResourceReady(engineResource, dataSource); }}... }Copy the code

Again, all ResourceCallback methods are called through a loop. Let’s go back to focus 8 of the Engine#load method in step 9, where ResourceCallback is registered. Equestlerequest# onSizeReady and the engine. Load parameter of the SingleRequest#onSizeReady method. EngineJob. AddCallback (cb) The implementation of cb is SingleRequest. Next, let’s look at SingleRequest’s onResourceReady method.

22, SingleRequest# onResourceReady

/** * A callback method that should never be invoked directly. */ @SuppressWarnings("unchecked") @Override public void onResourceReady(Resource<? > resource, DataSource dataSource) { ... // Get the Bitmap Object from Resource<Bitmap> Object received = resource-.get (); . onResourceReady((Resource<R>) resource, (R) received, dataSource); } private void onResourceReady(Resource<R> resource, R resultDataSource dataSource) { ... try { ... if (! anyListenerHandledUpdatingTarget) { Transition<? super R> animation = animationFactory.build(dataSource, isFirstResource); // Target. OnResourceReady (result, animation); } } finally { isCallingCallbacks = false; } notifyLoadSuccess(); }Copy the code

Target. onResourceReady(result, animation) method is called in the equestler# onResourceReady method. So the target here is actually the BitmapImageViewTarget that we set up in the into method, and when we look at the BitmapImageViewTarget class, we don’t see the onResourceReady method, But we found the onResourceReady method in its subclass ImageViewTarget, and we’ll move on from there.

23, ImageViewTarget# onResourceReady

public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z> implements Transition.ViewAdapter { ... @Override public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) { if (transition == null || ! Transition. Transition (resource, this)) {// setResourceInternal(resource); } else { maybeUpdateAnimatable(resource); }}... private void setResourceInternal(@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. maybeUpdateAnimatable(resource); } // protected abstract void setResource(@nullable Z resource); }Copy the code

Here we go back to the setResource method of the BitmapImageViewTarget and we finally see that the Bitmap has been set to the current imageView.

public class BitmapImageViewTarget extends ImageViewTarget<Bitmap> { ... @Override protected void setResource(Bitmap resource) { view.setImageBitmap(resource); }}Copy the code

This is the end of our analysis, and it can be seen that Glide puts most of the logical processing in the last into method, which goes through more than 20 analysis steps to request the image stream, decode the image, and finally set up to the corresponding imageView.

Finally, here is a complete Glide loading flow chart that I spent several hours drawing, very precious, we can carefully comb the main Glide process again.

Five, the summary

This is the end of Glide’s entire loading process. As you can see, Glide’s most core logic is concentrated in the into() method. The design is delicate and complex, and this part of the source code analysis is time-consuming. You may have an Epiphany on your way to Android. At present, Android mainstream tripartite library source analysis series has been on the network library (OkHttp, Retrofit) and picture loading library (Glide) for a detailed source analysis, next, will be on the database framework GreenDao core source in-depth analysis, please look forward to ~

Reference links:

Glide V4.8.0 source code

2. Understand Glide’s execution process from the perspective of source code

3, Glide source code analysis


Thank you for reading this article and I hope you can share it with your friends or technical group, it means a lot to me.