Glide

Glide 源码分析

2019-02-16  本文已影响104人  王英豪

Glide 作为一个出色的图片加载框架,对其剖析的文章数不胜数。而若像大多数文章一样常规的去分析源码就没什么新意了,本文旨在发掘一些新的或相对陌生的知识点,以完善对 Glide 的认知,涉及源码基于 v4.8.0。

主要内容:

1.磁盘缓存

2.内存缓存

3.网络请求

4.图片转换

5.感知生命周期

发起一个图片加载请求后,我们期望当该请求所处的界面 onStop 时请求也随之停止,再次 onStart 时请求能够随之继续, onDestroy 时请求能够随之销毁。这就需要能够感知当前 Activity 的生命周期变化,由于 Fragment 在 onAttach 之后与 Activity 有相同的生命周期,glide 利用这一点,通过给 Activity 添加一个无界面的 Fragment 实现感知。

发起请求时通过 with 方法传入上下文,此方法会返回一个 RequestManager,RequestManager 用于管理和启动图片加载请求,可以感知外部 Activity 的生命周期,从而管理请求随之启动、停止和重启。

先来分析一个较为简单的流程:with 方法传入 Activity,会调用到 RequestManagerRetriever 的 get 方法:

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

其中调用 fragmentGet 方法去新建 RequestManager :

@NonNull
  private RequestManager fragmentGet(@NonNull Context context,
      @NonNull android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    //这里新建了一个无界面的 Fragment,并添加到该界面
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      Glide glide = Glide.get(context);
      requestManager =
    //这里新建了一个 requestManager,并将无界面 Fragment 的生命周期暴露给 requestManager
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }

当无界面 Fragment 生命周期变化时,通过接口回调出去给 requestManager,这样 requestManager 就实现了随外部生命周期变化自动启动、停止和重启请求。with 方法若传入其他参数,流程上也是大同小异,都是找到当前 Activity 或 Fragment ,给其添加一个无界面 Fragment 罢了。

而不管传入何参数,都有这样一个逻辑:

  if (Util.isOnBackgroundThread()) {
       return get(view.getContext().getApplicationContext());
  }

若当前处于非主线程,则一律基于应用生命周期请求,不再关心所在 Fragment 或 Activity 的生命周期,这是因为子线程中执行的任务本身就是跟所在界面生命周期无关的。

在分析这一块时涉及 ContextWrapper 相关的逻辑,若不太熟悉,可参考:ContextWrapper

Glide 推出时谷歌还未发布 Architecture Components,而现在若要实现一个可感知生命周期的逻辑,大可不必像 Glide 一样添加一个 Fragment ,直接使用 Architecture Components 中的 Lifecycle 组件就可以很方便的实现了。

6.下载及预加载

下载的标准写法如下,也是官方示例写法:

    @WorkerThread
    private void downloadFile() {
        FutureTarget<File> target = null;
        try {
            target = Glide.with(context)
                    .downloadOnly()
                    .load(imgUrl)
                    .submit();
            final File cacheFile = target.get();
            /*
             *  默认会下载到磁盘缓存中,理论上不应对缓存文件进行编辑、删除
             */
        } catch (InterruptedException | ExecutionException e) {
            Log.e(TAG, "download: ", e);
        } finally {
            // 这里要调用cancel方法取消等待操作并释放资源
            if (target != null) {
                target.cancel(true); // 若传true则允许中断操作
            }
        }
    }

此方式要自行开子线程,你可能会觉得稍显麻烦,直接调用 listener 方法监听 onResourceReady 回调岂不是更简单?其实不是的,由于要拿到 FutureTarget 调用其 cancel 方法,若监听 onResourceReady 代码逻辑会更复杂。

对于 FutureTarget.get() 方法,并不是调用时才会去加载数据,调用 submit 方法后就已经开始去加载数据了,get 方法最终会调用到 RequestFutureTarget 的 doGet 方法如下:

  private synchronized R doGet(Long timeoutMillis)
      throws ExecutionException, InterruptedException, TimeoutException {
    if (assertBackgroundThread && !isDone()) {
      Util.assertBackgroundThread();
    }

    if (isCancelled) {
      throw new CancellationException();
    } else if (loadFailed) {
      throw new ExecutionException(exception);
    } else if (resultReceived) {
      return resource;
    }

    if (timeoutMillis == null) {
      waiter.waitForTimeout(this, 0);
    } else if (timeoutMillis > 0) {
      long now = System.currentTimeMillis();
      long deadline = now + timeoutMillis;
      while (!isDone() && now < deadline) {
        waiter.waitForTimeout(this, deadline - now);
        now = System.currentTimeMillis();
      }
    }

    if (Thread.interrupted()) {
      throw new InterruptedException();
    } else if (loadFailed) {
      throw new ExecutionException(exception);
    } else if (isCancelled) {
      throw new CancellationException();
    } else if (!resultReceived) {
      throw new TimeoutException();
    }
    return resource;
  }

可以看到 get 方法内部并没有加载数据的逻辑, RequestFutureTarget 内部通过锁实现了 get 方法的阻塞调用,当资源加载完毕后 onResourceReady 中会解除阻塞:

  @Override
  public synchronized boolean onResourceReady(
      R resource, Object model, Target<R> target, DataSource dataSource, boolean isFirstResource) {
    // We might get a null result.
    resultReceived = true;
    this.resource = resource;
    waiter.notifyAll(this);
    return false;
  }

除了下载 File 类型以外,还可以指定下载类型,比如下载 Bitmap:

    @WorkerThread
    private void downloadBitmap() {
        RequestOptions DOWNLOAD_ONLY_OPTIONS = RequestOptions
                .diskCacheStrategyOf(DiskCacheStrategy.DATA) //这边其实可以根据业务场景配置,如果是网络图片一般需要缓存
                .priority(Priority.LOW) // 设置优先级
                .skipMemoryCache(true);
        FutureTarget<Bitmap> target = null;
        try {
            target = Glide.with(context)
                    .asBitmap()
                    .apply(DOWNLOAD_ONLY_OPTIONS)
                    .load(imgUrl)
                    .submit();
            final Bitmap bitmap = target.get();

        } catch (InterruptedException | ExecutionException e) {
            Log.e(TAG, "download: ", e);
        } finally {
            // 这里要调用cancel方法取消等待操作并释放资源
            if (target != null) {
                target.cancel(true); // 若传true则允许中断操作
            }
        }
    }

这里的 DOWNLOAD_ONLY_OPTIONS 配置其实就是 downloadOnly 方法应用的配置。

实现预加载十分简单:

    Glide.with(context).load(imgUrl).preload();

关键代码位于 PreloadTarget 中:

public final class PreloadTarget<Z> extends SimpleTarget<Z> {
  private static final int MESSAGE_CLEAR = 1;
  private static final Handler HANDLER = new Handler(Looper.getMainLooper(), new Callback() {
    @Override
    public boolean handleMessage(Message message) {
      if (message.what == MESSAGE_CLEAR) {
        ((PreloadTarget<?>) message.obj).clear();
        return true;
      }
      return false;
    }
  });

  private final RequestManager requestManager;

  public static <Z> PreloadTarget<Z> obtain(RequestManager requestManager, int width, int height) {
    return new PreloadTarget<>(requestManager, width, height);
  }

  private PreloadTarget(RequestManager requestManager, int width, int height) {
    super(width, height);
    this.requestManager = requestManager;
  }

  @Override
  public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    HANDLER.obtainMessage(MESSAGE_CLEAR, this).sendToTarget();
  }

  @SuppressWarnings("WeakerAccess")
  @Synthetic void clear() {
    requestManager.clear(this);
  }
}

相比于 RequestFutureTarget,PreloadTarget 里的逻辑就简单多了,可以看到加载资源结束后只是把此次请求释放掉了,不用像其他 Target 一样做额外的操作。

7.加载图片到通知栏和应用小部件中

上面说到的下载、预加载主要通过 RequestFutureTarget、PreloadTarget 实现,平时使用 Glide 直接加载图片到 ImageView 的方式则是通过 ImageViewTarget,Glide 中还提供了 NotificationTarget 和 AppWidgetTarget 来实现加载图片到通知栏和应用小部件中。使用方法十分简单,下面列出加载图片到通知栏的实现示例:

    /**
     * 加载图片到通知栏
     */
    private void loadNotificationImg() {
        //构建一个通知栏
        final int NOTIFICATION_ID = 1;
        final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.remoteview_notification);
        rv.setImageViewResource(R.id.iv, R.mipmap.ic_launcher);
        rv.setTextViewText(R.id.tv, "Short Message");
        NotificationCompat.Builder mBuilder =
                new NotificationCompat.Builder(context, "channel_id")
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setContentTitle("Content Title")
                        .setContentText("Content Text")
                        .setContent(rv)
                        .setPriority(NotificationCompat.PRIORITY_HIGH);
        final Notification notification = mBuilder.build();
        notification.bigContentView = rv;
        NotificationManager service = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        service.notify(NOTIFICATION_ID, notification);

        //加载图片到通知栏
        NotificationTarget notificationTarget = new NotificationTarget(
                context,
                R.id.iv,
                rv,
                notification,
                NOTIFICATION_ID);
        Glide.with(context).asBitmap().load(imgUrl).into(notificationTarget);
    }

实际的更新方法封装于 NotificationTarget 中:

  /**
   * Updates the Notification after the Bitmap resource is loaded.
   */
  private void update() {
    NotificationManager manager =
        (NotificationManager) this.context.getSystemService(Context.NOTIFICATION_SERVICE);
    Preconditions.checkNotNull(manager)
        .notify(this.notificationTag, this.notificationId, this.notification);
  }

通过 AppWidgetTarget 加载图片到应用小部件中与此类似,这些均继承自 Target 接口,了解原理后,我们可以自定义 Target 来随意的定制功能了。

8.图片格式及内存优化

记得刚接触 Glide 时,总会看到这个描述:

Glide 默认的 Bitmap 格式是 RGB_565,相比于 Picasso,加载的图片质量略差,但比 ARGB_8888 格式的内存开销要小一半。

而现在再讲这个特性就不对了,因为在 Glide v4 中,默认的 Bitmap 格式改为了 ARGB_8888。准确来说是默认的解码格式由 PREFER_RGB_565 改为了 PREFER_ARGB_8888,具体可参考 官方文档

Glide 中可配置的解码格式只提供了 PREFER_RGB_565 和 PREFER_ARGB_8888 两个选项,而 Android Bitmap Config 中提供了 RGB_565、ARGB_8888、ARGB_4444 以及 HARDWARE 等 7 种格式,这让我们在使用层面上有一定程度的简化, Glide 内部自行适配了其他解码格式,比如若配置为 PREFER_ARGB_8888,在 Android 8.0 系统上就会尝试开启硬件位图编码格式,对应代码于 DecodeJob 中:

  @NonNull
  private Options getOptionsWithHardwareConfig(DataSource dataSource) {
    Options options = this.options;
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
      return options;
    }

    boolean isHardwareConfigSafe =
        dataSource == DataSource.RESOURCE_DISK_CACHE || decodeHelper.isScaleOnlyOrNoTransform();
    Boolean isHardwareConfigAllowed = options.get(Downsampler.ALLOW_HARDWARE_CONFIG);

    // If allow hardware config is defined, we can use it if it's set to false or if it's safe to
    // use the hardware config for the request.
    if (isHardwareConfigAllowed != null && (!isHardwareConfigAllowed || isHardwareConfigSafe)) {
      return options;
    }

    // If allow hardware config is undefined or is set to true but it's unsafe for us to use the
    // hardware config for this request, we need to override the config.
    options = new Options();
    options.putAll(this.options);
    options.set(Downsampler.ALLOW_HARDWARE_CONFIG, isHardwareConfigSafe);

    return options;
  }

官方文档关于 硬件位图 的介绍也比较清晰,就不过多描述了。另外 Android 端涉及到图片内存,必须了解的一个问题就是:你的 Bitmap 究竟占多大内存?

9.请求优先级及原理

若一个界面中需要展示多张图片,我们可能会期望某张图片优先加载,这就需要设置 Glide 的请求优先级, Glide 中提供四种优先级:

使用十分简单:

    RequestOptions options = new RequestOptions().priority(Priority.HIGH);
    Glide.with(context).load(imgUrl).apply(options).into(imageView);

下面来分析一下我们配置的 Priority.HIGH 到底是如何生效的,跟踪发现优先级参数 priority 会被传入到 RequestBuilder 的 buildThumbnailRequestRecursive 方法,其中主要逻辑如下:

 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) {
      // 缩略图相关,先忽略
    } else if (thumbSizeMultiplier != null) {
      // 缩略图相关,先忽略
    } else {
      // Base case: no thumbnail.
      return obtainRequest(
          target,
          targetListener,
          requestOptions,
          parentCoordinator,
          transitionOptions,
          priority,
          overrideWidth,
          overrideHeight);
    }
  }

由于并未设置 thumbnail,先忽略缩略图相关逻辑,此方法中会调用到 obtainRequest 方法,继续跟踪,发现我们配置的 priority 参数在 SingleRequest 中的 onSizeReady 方法中被传入到 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) {
   
    // ...省略其他逻辑

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

这里在构建 DecodeJob 时将优先级配置传入,最终传入到 DecodeJob 和 DecodeHelper 中,其中 DecodeHelper 被 DecodeJob 持有。

接下来分析 priority 参数分别在什么时候被用到,首先看 DecodeJob 中持有的 priority,其仅在实现 Comparable 接口时用到,这个比较容易理解,可以通过对 DecodeJob 排序来实现优先级的调整。DecodeHelper 中持有的 priority 在 DataFetcher 的 loadData 方法中被传入:

  void loadData(@NonNull Priority priority, @NonNull DataCallback<? super T> callback);

DataFetch 用于加载数据,其实现有很多:

但并不是所有方法都能应用优先级的,这取决与具体的业务组件,比如 OkHttp 不支持请求优先级设置,直接忽略了 priority 参数:

  @Override
  public void loadData(@NonNull Priority priority,
      @NonNull final DataCallback<? super InputStream> callback) {
    Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
    for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
      String key = headerEntry.getKey();
      requestBuilder.addHeader(key, headerEntry.getValue());
    }
    Request request = requestBuilder.build();
    this.callback = callback;

    call = client.newCall(request);
    call.enqueue(this);
  }

而 Volley 就支持请求优先级:

  @Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    request = requestFactory.create(url.toStringUrl(), callback, glideToVolleyPriority(priority),
        url.getHeaders());
    requestQueue.add(request);
  }

由此也可以得出设置请求优先级并不是必然生效的。

10.缩略图使用及原理

缩略图的使用可参考官方文档

若使用简化方式设置缩略图:

    Glide.with(context).load(imgUrl).thumbnail(0.2f).into(imageView);

发起缩略图请求的关键逻辑位于 RequestBuilder 的 buildThumbnailRequestRecursive 方法中:

    // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
      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;

其中 fullRequest、thumbnailRequest 分别为原图、缩略图请求,ThumbnailRequestCoordinator 就是字面意思,专门用于协调原图、缩略图的请求,并合并成一个请求,可以看到在构建缩略图请求时,为了尽量让缩略图比原图加载的更快一点,调用 getThumbnailPriority 方法调整了请求优先级:

  @NonNull
  private Priority getThumbnailPriority(@NonNull Priority current) {
    switch (current) {
      case LOW:
        return Priority.NORMAL;
      case NORMAL:
        return Priority.HIGH;
      case HIGH:
      case IMMEDIATE:
        return Priority.IMMEDIATE;
      default:
        throw new IllegalArgumentException("unknown priority: " + requestOptions.getPriority());
    }
  }

加载完数据后,Glide 会分别解码缩略图、原图两种尺寸的图片,具体解码实现位于 Downsampler 的 decode 方法:

  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()");

    byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
    bitmapFactoryOptions.inTempStorage = bytesForOptions;

    DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
    DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
    boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
    boolean isHardwareConfigAllowed =
      options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);

    try {
      Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
          downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
          requestedHeight, fixBitmapToRequestedDimensions, callbacks);
      return BitmapResource.obtain(result, bitmapPool);
    } finally {
      releaseOptions(bitmapFactoryOptions);
      byteArrayPool.put(bytesForOptions);
    }
  }

解码完后会调用到 DecodeJob 的 notifyComplete 方法,进一步调用到 EngineJob 的 onResourceReady 方法,在 onResourceReady 方法中通过 handle 转到主线程,然后调用到 EngineJob 的 handleResultOnMainThread 方法,接着调用 SingleRequest 的 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 {
      boolean anyListenerHandledUpdatingTarget = false;
      if (requestListeners != null) {
        for (RequestListener<R> listener : requestListeners) {
          anyListenerHandledUpdatingTarget |=
              listener.onResourceReady(result, model, target, dataSource, isFirstResource);
        }
      }
      anyListenerHandledUpdatingTarget |=
          targetListener != null
              && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);

      if (!anyListenerHandledUpdatingTarget) {
        Transition<? super R> animation =
            animationFactory.build(dataSource, isFirstResource);
        target.onResourceRReady(result, animation);
      }
    } finally {
      isCallingCallbacks = false;
    }

    notifyLoadSuccess();
  }

可以看到此方法中终于将加载资源回调给 Target 了,调用栈大致如下:

以上不止是设置缩略图时的加载流程,没有缩略图时走的也是这个流程。我们知道图片缩略图不过是通过 BitmapFactory.Options 解码一张尺寸较小、质量较差的图罢了,而 Glide 除了支持配置图片缩略图,还支持配置本地视频缩略图。取视频缩略图的关键逻辑位于 VideoDecoder 的 decodeFrame 方法:

 @Nullable
  private static Bitmap decodeFrame(
      MediaMetadataRetriever mediaMetadataRetriever,
      long frameTimeMicros,
      int frameOption,
      int outWidth,
      int outHeight,
      DownsampleStrategy strategy) {
    Bitmap result = null;
    // Arguably we should handle the case where just width or just height is set to
    // Target.SIZE_ORIGINAL. Up to and including OMR1, MediaMetadataRetriever defaults to setting
    // the dimensions to the display width and height if they aren't specified (ie
    // getScaledFrameAtTime is not used). Given that this is an optimization only if
    // Target.SIZE_ORIGINAL is not used and not using getScaledFrameAtTime ever would match the
    // behavior of Glide in all versions of Android prior to OMR1, it's probably fine for now.
    if (Build.VERSION.SDK_INT >= VERSION_CODES.O_MR1
        && outWidth != Target.SIZE_ORIGINAL
        && outHeight != Target.SIZE_ORIGINAL
        && strategy != DownsampleStrategy.NONE) {
      result =
          decodeScaledFrame(
              mediaMetadataRetriever, frameTimeMicros, frameOption, outWidth, outHeight, strategy);
    }

    if (result == null) {
      result = decodeOriginalFrame(mediaMetadataRetriever, frameTimeMicros, frameOption);
    }

    return result;
  }

可以看到在 Android 8.1 系统上支持直接获取缩放的视频缩略图,8.1 以下则直接获取帧原图,分别通过 MediaMetadataRetriever 的 getScaledFrameAtTime 、getFrameAtTime 获取。MediaMetadataRetriever 是 Android 提供的类,用来获取本地和网络 Media 文件信息,提供了用于从输入媒体文件检索帧和元数据的统一接口。

11.展示 gif 原理

在展示 gif 时,即使不调用 asGif 方法,Glide 也能识别出 gif 类型并正常展示。解码逻辑位于 Downsampler 的 decode 方法中,我们先从这里开始,看看 Glide 是如何识别 gif 类型的。decode 方法中调用了 decodeFromWrappedStreams 方法开始实际解码逻辑:

  private Bitmap decodeFromWrappedStreams(InputStream is,
      BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
      DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
      int requestedHeight, boolean fixBitmapToRequestedDimensions,
      DecodeCallbacks callbacks) throws IOException {
    long startTime = LogTime.getLogTime();

    int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
    int sourceWidth = sourceDimensions[0];
    int sourceHeight = sourceDimensions[1];
    String sourceMimeType = options.outMimeType;

    // If we failed to obtain the image dimensions, we may end up with an incorrectly sized Bitmap,
    // so we want to use a mutable Bitmap type. One way this can happen is if the image header is so
    // large (10mb+) that our attempt to use inJustDecodeBounds fails and we're forced to decode the
    // full size image.
    if (sourceWidth == -1 || sourceHeight == -1) {
      isHardwareConfigAllowed = false;
    }

    int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
    int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
    boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);

    int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth;
    int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight;

    ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);

    // ... 省略下面的解码逻辑代码

    return rotated;
  }

可以看到解码开始前,准备了图片角度、输出尺寸的信息,并通过 ImageHeaderParserUtils 获取了图片类型:

    ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);

Glide 中定义了以下图片类型:

  /**
   * The format of the image data including whether or not the image may include transparent
   * pixels.
   */
  enum ImageType {
    GIF(true),
    JPEG(false),
    RAW(false),
    /** PNG type with alpha. */
    PNG_A(true),
    /** PNG type without alpha. */
    PNG(false),
    /** WebP type with alpha. */
    WEBP_A(true),
    /** WebP type without alpha. */
    WEBP(false),
    /** Unrecognized type. */
    UNKNOWN(false);

    private final boolean hasAlpha;

    ImageType(boolean hasAlpha) {
      this.hasAlpha = hasAlpha;
    }

    public boolean hasAlpha() {
      return hasAlpha;
    }
  }

而实际获取图片类型的逻辑位于 DefaultImageHeaderParser 中:

  @NonNull
  private ImageType getType(Reader reader) throws IOException {
    final int firstTwoBytes = reader.getUInt16();

    // JPEG.
    if (firstTwoBytes == EXIF_MAGIC_NUMBER) {
      return JPEG;
    }

    final int firstFourBytes = (firstTwoBytes << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF);
    // PNG.
    if (firstFourBytes == PNG_HEADER) {
      // See: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha
      // -color-type
      reader.skip(25 - 4);
      int alpha = reader.getByte();
      // A RGB indexed PNG can also have transparency. Better safe than sorry!
      return alpha >= 3 ? PNG_A : PNG;
    }

    // GIF from first 3 bytes.
    if (firstFourBytes >> 8 == GIF_HEADER) {
      return GIF;
    }

    // WebP (reads up to 21 bytes). See https://developers.google.com/speed/webp/docs/riff_container
    // for details.
    if (firstFourBytes != RIFF_HEADER) {
      return UNKNOWN;
    }
    // Bytes 4 - 7 contain length information. Skip these.
    reader.skip(4);
    final int thirdFourBytes =
        (reader.getUInt16() << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF);
    if (thirdFourBytes != WEBP_HEADER) {
      return UNKNOWN;
    }
    final int fourthFourBytes =
        (reader.getUInt16() << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF);
    if ((fourthFourBytes & VP8_HEADER_MASK) != VP8_HEADER) {
      return UNKNOWN;
    }
    if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_EXTENDED) {
      // Skip some more length bytes and check for transparency/alpha flag.
      reader.skip(4);
      return (reader.getByte() & WEBP_EXTENDED_ALPHA_FLAG) != 0 ? ImageType.WEBP_A : ImageType.WEBP;
    }
    if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_LOSSLESS) {
      // See chromium.googlesource.com/webm/libwebp/+/master/doc/webp-lossless-bitstream-spec.txt
      // for more info.
      reader.skip(4);
      return (reader.getByte() & WEBP_LOSSLESS_ALPHA_FLAG) != 0 ? ImageType.WEBP_A : ImageType.WEBP;
    }
    return ImageType.WEBP;
  }

可以看到这里是通过文件头标示来获取图片类型的,而 gif 文件头如下:


查看代码,Glide 中便是通过 0x474946 文件头来判断的。

Glide 中将 gif 类型图片封装成了 GifDrawable,在 ByteBufferGifDecoder 中可以看到 GifDrawable 的生成逻辑:

  @Nullable
  private GifDrawableResource decode(
      ByteBuffer byteBuffer, int width, int height, GifHeaderParser parser, Options options) {
    long startTime = LogTime.getLogTime();
    try {
      final GifHeader header = parser.parseHeader();
      if (header.getNumFrames() <= 0 || header.getStatus() != GifDecoder.STATUS_OK) {
        // If we couldn't decode the GIF, we will end up with a frame count of 0.
        return null;
      }

      Bitmap.Config config = options.get(GifOptions.DECODE_FORMAT) == DecodeFormat.PREFER_RGB_565
          ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;

      int sampleSize = getSampleSize(header, width, height);
      GifDecoder gifDecoder = gifDecoderFactory.build(provider, header, byteBuffer, sampleSize);
      gifDecoder.setDefaultBitmapConfig(config);
      gifDecoder.advance();
      Bitmap firstFrame = gifDecoder.getNextFrame();
      if (firstFrame == null) {
        return null;
      }

      Transformation<Bitmap> unitTransformation = UnitTransformation.get();

      GifDrawable gifDrawable =
          new GifDrawable(context, gifDecoder, unitTransformation, width, height, firstFrame);

      return new GifDrawableResource(gifDrawable);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Decoded GIF from stream in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }

至此已经获取到 GifDrawable ,GifDrawable 中持有一个 GifFrameLoader,而 GifFrameLoader 中持有了 gif 解码器 StandardGifDecoder 。由此可以得出 gif 的展示逻辑就封装于 GifDrawable 中,主要通过 GifFrameLoader 实现。

12.自定义模块及延伸

13.兼容3.x写法

参考文章:
官方文档 https://muyangmin.github.io/glide-docs-cn/
源码分析 https://blog.csdn.net/sinyu890807/column/info/15318

上一篇下一篇

猜你喜欢

热点阅读