Glide 源码阅读笔记(六)

Glide 源码阅读笔记(六)

在第一篇文章中我简单介绍了 Glide 实例的创建过程,重点介绍了 Glide 的内存缓存实现和磁盘的缓存实现:Glide 源码阅读笔记(一)

在第二篇文章中介绍了 Glide 对生命周期的管理: Glide 源码阅读笔记(二)

在第三篇文章中介绍了 RequestRequestCoordinator,还简单介绍了 Registry 中的核心组件:Glide 源码阅读笔记(三)

第四篇文章中介绍了 Glide 如何加载内存缓存和磁盘缓存的资源。其中内存缓存又分为存活的内存缓存资源和被缓存的内存缓存资源;磁盘缓存分为 RESOURCE_CACHE 类型(有尺寸等参数作为 key)缓存与 DATA_CACHE 类型(从网络中加载的原始缓存数据,无尺寸等等信息):Glide 源码阅读笔记(四)

第五篇文章中介绍了 Glide 如何加载网络请求的数据和如何写入磁盘缓存(包括 ResourceCacheDataCache );还介绍了 RequestManager 具体如何处理不同的 Android 生命周期:Glide 源码阅读笔记(五)

前面几篇文章中基本跑通了整个 Glide,但是还有一部分非常核心的逻辑没有讲,那就是 Registry 中注册的核心组件,其中包括 ModelLoaderDecoderTranscoder。本篇文章中主要介绍一下其中有代表性的 ModelLoaderDecoder。那么废话少说,开始今天的内容吧。

ModelLoader

在聊 ModelLoader 之前,我们需要知道 Glidemodel 的概念,在前面的文章中其实都已经讲过了,这里再说一次,Glide.with(context).load(url) 其中 load() 方法传入的对象就是 model,我们不仅可以传入 String 类型作为 model,也可以传入 File 作为 modelModelLoader 就需要讲对应类型的 model 转换成 Decoder 能够接受的输入类型,通常 Decoder 能够接受的输入类型就是 InputStream

假如是 http 的协议的 url,就需要建立 http 的网络链接,然后获取到对应的 ResponseBodyInputStream,然后传递给 Decoder;如果是 File 作为 model 输入,那就更加简单了,直接打开文件流的 FileInputStream 就好了。

在开始之前我们先看看 ModelLoader 接口的定义:

Java 复制代码
public interface ModelLoader<Model, Data> {


  class LoadData<Data> {
    public final Key sourceKey;
    public final List<Key> alternateKeys;
    public final DataFetcher<Data> fetcher;

    public LoadData(@NonNull Key sourceKey, @NonNull DataFetcher<Data> fetcher) {
      this(sourceKey, Collections.<Key>emptyList(), fetcher);
    }

    public LoadData(
        @NonNull Key sourceKey,
        @NonNull List<Key> alternateKeys,
        @NonNull DataFetcher<Data> fetcher) {
      this.sourceKey = Preconditions.checkNotNull(sourceKey);
      this.alternateKeys = Preconditions.checkNotNull(alternateKeys);
      this.fetcher = Preconditions.checkNotNull(fetcher);
    }
  }


  @Nullable
  LoadData<Data> buildLoadData(
      @NonNull Model model, int width, int height, @NonNull Options options);


  boolean handles(@NonNull Model model);
}

其中 handles() 方法的返回值就表示当前的 ModelLoader 是否能够处理这个 modelbuildLoadData() 方法会把对 model 的处理方式封装在 LoadData 对象中;在 LoadData 中包含了 model 对应的 key,这对缓存的 key 的计算非常重要,其中最重要的是 DataFetcher,他是真正处理 model 转换的地方。

我们再简单看看 DataFetcher 的接口:

Java 复制代码
public interface DataFetcher<T> {


  interface DataCallback<T> {


    void onDataReady(@Nullable T data);


    void onLoadFailed(@NonNull Exception e);
  }

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


  void cleanup();


  void cancel();


  @NonNull
  Class<T> getDataClass();


  @NonNull
  DataSource getDataSource();
}

其中 loadData() 方法就是执行异步加载;getDataClass() 获取最终输出的对象的 ClassgetDataSource() 获取对应的数据来源,比如内存缓存,磁盘缓存和网络请求等等。

在了解了 ModelLoader 的相关接口后,我们来看看一些重要的 ModelLoader 是怎么实现的。

StringLoader

首先这里要了解 StringLoader 是将 String 类型的 model 转换成 InputStream

我们先看看 StreamFactory 的实现:

Java 复制代码
  public static class StreamFactory implements ModelLoaderFactory<String, InputStream> {

    @NonNull
    @Override
    public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
      return new StringLoader<>(multiFactory.build(Uri.class, InputStream.class));
    }

    @Override
    public void teardown() {
      // Do nothing.
    }
  }

我们看到 build 方法中创建了一个 StringLoader 实例,而还通过 MultiFactory 创建了一个其他的 ModelLoader 作为 StringLoader 的构造函数的参数。这个 ModelLoader 输入的 model 类型是 Uri,输出的类型是 InputStream

我们再看看 StringLoader 的源码:

Java 复制代码
public class StringLoader<Data> implements ModelLoader<String, Data> {
  private final ModelLoader<Uri, Data> uriLoader;

  // Public API.
  @SuppressWarnings("WeakerAccess")
  public StringLoader(ModelLoader<Uri, Data> uriLoader) {
    this.uriLoader = uriLoader;
  }

  @Override
  public LoadData<Data> buildLoadData(
      @NonNull String model, int width, int height, @NonNull Options options) {
    Uri uri = parseUri(model);
    if (uri == null || !uriLoader.handles(uri)) {
      return null;
    }
    return uriLoader.buildLoadData(uri, width, height, options);
  }

  @Override
  public boolean handles(@NonNull String model) {
    // Avoid parsing the Uri twice and simply return null from buildLoadData if we don't handle this
    // particular Uri type.
    return true;
  }

  @Nullable
  private static Uri parseUri(String model) {
    Uri uri;
    if (TextUtils.isEmpty(model)) {
      return null;
      // See https://pmd.github.io/pmd-6.0.0/pmd_rules_java_performance.html#simplifystartswith
    } else if (model.charAt(0) == '/') {
      uri = toFileUri(model);
    } else {
      uri = Uri.parse(model);
      String scheme = uri.getScheme();
      if (scheme == null) {
        uri = toFileUri(model);
      }
    }
    return uri;
  }

  private static Uri toFileUri(String path) {
    return Uri.fromFile(new File(path));
  }
  
}

上面的代码也非常简单,将输入的 model 转换成 Uri 对象,如果 Uri 没有对应的 scheme,那就当文件处理,然后将转换后的 Uri 对象交由构造函数传入的 ModelLoader 处理,前面我们知道这个 ModelLoader 接受 Uri 的输入,最后返回 InputStream 数据。

UrlUriLoader

它是支持 Uri 输入,InputStream 输出的 ModleLoader

我们继续看看它的 StreamFactory 对象是怎么实现的:

Java 复制代码
  public static class StreamFactory implements ModelLoaderFactory<Uri, InputStream> {

    @NonNull
    @Override
    public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {
      return new UrlUriLoader<>(multiFactory.build(GlideUrl.class, InputStream.class));
    }

    @Override
    public void teardown() {
      // Do nothing.
    }
  }

我们看到它的处理方式和 StringLoader 中类似,构建 UrlUriLoader 实例时也会先创建一个 ModelLoader 实例,作为它的构造函数的参数,这个 ModelLoader 接受的输入是 GlideUrl,输出是 InputStream

我们再来看看 UrlUriLoader 中的源码实现:

Java 复制代码
public class UrlUriLoader<Data> implements ModelLoader<Uri, Data> {
  private static final Set<String> SCHEMES =
      Collections.unmodifiableSet(new HashSet<>(Arrays.asList("http", "https")));
  private final ModelLoader<GlideUrl, Data> urlLoader;

  // Public API.
  @SuppressWarnings("WeakerAccess")
  public UrlUriLoader(ModelLoader<GlideUrl, Data> urlLoader) {
    this.urlLoader = urlLoader;
  }

  @Override
  public LoadData<Data> buildLoadData(
      @NonNull Uri uri, int width, int height, @NonNull Options options) {
    GlideUrl glideUrl = new GlideUrl(uri.toString());
    return urlLoader.buildLoadData(glideUrl, width, height, options);
  }

  @Override
  public boolean handles(@NonNull Uri uri) {
    return SCHEMES.contains(uri.getScheme());
  }
  
}

我们注意看它的 handles() 方法实现,它只支持协议为 httphttpsuri。在 buildLoadData() 方法中会以 uri 作为参数,然后构建一个 GlideUrl 对象,然后传给构造函数传过来的 ModelLoader

HttpGlideUrlLoader

他就是真正的处理 http 请求的 ModelLoader,它以 GlideUrl 作为输入对象,输出一个 InputStream。他是默认的网络请求的 ModelLoader,所以假如我们要替换默认的网络请求 ModelLoader,代码就要像以下这样写:

Kotlin 复制代码
    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
        super.registerComponents(context, glide, registry)
        registry.replace(
            GlideUrl::class.java,
            InputStream::class.java,
            OkHttpUrlLoader.Factory(appOkHttpClient)
        )
    }

我们言归正传,看看 HttpGlideUrlLoaderFactory 是怎么实现的:

Java 复制代码
  public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
    private final ModelCache<GlideUrl, GlideUrl> modelCache = new ModelCache<>(500);

    @NonNull
    @Override
    public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
      return new HttpGlideUrlLoader(modelCache);
    }

    @Override
    public void teardown() {
      // Do nothing.
    }
  }

构建 HttpGlideUrlLoader 实例时,还创建了一个 ModelCache 用来记录 model 的内存缓存,缓存的大小是 500。

继续看看 HttpGlideUrlLoader 的代码实现:

Java 复制代码
public class HttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {

  public static final Option<Integer> TIMEOUT =
      Option.memory("com.bumptech.glide.load.model.stream.HttpGlideUrlLoader.Timeout", 2500);

  @Nullable private final ModelCache<GlideUrl, GlideUrl> modelCache;

  public HttpGlideUrlLoader() {
    this(null);
  }

  public HttpGlideUrlLoader(@Nullable ModelCache<GlideUrl, GlideUrl> modelCache) {
    this.modelCache = modelCache;
  }

  @Override
  public LoadData<InputStream> buildLoadData(
      @NonNull GlideUrl model, int width, int height, @NonNull Options options) {
    GlideUrl url = model;
    if (modelCache != null) {
      url = modelCache.get(model, 0, 0);
      if (url == null) {
        modelCache.put(model, 0, 0, model);
        url = model;
      }
    }
    int timeout = options.get(TIMEOUT);
    return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
  }

  @Override
  public boolean handles(@NonNull GlideUrl model) {
    return true;
  }
  
}  

上面代码非常简单,只是如果 model 没有在 ModelCache 中,会添加到 ModelCache 中,然后构建一个 HttpUrlFetcher 对象,并存入 LoadData 中,HttpUrlFetcher 实现了真正的网络请求。

我们看看 HttpUrlFetcher#loadData() 方法是如何构建网络请求的:

Java 复制代码
  @Override
  public void loadData(
      @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      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));
      }
    }
  }

通过 loadDataWithRedirects() 方法执行 http 请求,如果成功回调 Callback#onDataReady(),如果失败回调 Callback#onLoadFailed()

我们看看 loadDataWithRedirects() 方法的实现:

Java 复制代码
  private InputStream loadDataWithRedirects(
      URL url, int redirects, URL lastUrl, Map<String, String> headers) throws HttpException {
    // 判断是否达到最大的重定向次数,最大次数 5 次。  
    if (redirects >= MAXIMUM_REDIRECTS) {
      throw new HttpException(
          "Too many (> " + MAXIMUM_REDIRECTS + ") redirects!", INVALID_STATUS_CODE);
    } else {
      // Comparing the URLs using .equals performs additional network I/O and is generally broken.
      // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
      try {
        if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
          throw new HttpException("In re-direct loop", INVALID_STATUS_CODE);
        }
      } catch (URISyntaxException e) {
        // Do nothing, this is best effort.
      }
    }
    
    // 构建 http 的 Connection
    urlConnection = buildAndConfigureConnection(url, headers);

    try {
      // Connect explicitly to avoid errors in decoders if connection fails.
      // 执行 http 请求
      urlConnection.connect();
      // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
      // 获取对应的 stream,用于释放链接
      stream = urlConnection.getInputStream();
    } catch (IOException e) {
      throw new HttpException(
          "Failed to connect or obtain data", getHttpStatusCodeOrInvalid(urlConnection), e);
    }

    if (isCancelled) {
      return null;
    }
    
    // 获取 http 状态码
    final int statusCode = getHttpStatusCodeOrInvalid(urlConnection);
    if (isHttpOk(statusCode)) {
      // 请求成功,直接获取对应的 ResponseBody 的 InputStream。
      return getStreamForSuccessfulRequest(urlConnection);
    } else if (isHttpRedirect(statusCode)) {
      // 如果是重定向,解析新的 Url 然后递归调用 loadDataWithRedirects() 方法。
      String redirectUrlString = urlConnection.getHeaderField(REDIRECT_HEADER_FIELD);
      if (TextUtils.isEmpty(redirectUrlString)) {
        throw new HttpException("Received empty or null redirect url", statusCode);
      }
      URL redirectUrl;
      try {
        redirectUrl = new URL(url, redirectUrlString);
      } catch (MalformedURLException e) {
        throw new HttpException("Bad redirect url: " + redirectUrlString, statusCode, e);
      }
      // Closing the stream specifically is required to avoid leaking ResponseBodys in addition
      // to disconnecting the url connection below. See #2352.
      cleanup();
      return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
    } else if (statusCode == INVALID_STATUS_CODE) {
      throw new HttpException(statusCode);
    } else {
      try {
        throw new HttpException(urlConnection.getResponseMessage(), statusCode);
      } catch (IOException e) {
        throw new HttpException("Failed to get a response message", statusCode, e);
      }
    }
  }

我相信很多年轻的朋友都没有用过 HttpUrlConnection,我是在刚学 Android 的时候用过,然后工作后有用过 Volly,再到然后就一直是 OkHttp 了。说了一点废话,我们简单看看 HttpUrlConnection 是怎么用的吧(反正现在工作中不可能用这个)。

  1. 判断是否达到最大的重定向次数 5 次。
  2. 通过 buildAndConfigureConnection() 方法构建一个 HttpUrlConnection
  3. 调用 HttpUrlConnection#connect() 方法请求 http
  4. 通过 getHttpStatusCodeOrInvalid() 方法获取 http 的状态码。
  • 请求成功:通过 getStreamForSuccessfulRequest() 方法获取 ResponseBodyInputStream
  • 重定向:重新解析获取重定向的 url,然后递归调用 loadDataWithRedirects() 方法。
  • 其他情况:抛出异常。

我们看看 buildAndConfigureConnection() 方法如何构建 HttpUrlConnection 的:

Java 复制代码
  private HttpURLConnection buildAndConfigureConnection(URL url, Map<String, String> headers)
      throws HttpException {
    HttpURLConnection urlConnection;
    try {
      urlConnection = connectionFactory.build(url);
    } catch (IOException e) {
      throw new HttpException("URL.openConnection threw", /* statusCode= */ 0, e);
    }
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    urlConnection.setConnectTimeout(timeout);
    urlConnection.setReadTimeout(timeout);
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true);
    // Stop the urlConnection instance of HttpUrlConnection from following redirects so that
    // redirects will be handled by recursive calls to this method, loadDataWithRedirects.
    urlConnection.setInstanceFollowRedirects(false);
    return urlConnection;
  }
  
  private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory {

    @Synthetic
    DefaultHttpUrlConnectionFactory() {}

    @Override
    public HttpURLConnection build(URL url) throws IOException {
      return (HttpURLConnection) url.openConnection();
    }
  }

朴实无华的代码,添加了 RequestHeader,设置了超时,关闭了默认的重定向处理(由代码自己处理)。

继续看看 getStreamForSuccessfulRequest() 方法是如何获取 RequestBodyInputStream 的:

Java 复制代码
  private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
      throws HttpException {
    try {
      if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
        int contentLength = urlConnection.getContentLength();
        stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
      } else {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());
        }
        stream = urlConnection.getInputStream();
      }
    } catch (IOException e) {
      throw new HttpException(
          "Failed to obtain InputStream", getHttpStatusCodeOrInvalid(urlConnection), e);
    }
    return stream;
  }

上面代码中会尝试去读 Content_Length,如果读取成功,就会将原来的 InputStreamContentLengthInputStream 封装一下,反之就直接用 HttpUrlConnection#getInputStream() 方法返回的结果。

有一说一 HttpUrlConnection 的代码可读性和使用的便利性与 OkHttp 相比差远了,所以我们再来看看用 OkHttpModelLoader 是怎么实现的。

OkHttpUrlLoader

同样先看看它的 Factory 怎么写的:

Java 复制代码
  public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
    private static volatile Call.Factory internalClient;
    private final Call.Factory client;

    private static Call.Factory getInternalClient() {
      if (internalClient == null) {
        synchronized (Factory.class) {
          if (internalClient == null) {
            internalClient = new OkHttpClient();
          }
        }
      }
      return internalClient;
    }

    public Factory() {
      this(getInternalClient());
    }

    public Factory(@NonNull Call.Factory client) {
      this.client = client;
    }

    @NonNull
    @Override
    public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
      return new OkHttpUrlLoader(client);
    }

    @Override
    public void teardown() {
      // Do nothing, this instance doesn't own the client.
    }
  }

如果没有默认的 OkHttpClient 就会创建一个,然后以 OkHttpClient 为构造函数的参数创建一个 OkHttpUrlLoader 实例。

再看看 OkHttpUrlLoader#buildLoadData() 的实现:

Java 复制代码
  @Override
  public LoadData<InputStream> buildLoadData(
      @NonNull GlideUrl model, int width, int height, @NonNull Options options) {
    return new LoadData<>(model, new OkHttpStreamFetcher(client, model));
  }

网络请求封装在 OkHttpStreamFetcher 中,我们看看 OkHttpStreamFetcher#loadData() 方法是如何实现的:

Java 复制代码
  @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);
  }

上面的代码相信大家都很熟悉了,先构建了一个 RequestBuilder,把 urlHttp Header 都添加进去后,然后构建一个 Request,最后使用的是 OkHttp 异步调用的代码。

我们看看 onResponse() 成功的回调中怎么处理的:

Java 复制代码
  @Override
  public void onResponse(@NonNull Call call, @NonNull Response response) {
    responseBody = response.body();
    if (response.isSuccessful()) {
      long contentLength = Preconditions.checkNotNull(responseBody).contentLength();
      stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
      callback.onDataReady(stream);
    } else {
      callback.onLoadFailed(new HttpException(response.message(), response.code()));
    }
  }

同样使用 ContentLengthInputStream 来封装 ResponseBodyInputStream,这里我要说明下,OkHttp 中自带的重定向处理次数是 20 次,并且无法关闭。

再看看 onFailure() 异常处理的代码是怎么样的:

Java 复制代码
  @Override
  public void onFailure(@NonNull Call call, @NonNull IOException e) {
    if (Log.isLoggable(TAG, Log.DEBUG)) {
      Log.d(TAG, "OkHttp failed to obtain result", e);
    }

    callback.onLoadFailed(e);
  }

朴实无华的代码,没啥好解释的。

Decoder

我们从网络或者本地缓存中获取到的图片文件通常是压缩过的格式,常见的有 PNGJPEGGIF 等等。这些压缩格式文件的图片 Android 是无法直接渲染的,我们需要将这些文件解码成 BitmapAndroid 中可以渲染 Bitmap Raw 格式的图片。而 Decoder 的工作就是将这些压缩的图片格式转换成 Bitmap

我们先看看 Decoder 的接口设计:

Java 复制代码
public interface ResourceDecoder<T, Z> {

  boolean handles(@NonNull T source, @NonNull Options options) throws IOException;

  
  @Nullable
  Resource<Z> decode(@NonNull T source, int width, int height, @NonNull Options options)
      throws IOException;
}

handles() 方法就是用来判断是否能够解码对应的 sourcedecode() 方法就是执行解码。

后续我们就以输入是 InputStream,解码后输出是 BitmapDecoder 作为例子来分析(Glide 的处理方式分为 Android 9 及其以上版本和 Android 9以下版本两种方式),其他的类型是类似的,我就不全部都分析了,gif 格式的图片也是类似的,需要的同学自行去找源码。

Android 9 及其以上版本

Android 9 及其以上的版本解码 InputStream 并输出 BitmapDecoderInputStreamBitmapImageDecoderResourceDecoder

我们来看看它的 decode() 方法:

Java 复制代码
  private final BitmapImageDecoderResourceDecoder wrapped = new BitmapImageDecoderResourceDecoder();
  @Override
  public Resource<Bitmap> decode(
      @NonNull InputStream stream, int width, int height, @NonNull Options options)
      throws IOException {
    ByteBuffer buffer = ByteBufferUtil.fromStream(stream);
    Source source = ImageDecoder.createSource(buffer);
    return wrapped.decode(source, width, height, options);
  }

首先将传过来的 InputStream 读取到 ByteBuffer 中,然后通过 ByteBuffer 构建一个 Source 对象(只有 Android 9 及其以上版本才能用),然后调用 BitmapImageDecoderResourceDecoder#decode() 方法完成解码,它是支持输入是 Source,输出是 BitmapDecoder

我们再来看看 BitmapImageDecoderResourceDecoder#decode() 方法的实现:

Java 复制代码
  public Resource<Bitmap> decode(
      @NonNull Source source, int width, int height, @NonNull Options options) throws IOException {
    Bitmap result =
        ImageDecoder.decodeBitmap(
            source, new DefaultOnHeaderDecodedListener(width, height, options));
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      // ...
    }
    return new BitmapResource(result, bitmapPool);
  }

直接调用 ImageDecoder#decodeBitmap() 完成解码,这是 Android 中的实现,这里要注意 DefaultOnHeaderDecodedListener 回调对象,它其中做了一些配置处理。

我们来看看 DefaultOnHeaderDecodedListener#onHeaderDecoded() 方法的处理:

Java 复制代码
  @Override
  public void onHeaderDecoded(
      @NonNull ImageDecoder decoder, @NonNull ImageInfo info, @NonNull Source source) {
    
    // 设置是否支持 HARDWARE 解码
    if (hardwareConfigState.isHardwareConfigAllowed(
        requestedWidth,
        requestedHeight,
        isHardwareConfigAllowed,
        /* isExifOrientationRequired= */ false)) {
      decoder.setAllocator(ImageDecoder.ALLOCATOR_HARDWARE);
    } else {
      decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
    }
    
    // 设置内存策略
    if (decodeFormat == DecodeFormat.PREFER_RGB_565) {
      decoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
    }

    decoder.setOnPartialImageListener(
        new OnPartialImageListener() {
          @Override
          public boolean onPartialImage(@NonNull DecodeException e) {
            // Never return partial images.
            return false;
          }
        });
    
    
    // 计算目标的尺寸
    Size size = info.getSize();
    int targetWidth = requestedWidth;
    if (requestedWidth == Target.SIZE_ORIGINAL) {
      targetWidth = size.getWidth();
    }
    int targetHeight = requestedHeight;
    if (requestedHeight == Target.SIZE_ORIGINAL) {
      targetHeight = size.getHeight();
    }

    float scaleFactor =
        strategy.getScaleFactor(size.getWidth(), size.getHeight(), targetWidth, targetHeight);

    int resizeWidth = Math.round(scaleFactor * size.getWidth());
    int resizeHeight = Math.round(scaleFactor * size.getHeight());
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      // ...
    }
    
    // 设置解码后的 Bitmap 的尺寸
    decoder.setTargetSize(resizeWidth, resizeHeight);
    
    // 设置色域,P3 或者 sRGB。
    if (preferredColorSpace != null) {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        boolean isP3Eligible =
            preferredColorSpace == PreferredColorSpace.DISPLAY_P3
                && info.getColorSpace() != null
                && info.getColorSpace().isWideGamut();
        decoder.setTargetColorSpace(
            ColorSpace.get(isP3Eligible ? ColorSpace.Named.DISPLAY_P3 : ColorSpace.Named.SRGB));
      } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB));
      }
    }
  }

设置的代码比较简单:

  1. 设置是否支持 HARDWARE 解码。
  2. 设置内存策略。
  3. 计算并设置最后输出的 Bitmap 尺寸。
  4. 设置颜色空间,P3 或者 sRGB

Android 9 以下版本

Android 9 以下的版本解码 InputStream 并输出 BitmapDecoderStreamBitmapDecoder

我们来看看它的 decode() 方法:

Java 复制代码
  @Override
  public Resource<Bitmap> decode(
      @NonNull InputStream source, int width, int height, @NonNull Options options)
      throws IOException {

    final RecyclableBufferedInputStream bufferedStream;
    final boolean ownsBufferedStream;
    // 用 RecyclableBufferedInputStream 包裹 InputStream
    if (source instanceof RecyclableBufferedInputStream) {
      bufferedStream = (RecyclableBufferedInputStream) source;
      ownsBufferedStream = false;
    } else {
      bufferedStream = new RecyclableBufferedInputStream(source, byteArrayPool);
      ownsBufferedStream = true;
    }
    
    // 用 ExceptionPassthroughInputStream 包裹 InputStream
    ExceptionPassthroughInputStream exceptionStream =
        ExceptionPassthroughInputStream.obtain(bufferedStream);
    
    // 用 MarkEnforcingInputStream 包裹 InputStream
    MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream);
    UntrustedCallbacks callbacks = new UntrustedCallbacks(bufferedStream, exceptionStream);
    try {
      // 调用 Downsampler#decode() 方法完成解码
      return downsampler.decode(invalidatingStream, width, height, options, callbacks);
    } finally {
      exceptionStream.release();
      if (ownsBufferedStream) {
        bufferedStream.release();
      }
    }
  }

输入的 InputStream 会被 RecyclableBufferedInputStream(其中会使用 LruArrayPool 来获取 ByteArray,在第一篇文章中介绍过,一般这个缓存池的大小是 4MB)、 ExceptionPassthroughInputStream(用于拦截异常) 和 MarkEnforcingInputStream (用于读取标记)包裹。最后调用 Downsampler#decode() 方法完成解码。

我们再来看看 Downsampler#decode() 方法的实现:

Java 复制代码
  public Resource<Bitmap> decode(
      InputStream is,
      int requestedWidth,
      int requestedHeight,
      Options options,
      DecodeCallbacks callbacks)
      throws IOException {
    return decode(
        new ImageReader.InputStreamImageReader(is, parsers, byteArrayPool),
        requestedWidth,
        requestedHeight,
        options,
        callbacks);
  }

InputStream 封装成 InputStreamImageReader 继续调用 decode() 方法,我们继续追踪。

Java 复制代码
  private Resource<Bitmap> decode(
      ImageReader imageReader,
      int requestedWidth,
      int requestedHeight,
      Options options,
      DecodeCallbacks callbacks)
      throws IOException {
    // 从 LruArrayPool 中获取解码过程中用到的临时缓存,大小为 64K。  
    byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    // 获取默认的 Options
    BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
    // 添加解码过程中用到的临时缓存
    bitmapFactoryOptions.inTempStorage = bytesForOptions;
    // 获取解码格式 565 或者 8888
    DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
    // 获取颜色空间,P3 或者 sRGB
    PreferredColorSpace preferredColorSpace = options.get(PREFERRED_COLOR_SPACE);
    
    // 获取裁剪策略,比如 FitCenter,或者 CenterCrop。
    DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
    boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
    // 是否支持 HARDWARE。
    boolean isHardwareConfigAllowed =
        options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);

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

上面的代码比较简单,看其中注释就好了,这里要强调一下,这个从 LruArrayPool 中获取了 64KByteArray,用于解码时用到的临时内存,通过 Options#inTempStorage 参数设置。

我们继续看 decodeFromWrappedStreams() 方法的实现:

Java 复制代码
  private Bitmap decodeFromWrappedStreams(
      ImageReader imageReader,
      BitmapFactory.Options options,
      DownsampleStrategy downsampleStrategy,
      DecodeFormat decodeFormat,
      PreferredColorSpace preferredColorSpace,
      boolean isHardwareConfigAllowed,
      int requestedWidth,
      int requestedHeight,
      boolean fixBitmapToRequestedDimensions,
      DecodeCallbacks callbacks)
      throws IOException {
    long startTime = LogTime.getLogTime();
    
    // 计算图片的大小
    int[] sourceDimensions = getDimensions(imageReader, options, callbacks, bitmapPool);
    int sourceWidth = sourceDimensions[0];
    int sourceHeight = sourceDimensions[1];
    String sourceMimeType = options.outMimeType;

    if (sourceWidth == -1 || sourceHeight == -1) {
      isHardwareConfigAllowed = false;
    }

    int orientation = imageReader.getImageOrientation();
    // 通过 Exif 来读取图片的旋转角度
    int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
    boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);
    
    // 计算目标 bitmap 尺寸
    int targetWidth =
        requestedWidth == Target.SIZE_ORIGINAL
            ? (isRotationRequired(degreesToRotate) ? sourceHeight : sourceWidth)
            : requestedWidth;
    int targetHeight =
        requestedHeight == Target.SIZE_ORIGINAL
            ? (isRotationRequired(degreesToRotate) ? sourceWidth : sourceHeight)
            : requestedHeight;

    ImageType imageType = imageReader.getImageType();
    
    // 计算配置放缩
    calculateScaling(
        imageType,
        imageReader,
        callbacks,
        bitmapPool,
        downsampleStrategy,
        degreesToRotate,
        sourceWidth,
        sourceHeight,
        targetWidth,
        targetHeight,
        options);
    // 计算使用 565 还是 8888    
    calculateConfig(
        imageReader,
        decodeFormat,
        isHardwareConfigAllowed,
        isExifOrientationRequired,
        options,
        targetWidth,
        targetHeight);

    boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    // Prior to KitKat, the inBitmap size must exactly match the size of the bitmap we're decoding.
    if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
      int expectedWidth;
      int expectedHeight;
      if (sourceWidth >= 0
          && sourceHeight >= 0
          && fixBitmapToRequestedDimensions
          && isKitKatOrGreater) {
        expectedWidth = targetWidth;
        expectedHeight = targetHeight;
      } else {
        // 计算最后输出的 Bitmap 尺寸
        float densityMultiplier =
            isScaling(options) ? (float) options.inTargetDensity / options.inDensity : 1f;
        int sampleSize = options.inSampleSize;
        int downsampledWidth = (int) Math.ceil(sourceWidth / (float) sampleSize);
        int downsampledHeight = (int) Math.ceil(sourceHeight / (float) sampleSize);
        expectedWidth = Math.round(downsampledWidth * densityMultiplier);
        expectedHeight = Math.round(downsampledHeight * densityMultiplier);

        if (Log.isLoggable(TAG, Log.VERBOSE)) {
           // ...
        }
      }
      if (expectedWidth > 0 && expectedHeight > 0) {
        // 设置中添加输出的 Bitmap,从 BitmapPool 中获取
        setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
      }
    }
    
    // 设置最后输出的色域
    if (preferredColorSpace != null) {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        boolean isP3Eligible =
            preferredColorSpace == PreferredColorSpace.DISPLAY_P3
                && options.outColorSpace != null
                && options.outColorSpace.isWideGamut();
        options.inPreferredColorSpace =
            ColorSpace.get(isP3Eligible ? ColorSpace.Named.DISPLAY_P3 : ColorSpace.Named.SRGB);
      } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
      }
    }
    
    // 解码
    Bitmap downsampled = decodeStream(imageReader, options, callbacks, bitmapPool);
    callbacks.onDecodeComplete(bitmapPool, downsampled);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      // ...
    }

    Bitmap rotated = null;
    // 执行旋转操作
    if (downsampled != null) {
      downsampled.setDensity(displayMetrics.densityDpi);

      rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation);
      if (!downsampled.equals(rotated)) {
        bitmapPool.put(downsampled);
      }
    }

    return rotated;
  }

上面的代码稍微有一点点复杂,理一下:

  1. 通过 getDimensions() 方法获取图片的尺寸。
  2. 读取图片中 Exif 信息里面的旋转角度信息。
  3. 通过 calculateScaling() 方法计算配置放缩的信息,图片的编码方式也会影响这个值,图片的放缩是通过修改 BitmapFactory.OptionsinDensityinTargetDensityinSampleSize 等等参数,不太清楚的同学可以看看[小笔记] Android 中 Bitmap 的放缩配置
  4. 通过 calculateConfig() 方法计算使用 565 还是 8888
  5. 通过 setInBitmap() 设置缓存的 Bitmap 到设置中,这样就不用每次解码时都创建一个新的 Bitmap,优化内存的使用。
  6. 调用 decodeStream() 方法完成解码。
  7. 根据 Exif 中的信息,执行旋转操作。

上面的一些方法我们一起分析一下,有的没有分析到的,大家感兴趣的话可以自己去找找代码。

我们先来看看 getDimenssions() 方法是如何获取图片的尺寸的:

Java 复制代码
  private static int[] getDimensions(
      ImageReader imageReader,
      BitmapFactory.Options options,
      DecodeCallbacks decodeCallbacks,
      BitmapPool bitmapPool)
      throws IOException {
    options.inJustDecodeBounds = true;
    decodeStream(imageReader, options, decodeCallbacks, bitmapPool);
    options.inJustDecodeBounds = false;
    return new int[] {options.outWidth, options.outHeight};
  }

上面的代码很简单只是将设置中的 inJustDecodeBounds 参数设置为 true,然后调用 decodeStream() 方法去解码,最后 options 中的 outWithoutHeight 就是对应的图片的尺寸。

再看看 setInBitmap() 方法是如何设置输入的 Bitmap 的:

Java 复制代码
  private static void setInBitmap(
      BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
    @Nullable Bitmap.Config expectedConfig = null;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      if (options.inPreferredConfig == Config.HARDWARE) {
        return;
      }
      expectedConfig = options.outConfig;
    }

    if (expectedConfig == null) {
      expectedConfig = options.inPreferredConfig;
    }
    options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
  }

当配置是 HARDWARE 时,不需要设置输出的 Bitmap,因为这种配置下不会占用内存而是占用显存,同时 HARDWARE 配置的 Bitmap 也无法存放在 LruBitmapPool 中,因为这种 Bitmap 是无法修改的。

输出的 Bitmap 是从 LruBitmapPool 中获取的,第一篇文章中已经介绍过 LruBitmapPool,在 Android 8 以下的很多设备上面 Pool 的大小是 4 倍屏幕大小的图片占用的内存大小,在 Android 8 及其以上的设备中这个 Pool 的大小设置为 0,因为 Android 8 的设备默认的 Bitmap 配置是 HARDWARE,所以它不需要 BitmapPool 来缓存(不过我看到新的版本又把这个值由 0 调整为 1 倍屏幕大小的图片的内存)。

我们再看看 decodeStream() 方法是如何解码图片的:

Java 复制代码
  private static Bitmap decodeStream(
      ImageReader imageReader,
      BitmapFactory.Options options,
      DecodeCallbacks callbacks,
      BitmapPool bitmapPool)
      throws IOException {
    if (!options.inJustDecodeBounds) {
      callbacks.onObtainBounds();
      imageReader.stopGrowingBuffers();
    }

    int sourceWidth = options.outWidth;
    int sourceHeight = options.outHeight;
    String outMimeType = options.outMimeType;
    final Bitmap result;
    TransformationUtils.getBitmapDrawableLock().lock();
    try {
      // 通过 ImageReader 解码
      result = imageReader.decodeBitmap(options);
    } catch (IllegalArgumentException e) {
      IOException bitmapAssertionException =
          newIoExceptionForInBitmapAssertion(e, sourceWidth, sourceHeight, outMimeType, options);
      if (Log.isLoggable(TAG, Log.DEBUG)) {
         // ...
      }
      if (options.inBitmap != null) {
        // 如果解码失败,同时有设置输出的 Bitmap,将输出的 Bitmap 移除后再尝试解码一次。  
        try {
          bitmapPool.put(options.inBitmap);
          options.inBitmap = null;
          return decodeStream(imageReader, options, callbacks, bitmapPool);
        } catch (IOException resetException) {
          throw bitmapAssertionException;
        }
      }
      throw bitmapAssertionException;
    } finally {
      TransformationUtils.getBitmapDrawableLock().unlock();
    }

    return result;
  }

上面代码看似很多,其实逻辑非常简单,直接调用 ImageReader#decodeBitmap() 方法完成解码,如果解码失败同时有设置自定义的 Bitmap,那么就移除这个 Bitmap 再重试一次,我想的是可能这个自定义的 Bitmap 可能不对,导致的异常,所以移除后再试一次。

我们看看 InputStreamImageReader#decodeBitmap() 方法是怎么实现的:

Java 复制代码
    @Nullable
    @Override
    public Bitmap decodeBitmap(BitmapFactory.Options options) throws IOException {
      return BitmapFactory.decodeStream(dataRewinder.rewindAndGet(), null, options);
    }

我我们自己写的代码也没有什么不同,也是直接调用 BitmapFactory#decodeStream() 方法完成解码。

最后

本篇文章是 Glide 源码阅读系列的最后一篇,Glide 源码的复杂程度比我想象的确实要高很多,如果你看懂了 Glide 处理网络图片的方式,我相信你一定会有所收获。

今天多年工作的同事被裁了 lastday,不开心 T_T

相关推荐
Geeker555 小时前
如何在忘记密码的情况下解锁Android手机?
android·网络·macos·华为·智能手机·电脑·手机
wxx21506 小时前
【android】【adb shell】写一个shell脚本,监听进程pid变化
android·adb
心死翼未伤7 小时前
【MySQL基础篇】多表查询
android·数据结构·数据库·mysql·算法
喂_balabala7 小时前
Android手机拍照或从本地相册选取图片设置头像-高版本适配
android·开发语言
_小马快跑_9 小时前
Android | StandardCharsets.UTF_8.toString() 遇到的兼容问题记录
android
wxx215010 小时前
【Android】【多屏】多屏异显异触调试技巧总结
android
人民的石头12 小时前
Android增量更新----java版
android
Geeker5513 小时前
如何在忘记密码的情况下删除华为ID激活锁
android·运维·服务器·网络·macos·华为·智能手机
XD74297163615 小时前
【Android】ADB 使用指南
android·adb
骨子里的偏爱15 小时前
uniapp/Android App上架三星市场需要下载所需要的SDK
android·uni-app