Android最火的框架系列(六)Glide

 

        Android开源的图片加载框架有很多,常见的四种分别是:ImageLoader、Picasso、Glide和Fresco。其实,这些框架加载普通的图片,使用方法都差不多。这篇文章,我也不会花很多的篇幅去比较几种框架的优缺点。

        这几种框架我都使用过,都是比较简单的几行代码就可以实现图片的加载。在实际项目开发中,我基本是使用Glide。放一下GitHub上的star数。ImageLoader(16.5K),Picasso(16.9K),Glide(26.5K),Fresco(15.7K)。嗯,还是Glide的星星多一点。接下来,简单的总结一下Glide的基本使用。

一.Glide的导入

    首先,Glide的GitHub地址:https://github.com/bumptech/glide,可以看到,当前最新版本是4.9.0。我们在gradle文件中添加依赖:

implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'

二.Glide的使用

1.Glide加载普通图片

        Glide加载普通图片,需要三个参数:Context,URL,ImageView。无论是网络图片,还是drawable下的图片,还是手机内存里的图片,都只需要指定正确的URL即可。

Glide.with(LoginActivity.this)

        .load(R.drawable.login_logo)

        .into(mLoginLogo);

2.Glide加载圆角图片

    Glide加载圆角图片,除了Context,url,ImageView外,还需要另一个参数指定圆角的半径:

Glide.with(LoginActivity.this)
        .load(R.drawable.login_logo)
        .apply(RequestOptions.bitmapTransform(new RoundedCorners(20)))
        .into(mLoginLogo);

3.Glide加载圆形图片

Glide.with(LoginActivity.this)
        .load(R.drawable.login_logo)
        .apply(RequestOptions.bitmapTransform(new CircleCrop()))
        .into(mLoginLogo);

4.Glide加载gif图片

    Glide支持加载gif图片,使用方法与加载普通图片一样,也可以为其指定圆角或者圆形。

Glide.with(LoginActivity.this)
        .load(R.drawable.test)
        .into(mLoginLogo);

三.Glide源码分析

1.with()

    通过上面对Glide的基本使用,我们对Glide的源码做一下分析。首先,我们看下.with()方法的源码。如我们前面所说的,with方法需要我们传入context,可以看到,context可以是任何context:activity,fragmentActivity,fragment等。然后,会通过传入的context通过getRetriever().get()方法返回一个RequestManager对象。也就是说,通过with方法传入需要使用Glide加载图片的Activity或者Fragment,然后为这个Activity或者fragment提供一个用于加载图片的RequestManager。

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

  public static RequestManager with(@NonNull Activity activity) {
    return getRetriever(activity).get(activity);
  }

  public static RequestManager with(@NonNull FragmentActivity activity) {
    return getRetriever(activity).get(activity);
  }

  public static RequestManager with(@NonNull Fragment fragment) {
    return getRetriever(fragment.getActivity()).get(fragment);
  }

  public static RequestManager with(@NonNull android.app.Fragment fragment) {
    return getRetriever(fragment.getActivity()).get(fragment);
  }

  public static RequestManager with(@NonNull View view) {
    return getRetriever(view.getContext()).get(view);
  }

        我们看一下RequestManager这个类的介绍:

/**
 * A class for managing and starting requests for Glide. Can use activity, fragment and connectivity
 * lifecycle events to intelligently stop, start, and restart requests. Retrieve either by
 * instantiating a new object, or to take advantage built in Activity and Fragment lifecycle
 * handling, use the static Glide.load methods with your Fragment or Activity.

        一个为Glide管理和开始请求的类。可以通过activity,fragment等的生命周期来停止、开始和重新开始请求。所谓的请求,就是加载图片的请求。而RequestManager可以通过Activity等的生命周期来自动的管理图片的加载。例如,在Activity执行onDestroy的时候,Glide应该停止图片的加载。

2.load()

        接下来,看一下load方法。通过前面的介绍,我们知道,load方法接收的是图片的路径,这个路径可以是任意的路径。通过源码我们可以知道,load接收的参数,可以是下面的任意一种:bitmap,drawable,string,uri,file,resourceId,url,byte数组,Object。

  public RequestBuilder load(@Nullable Bitmap bitmap) {
    return asDrawable().load(bitmap);
  }

  public RequestBuilder load(@Nullable Drawable drawable) {
    return asDrawable().load(drawable);
  }

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

  public RequestBuilder load(@Nullable Uri uri) {
    return asDrawable().load(uri);
  }

  public RequestBuilder load(@Nullable File file) {
    return asDrawable().load(file);
  }

  public RequestBuilder load(@RawRes @DrawableRes @Nullable Integer resourceId) {
    return asDrawable().load(resourceId);
  }

  public RequestBuilder load(@Nullable URL url) {
    return asDrawable().load(url);
  }

  public RequestBuilder load(@Nullable byte[] model) {
    return asDrawable().load(model);
  }

  public RequestBuilder load(@Nullable Object model) {
    return asDrawable().load(model);
  }

        继续跟踪asDrawable().load()方法,先看一下第一个asDrawable方法:

  public RequestBuilder asDrawable() {
    return as(Drawable.class);
  }

继续跟踪as方法,可以看到,在这里获取了一个RequestBuilder对象。

  public  RequestBuilder as(
      @NonNull Class resourceClass) {
    return new RequestBuilder<>(glide, this, resourceClass, context);
  }

我们跟踪第二个方法load方法,也是跳转到RequestBuilder类:

  public RequestBuilder load(@Nullable String string) {
    return loadGeneric(string);
  }

        继续跟踪loadGeneric方法,通过这一系列的方法,最后,我们的url传到了这里,同时,isModelSet置为true,也就是标志着我们的数据源设置成功,最后返回RequestBuilder自己。

  private RequestBuilder loadGeneric(@Nullable Object model) {
    this.model = model;
    isModelSet = true;
    return this;
  }

3.into()

        最后,我们看下into方法的源码。这个方法里面,会获取imageview的ScaleType属性,根据不同的ScaleType得到不同的requestOptions参数。

  public ViewTarget into(@NonNull ImageView view) {
    Util.assertMainThread();
    Preconditions.checkNotNull(view);

    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.
      }
    }

    return into(
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        Executors.mainThreadExecutor());
  }

        上面,最后return into(),我们继续跟踪。可以看到,前面通过load设置的isModelSet在这起作用了。如果我们没有调用load方法,也就是没有设置数据源,会抛出一个异常。

  private > Y into(
      @NonNull Y target,
      @Nullable RequestListener targetListener,
      BaseRequestOptions options,
      Executor callbackExecutor) {
    Preconditions.checkNotNull(target);
    if (!isModelSet) {
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }

    Request request = buildRequest(target, targetListener, options, callbackExecutor);

    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;
    }

    requestManager.clear(target);
    target.setRequest(request);
    requestManager.track(target, request);

    return target;
  }

        上面的方法,我们看到一个target,首先,我们先跟踪一下target是个什么东西:

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

        继续跟踪buildTarget方法,通过对图片类型的判断,创建并返回与图片来源对应的imageViewTarget:

public class ImageViewTargetFactory {
  @NonNull
  @SuppressWarnings("unchecked")
  public  ViewTarget buildTarget(@NonNull ImageView view,
      @NonNull Class clazz) {
    if (Bitmap.class.equals(clazz)) {
      return (ViewTarget) new BitmapImageViewTarget(view);
    } else if (Drawable.class.isAssignableFrom(clazz)) {
      return (ViewTarget) new DrawableImageViewTarget(view);
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
  }
}

        into方法的target我们分析到此为止,继续看一下into方法的其他代码,我们会发现其中这么两行代码:

Request request = buildRequest(target, targetListener, options, callbackExecutor);

target.setRequest(request);
requestManager.track(target, request);

        继续看一下buildRequest方法,我们发现,这个加载图片的request里面有很多参数,例如target和宽高信息。后面,target.setRequest()。最后,调用RequestManager的track方法。

  private Request buildRequest(
      Target target,
      @Nullable RequestListener targetListener,
      BaseRequestOptions requestOptions,
      Executor callbackExecutor) {
    return buildRequestRecursive(
        target,
        targetListener,
        /*parentCoordinator=*/ null,
        transitionOptions,
        requestOptions.getPriority(),
        requestOptions.getOverrideWidth(),
        requestOptions.getOverrideHeight(),
        requestOptions,
        callbackExecutor);
  }

        前面我们就知道了,RequestManager是管理图片加载开始和停止的类。我们继续看一下track方法。track方法里,我们看到了runRequest方法。不管英语好不好,看到runRequest基本都能猜到意思:执行一个请求。

  synchronized void track(@NonNull Target target, @NonNull Request request) {
    targetTracker.track(target);
    requestTracker.runRequest(request);
  }

        接下来,看一下runRequest方法。如果请求的标志不是暂停的,开始这个请求,如果请求的标志是暂停的,那么清除这个请求,也就是停止加载图片的请求。

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

        接下来,毫无疑问的,我们需要看一下begin这个方法,点进去,是Request接口,实现begin方法的是实现Request的SingleRequest类,我们看一下begin 方法:

  public synchronized 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 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));
    }
  }

        大体的过程如下:

        (1)获取宽高,如果有重新设置宽高并且均大于零,那么宽高使用重新设置的宽高。

        (2)宽高信息确定后,不论是处于RUNNING状态还是WAITING_FOR_SIZE状态,首先会加载占位图。

        (3)在WAITING_FOR_SIZE下,如果宽高合法,那么会走onSizeReady方法。至此,我们还没看到解码图片的代码,我们跟踪onSizeReady方法:

  public synchronized 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,
            callbackExecutor);

    // 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.load方法,继续跟踪:

  public synchronized  LoadStatus load(
      GlideContext glideContext,
      Object model,
      Key signature,
      int width,
      int height,
      Class resourceClass,
      Class transcodeClass,
      Priority priority,
      DiskCacheStrategy diskCacheStrategy,
      Map, Transformation> transformations,
      boolean isTransformationRequired,
      boolean isScaleOnlyOrNoTransform,
      Options options,
      boolean isMemoryCacheable,
      boolean useUnlimitedSourceExecutorPool,
      boolean useAnimationPool,
      boolean onlyRetrieveFromCache,
      ResourceCallback cb,
      Executor callbackExecutor) {
    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, callbackExecutor);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

    EngineJob engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    DecodeJob 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, callbackExecutor);
    engineJob.start(decodeJob);

    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }

        上面的代码,我们发现两个疑似加载图片的地方:loadFromActiveResources,loadFromCache。这其实是两种加载图片的方式,从弱引用加载和从缓存加载。这两个方法位于Engine类中,Engine类负责开始加载图片和管理弱引用和缓存的资源。

        engineJob添加了一个回调接口:engineJob.addCallback(cb, callbackExecutor)。跟进去,最后实际上就是上面两种解码方式的onResourceReady回调:

  public synchronized void onResourceReady(Resource resource, DataSource dataSource) {
    stateVerifier.throwIfRecycled();
    loadStatus = null;
    if (resource == null) {
      GlideException exception = new GlideException("Expected to receive a Resource with an "
          + "object of " + transcodeClass + " inside, but instead got null.");
      onLoadFailed(exception);
      return;
    }

    Object received = resource.get();
    if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
      releaseResource(resource);
      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()) {
      releaseResource(resource);
      // We can't put the status to complete before asking canSetResource().
      status = Status.COMPLETE;
      return;
    }

    onResourceReady((Resource) resource, (R) received, dataSource);
  }

        核心还是最后一行代码,继续跟踪进去:

  private synchronized void onResourceReady(Resource 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 {
      boolean anyListenerHandledUpdatingTarget = false;
      if (requestListeners != null) {
        for (RequestListener listener : requestListeners) {
          anyListenerHandledUpdatingTarget |=
              listener.onResourceReady(result, model, target, dataSource, isFirstResource);
        }
      }
      anyListenerHandledUpdatingTarget |=
          targetListener != null
              && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);

      if (!anyListenerHandledUpdatingTarget) {
        Transition animation =
            animationFactory.build(dataSource, isFirstResource);
        target.onResourceReady(result, animation);
      }
    } finally {
      isCallingCallbacks = false;
    }

    notifyLoadSuccess();
  }

        核心代码是target.onResourceReady(result, animation)。实际执行这个方法的是抽象类ImageViewTarget:

  @Override
  public void onResourceReady(@NonNull Z resource, @Nullable Transition transition) {
    if (transition == null || !transition.transition(resource, this)) {
      setResourceInternal(resource);
    } else {
      maybeUpdateAnimatable(resource);
    }
  }

        继续跟踪方法setResourceInternal(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.
    setResource(resource);
    maybeUpdateAnimatable(resource);
  }

        继续跟踪setResource方法,这是抽象类ImageViewTarget一个抽象方法,跟踪到实现类DrawableImageViewTarget。最后,我们发现,通过imageview的setDrawable方法,将drawable显示到ImageView中。  

public class DrawableImageViewTarget extends ImageViewTarget {

  public DrawableImageViewTarget(ImageView view) {
    super(view);
  }

  /**
   * @deprecated Use {@link #waitForLayout()} instead.
   */
  // Public API.
  @SuppressWarnings({"unused", "deprecation"})
  @Deprecated
  public DrawableImageViewTarget(ImageView view, boolean waitForLayout) {
    super(view, waitForLayout);
  }

  @Override
  protected void setResource(@Nullable Drawable resource) {
    view.setImageDrawable(resource);
  }
}

        后面,有个EnginJob和DecodeJob,并且通过engineJob.start(decodeJob)来开始解码,跟踪进去,最终是通过线程池来执行解码的任务:

  public synchronized void start(DecodeJob decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }

    最后,总结一下,使用Glide加载图片确实很方便,只需要几行代码即可。其实我们没有必要太去关注Glide的源码,只要会基本的使用就可以了,跟踪源码其实是一个很漫长很复杂的过程,跟踪到最后,好几个小时,都有点坚持不下去的感觉了。最后,坚持跟踪完into方法。好了,周末结束,洗漱睡觉,下次再见!

你可能感兴趣的:(Android框架)