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

相关推荐
编程、小哥哥10 分钟前
python操作mysql
android·python
Couvrir洪荒猛兽37 分钟前
Android实训十 数据存储和访问
android
五味香3 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
十二测试录3 小时前
【自动化测试】—— Appium使用保姆教程
android·经验分享·测试工具·程序人生·adb·appium·自动化
Couvrir洪荒猛兽5 小时前
Android实训九 数据存储和访问
android
aloneboyooo5 小时前
Android Studio安装配置
android·ide·android studio
Jacob程序员6 小时前
leaflet绘制室内平面图
android·开发语言·javascript
2401_897907866 小时前
10天学会flutter DAY2 玩转dart 类
android·flutter
m0_748233647 小时前
【PHP】部署和发布PHP网站到IIS服务器
android·服务器·php
Yeats_Liao8 小时前
Spring 定时任务:@Scheduled 注解四大参数解析
android·java·spring