Glide源码分析(五)——ModelLoader机制

ModelLoaderRegistry

ModelLoaderFactory和MultiModelLoaderFactory

ModelLoaderFactory是一个工厂类接口,实现类使用它的接口来对外构建不同了类型的modelLoader,它的实现类一般是不同类型的ModelLoader的内部类,例如ByteArrayLoader内部就实现了两个ModelLoaderFactory,分别生成能够将byte数组类型的数据转换成ByteBuffer或者ByteArrayInputStream的ByteArrayLoader对象。
MultiModelLoaderFactory实际上不算真正意义上的工厂类实现,它是多个不同类型的ModelLoaderFactory的集合类,除了对外暴露能够操作这个内部列表的接口外,主要提供以下几个build方法:

synchronized <Model> List<ModelLoader<Model, ?>> build(Class<Model> modelClass) {
    try {
      List<ModelLoader<Model, ?>> loaders = new ArrayList<>();
      for (Entry<?, ?> entry : entries) {
        // Avoid stack overflow recursively creating model loaders by only creating loaders in
        // recursive requests if they haven't been created earlier in the chain. For example:
        // A Uri loader may translate to another model, which in turn may translate back to a Uri.
        // The original Uri loader won't be provided to the intermediate model loader, although
        // other Uri loaders will be.
        if (alreadyUsedEntries.contains(entry)) {
          continue;
        }
        if (entry.handles(modelClass)) {
          alreadyUsedEntries.add(entry);
          loaders.add(this.<Model, Object>build(entry));
          alreadyUsedEntries.remove(entry);
        }
      }
      return loaders;
    } catch (Throwable t) {
      alreadyUsedEntries.clear();
      throw t;
    }
}

public synchronized <Model, Data> ModelLoader<Model, Data> build(Class<Model> modelClass,
      Class<Data> dataClass) {
    try {
      List<ModelLoader<Model, Data>> loaders = new ArrayList<>();
      boolean ignoredAnyEntries = false;
      for (Entry<?, ?> entry : entries) {
        // Avoid stack overflow recursively creating model loaders by only creating loaders in
        // recursive requests if they haven't been created earlier in the chain. For example:
        // A Uri loader may translate to another model, which in turn may translate back to a Uri.
        // The original Uri loader won't be provided to the intermediate model loader, although
        // other Uri loaders will be.
        if (alreadyUsedEntries.contains(entry)) {
          ignoredAnyEntries = true;
          continue;
        }
        if (entry.handles(modelClass, dataClass)) {
          alreadyUsedEntries.add(entry);
          loaders.add(this.<Model, Data>build(entry));
          alreadyUsedEntries.remove(entry);
        }
      }
      if (loaders.size() > 1) {
        return factory.build(loaders, exceptionListPool);
      } else if (loaders.size() == 1) {
        return loaders.get(0);
      } else {
        // Avoid crashing if recursion results in no loaders available. The assertion is supposed to
        // catch completely unhandled types, recursion may mean a subtype isn't handled somewhere
        // down the stack, which is often ok.
        if (ignoredAnyEntries) {
          return emptyModelLoader();
        } else {
          throw new NoModelLoaderAvailableException(modelClass, dataClass);
        }
      }
    } catch (Throwable t) {
      alreadyUsedEntries.clear();
      throw t;
    }
  }

这两个build方法主要根据modelClass和dataClass类型来在内部选择合适的ModelLoaderFactory,以便生成对应的ModelLoader进行后续数据加载。其中第二个build方法对选择出来的多个ModelLoader又包装了一层,成为了MultiModelLoader,这个MultiModelLoader是一个装饰类,实现了ModelLoader的接口并在内部实现了一个DataFetcher,但是这些接口的实现都是委托给了内部的ModelLoader列表来实现。

ModelLoaderRegistry

ModelLoaderRegistry又在MultiModelLoaderFactory的基础上又进行了一次封装,它提供了建立和删除modelClass dataClass和ModelLoaderFactory的映射的方法,这些方法实际上是委托给内部的MultiModelLoaderFactory来完成的。同时它在这个基础上还在内部实现了一个cache,用来防止每次都需要根据modelClass去MultiModelLoaderFactory中查找与其对应的ModelLoaderFactory。为什么对ModelLoaderFactory包了一层又一层,我的理解是单一原则的设计模式,即factory就应该做构建相关的工作,要在构建工作的基础上加上cache的功能,就再添加一个包装类来实现,这样即使以后还要在构建的基础上增加其他功能或者去掉这个cache功能就能够很容易实现,不会伤及factory内部的代码,实现了内聚。

ModelLoader和DataFetcher

ModelLoader和DataFetcher结合起来实现从指定的数据源中加载数据并将数据转换成指定类型的资源,在这个过程中,它可以结合指定的尺寸要求进行不同尺寸图片的加载。

Model和Data泛型说明

ModelLoader是一个泛型接口,子类需要根据自己来指定对应泛型的类型,其中Model泛型一般指数据来源的相关的类型,Data表示数据加载后需要封装成的类型。例如ByteBufferFileLoader表示这个类需要从文件中加载数据,并将数据封装成ByteBuffer类型,于是他的Model类型为File,数据封装类型为ByteBuffer;同样的,HttpGlideUrlLoader表示从GlideUrl指定的数据源中获取数据,将结果封装成Http IO流的类型,因此它的Model类型为GlideUrl,Data类型为InputStream。

ModelLoader

ModelLoader接口的实现很简单明了,其内部有一个LoadData实现如下:

class LoadData<Data> {
  public final Key sourceKey;       // 需要加载资源的key
  public final List<Key> alternateKeys;     // 其他可选资源的key
  public final DataFetcher<Data> fetcher;       // 资源加载器

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

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

这个类涵盖了ModelLoader加载数据需要的所有信息,主要是资源的key和需要执行资源加载的DataFetcher。
此外,ModelLoader就剩下两个简单的抽象方法:
LoadData buildLoadData(Model model, int width, int height, Options options);

boolean handles(Model model);

其中buildLoadData根据指定的Model和宽高等附加条件,返回一个能够对数据进行加载的LoadData实现。handles方法是Modelloader的实现类根据自身情况判断是否能够加载转换指定model类型的数据。

DataFetcher

DataFetcher也是一个泛型接口,其中泛型类型为加载的数据源,例如文件、数组或者流等。DataFetcher内部除了一个数据加载结果回调接口之外,主要有以下几个和数据加载相关的接口:

/**
 * 用来从指定的数据源中进行数据的加载。需要注意的是:
 * 1. 如果指定的数据能够从缓存中找到,则没有必要使用这个方法进行数据加载
 * 2. 这个方法往往需要在后台线程中实现,因此实现类需要考虑该方法阻塞的情况,避免因为该方法阻塞导致anr等问题
 */
void loadData(Priority priority, DataCallback<? super T> callback);

/**
 * 清理加载并转换的resource资源
 */
void cleanup();


/**
 * 用来取消不需要的数据加载请求,最好在数据加载之前就调用该方法,当然也可以在数据加载完成或者正在进行加载的时候调用,调用该方法并一定会马上结束
 * 正在进行加载的动作。该方法一般需要在主线程调用,因此不能有阻塞操作。
 */
void cancel();

典型实现

Modelloader和DataFetcher两者结合起来构成了Glide的数据加载核心。当缓存中给定的数据不存在的时候,Glide就会通过指定的Modelloader和DataFetcher进行数据加载,这些数据可能来自文件、网络、byte数组等地方。下面就来分析几个典型的Modelloader和DataFetcher实现。

典型实现之FileLoader和FileFetcher

FileLoader用来从文件中进行数据加载,它内部方法实现得很简单,主要将相关的工作委托给了内部的FileOpener和FileFetcher实现,其获取数据的方法如下:

public LoadData<Data> buildLoadData(File model, int width, int height,
    Options options) {
  return new LoadData<>(new ObjectKey(model), new FileFetcher<>(model, fileOpener));
}

可以看到该方法只是将对应的加载需要的信息封装成LoadData返回了。然后看内部的FileFetcher主要实现:

private static class FileFetcher<Data> implements DataFetcher<Data> {
  private final File file;
  private final FileOpener<Data> opener;
  private Data data;

  public FileFetcher(File file, FileOpener<Data> opener) {
    this.file = file;
    this.opener = opener;
  }

  @Override
  public void loadData(Priority priority, DataCallback<? super Data> callback) {
    try {
      data = opener.open(file);
    } catch (FileNotFoundException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to open file", e);
      }
      callback.onLoadFailed(e);
      return;
    }
    callback.onDataReady(data);
  }

  @Override
  public void cleanup() {
    if (data != null) {
      try {
        opener.close(data);
      } catch (IOException e) {
        // Ignored.
      }
    }
  }

  ...

}

主要看loadData的实现,它直接简单地从FileOpener中获取文件数据,下面看看FileOpener接口的定义:

/**
 * Allows opening a specific type of data from a {@link java.io.File}.
 * @param <Data> The type of data that can be opened.
 */
public interface FileOpener<Data> {
  Data open(File file) throws FileNotFoundException;
  void close(Data data) throws IOException;
  Class<Data> getDataClass();
}

该接口只定义了获取文件数据,关闭数据获取以及获取数据类型的方法。该接口的具体实现在FileLoader内部实现的几种不同类型的factory中,例如StreamFactory的实现如下:

/**
 * Factory for loading {@link InputStream}s from {@link File}s.
 */
public static class StreamFactory extends Factory<InputStream> {
  public StreamFactory() {
    super(new FileOpener<InputStream>() {           // 具体FileOpener的实现
      @Override
      public InputStream open(File file) throws FileNotFoundException {     // decode的时候通过fileInputStream来获取缓存图片的
        return new FileInputStream(file);
      }

      @Override
      public void close(InputStream inputStream) throws IOException {
        inputStream.close();
      }

      @Override
      public Class<InputStream> getDataClass() {
        return InputStream.class;
      }
    });
  }
}

StreamFactory在构建modelLoader的时候使用了一个匿名的FileOpener实现来作为modelLoader的数据加载实现,可以看到该实现仅仅就是得到对应文件流。Fileloader的调用者通过LoadData能够得到该文件流,也就可以直接读取文件中的内容从而实现数据加载。

典型实现之HttpGlideUrlLoader和HttpUrlFetcher

和FileLoader类似,HttpGlideUrlLoader本身实现得也比较简单,相对于Fileloader,它只是多一个对url的缓存,其buildLoadData实现如下:

@Override
 public LoadData<InputStream> buildLoadData(GlideUrl model, int width, int height,
     Options options) {
   // GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time
   // spent parsing urls.
   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));
 }

这个函数返回的LoadData在需要数据的时候通过HttpUrlFetcher来进行数据加载。HttpUrlFetcher对网络数据进行加载,其主要实现的方法如下:

@Override
  public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    final InputStream result;
    try {
      result = loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/,
          glideUrl.getHeaders());
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
      return;
    }

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime)
          + " ms and loaded " + result);
    }
    callback.onDataReady(result);
  }

  /**
   * 进行数据加载,在资源重定向的时候可能会递归地调用该方法
   * @param url
   * @param redirects
   * @param lastUrl
   * @param headers
   * @return
   * @throws IOException
   */
  private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
      Map<String, String> headers) throws IOException {
    if (redirects >= MAXIMUM_REDIRECTS) {
      throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
    } 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");

        }
      } catch (URISyntaxException e) {
        // Do nothing, this is best effort.
      }
    }

    urlConnection = connectionFactory.build(url);
    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);

    // Connect explicitly to avoid errors in decoders if connection fails.
    urlConnection.connect();
    if (isCancelled) {
      return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (statusCode / 100 == 2) {
      return getStreamForSuccessfulRequest(urlConnection);
    } else if (statusCode / 100 == 3) {
      String redirectUrlString = urlConnection.getHeaderField("Location");
      if (TextUtils.isEmpty(redirectUrlString)) {
        throw new HttpException("Received empty or null redirect url");
      }
      URL redirectUrl = new URL(url, redirectUrlString);
      return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
    } else if (statusCode == -1) {
      throw new HttpException(statusCode);
    } else {
      throw new HttpException(urlConnection.getResponseMessage(), statusCode);
    }
  }

  private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
      throws IOException {
    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();
    }
    return stream;
  }

进行数据加载的逻辑在loadDataWithRedirects中实现,它使用urlConnection进行网络连接,当请求成功,使用getStreamForSuccessfulRequest返回网络连接的数据流,以便调用者可以进行网络数据加载。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值