Glide源码解析

作者: 奔跑吧李博 | 来源:发表于2020-09-08 00:59 被阅读0次

    Glide滑行的意思,可以看出这个库的主旨就在于让图片加载变的流畅。

    Glide架构设计

    这里我们从使用入手研究Glide源码:

    Glide.with(this).load(url).into(imageView);
    

    with方法

      @NonNull
      public static RequestManager with(@NonNull Context context) {
        return getRetriever(context).get(context);
      }
    

    再进入了RequestManagerRetriever的get方法,判断是否在主线程中调用,如果是子线程中,则调用get(activity.getApplicationContext())。

      @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 (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);
      }
    

    上面这个方法主要是通过传入context的不同类型来做不同的操作。context可以是Application、FragmentActivity、Activity或者是ContextWrapper。如果context是Application,调用getApplicationManager方法,最终返回RequestManager。

      private RequestManager getApplicationManager(@NonNull Context context) {
        // Either an application context or we're on a background thread.
        if (applicationManager == null) {
          synchronized (this) {
            if (applicationManager == null) {
              Glide glide = Glide.get(context.getApplicationContext());
              applicationManager =
                  factory.build(
                      glide,
                      new ApplicationLifecycle(),
                      new EmptyRequestManagerTreeNode(),
                      context.getApplicationContext());
            }
          }
        }
    
        return applicationManager;
      }
    

    getApplicationManager(Context context) 通过单例模式创建并返回了 applicationManager。

    如果context为activity,则调用fragmentGet方法,返回RequestManager。

      private RequestManager fragmentGet(@NonNull Context context,
          @NonNull android.app.FragmentManager fm,
          @Nullable android.app.Fragment parentHint,
          boolean isParentVisible) {
        RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
        RequestManager requestManager = current.getRequestManager();
        if (requestManager == null) {
          // TODO(b/27524013): Factor out this Glide.get() call.
          Glide glide = Glide.get(context);
          requestManager =
              factory.build(
                  glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
          current.setRequestManager(requestManager);
        }
        return requestManager;
      }
    
    
    with方法过程总结:

    通过RequestManagerRetriever的get获取RequestManagerRetriever单例对象
    通过retriever.get(context)获取RequestManager,在get(context)方法中通过对context类型的判断做不同的处理:

    • context是Application,通过getApplicationManager(Context context) 创建并返回一个RequestManager对象
    • context是Activity,通过fragmentGet(activity, fm)在当前activity创建并添加一个没有界面的fragment,从而实现图片加载与activity的生命周期相绑定,之后创建并返回一个RequestManager对象
      最终在RequestManager中完成对Glide对象的初始化Glide.get(context)。

    load方法

    load方法调用的是RequestManager中的load方法

     public RequestBuilder<Drawable> load(@Nullable String string) {
        return asDrawable().load(string);
      }
    

    进入RequestBuilder

      private Object model;
      private boolean isModelSet;
    
      public RequestBuilder<TranscodeType> load(@Nullable String string) {
        return loadGeneric(string);
      }
    
      private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
        this.model = model;
        isModelSet = true;
        return this;
      }
    

    这个load方法其实是把我们传入的String类型的URL存入了内部的model成员变量中,再将数据来源是否已经设置的标志位 isModelSet 设置为true,意味着我们在调用 Glide.with(context).load(url) 之后数据来源已经设置成功了。

    into()

    into方法调用的是RequestBuilder中的into方法

      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()) {
            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.
          }
        }
    
        return into(
            glideContext.buildImageViewTarget(view, transcodeClass),
            /*targetListener=*/ null,
            requestOptions);
      }
    

    方法的核心是最后一行: into(glideContext.buildImageViewTarget(view, transcodeClass)) ,首先是通过 glideContext.buildImageViewTarget(view, transcodeClass) 创建出一个
    ViewTarget 类型的对象,然后把这个target传入GenericRequestBuilder中的into方法中。

    buildImageViewTarget方法

      public <X> ViewTarget<ImageView, X> buildImageViewTarget(
          @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
        return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
      }
    

    这个方法的目的是把我们传入的imageView包装成一个Target。

      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();
        Request request = buildRequest(target, targetListener, options);
    
        Request previous = target.getRequest();
        if (request.isEquivalentTo(previous)
            && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
          request.recycle();
    
          if (!Preconditions.checkNotNull(previous).isRunning()) {
            previous.begin();
          }
          return target;
        }
    
        requestManager.clear(target);
        target.setRequest(request);
        requestManager.track(target, request);
    
        return target;
      }
    

    过程分析:获取当前target中的Request对象,如果存在,则清空并终止这个Request对象的执行;创建新的Request对象并与当前target绑定;执行新创建的图片处理请求Request。

    然后在RequestTracker中调用runRequest方法,这个类主要负责Request的执行,暂停,取消等等关于图片请求的操作。

      public void runRequest(@NonNull Request request) {
        requests.add(request);
        if (!isPaused) {
          request.begin();
        } else {
          request.clear();
          if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "Paused, delaying request");
          }
          pendingRequests.add(request);
        }
      }
    
      @Override
      public void begin() {
        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) {
          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)) {
          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));
        }
      }
    

    begin的实际实现在SingleRequest中,方法的逻辑大致是这样的:

    获取图片的长宽尺寸,如果长宽已经确定,走 onSizeReady(overrideWidth, overrideHeight) 流程;如果未确定,先获取长宽,再走 onSizeReady(overrideWidth, overrideHeight)
    图片开始加载,首先显示占位图。

      @Override
      public void onSizeReady(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;
    
        float sizeMultiplier = requestOptions.getSizeMultiplier();
        this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
        this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
    
        if (IS_VERBOSE_LOGGABLE) {
          logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
        }
        loadStatus = engine.load(
            glideContext,
            model,
            requestOptions.getSignature(),
            this.width,
            this.height,
            requestOptions.getResourceClass(),
            transcodeClass,
            priority,
            requestOptions.getDiskCacheStrategy(),
            requestOptions.getTransformations(),
            requestOptions.isTransformationRequired(),
            requestOptions.isScaleOnlyOrNoTransform(),
            requestOptions.getOptions(),
            requestOptions.isMemoryCacheable(),
            requestOptions.getUseUnlimitedSourceGeneratorsPool(),
            requestOptions.getUseAnimationPool(),
            requestOptions.getOnlyRetrieveFromCache(),
            this);
    
        // 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));
        }
      }
    

    核心代码最终调用了Engine类中的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) {
        Util.assertMainThread();
        long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
    
        EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
            resourceClass, transcodeClass, options);
    
        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;
        }
    
        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);
        }
    
        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);
    
        engineJob.addCallback(cb);
        engineJob.start(decodeJob);
    
        if (VERBOSE_IS_LOGGABLE) {
          logWithTimeAndKey("Started new load", startTime, key);
        }
        return new LoadStatus(cb, engineJob);
      }
    

    load方法内部会从内存、本地、网络三个来源获取图片数据,使用的LruCache原理。获取图片资源后,通过onResourceReady方法回调。

      private void onResourceReady(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");
        }
    
        isCallingCallbacks = true;
        try {
          if ((requestListener == null
              || !requestListener.onResourceReady(result, model, target, dataSource, isFirstResource))
              && (targetListener == null
              || !targetListener.onResourceReady(result, model, target, dataSource, isFirstResource))) {
            Transition<? super R> animation =
                animationFactory.build(dataSource, isFirstResource);
            target.onResourceReady(result, animation);
          }
        } finally {
          isCallingCallbacks = false;
        }
    
        notifyLoadSuccess();
      }
    

    核心代码: target.onResourceReady(result, animation),其实在这句代码的内部最终是通过:

    public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {
        public DrawableImageViewTarget(ImageView view) {
            super(view);
        }
    
        @Override
        protected void setResource(Drawable resource) {
           view.setImageDrawable(resource);
        }
    }
    

    本质是通过 setResource(Drawable resource) 来实现的,在这个方法的内部调用了Android内部最常用的加载图片的方法 view.setImageDrawable(resource) 。

    into总结

    1.将imageview包装成imageViewTarget。
    2.清除这个imageViewTarget之前绑定的请求,绑定新的请求
    执行新的请求。
    3.获取图片数据之后,成功则会调用ImageViewTarget中的onResourceReady()方法,失败则会调用ImageViewTarget中的onLoadFailed();二者的本质都是通过调用Android中的imageView.setImageDrawable(drawable)来实现对imageView的图片加载。

    参考文章

    相关文章

      网友评论

        本文标题:Glide源码解析

        本文链接:https://www.haomeiwen.com/subject/cdhmrktx.html