Glide源码分析之数据拉取

同样地,开始之前先思考1个问题:

  1. Glide是怎么实现那么多资源Model的加载的?比如可以从Url、Asset、FileDescriptor、Uri、File等来源加载数据

1.ModelLoader

Glide的所有数据加载都实现一个接口ModelLoader<Model, Data>,其中Model类型就是来源的类型,Data是加载得到的数据类型,看下接口的具体源码,包含一个内部类LoadData,和两个接口方法,一个buildLoadData方法用来构造返回一个LoadData,另外一个方法handles用来返回这个ModelLoader能否处理这个ModelLoadData这个类里面有三个字段,一个sourceKey用来表示这次下载,一个DataFetcher用来获取不在缓存中的数据:

public interface ModelLoader<Model, Data> {

  /**
   * @param <Data> The type of data that well be loaded.
   */
  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);
}

看下Glide里面所有的实现类,这些种类就代表了Glide支持从哪些数据类型下载数据:

ModelLoader.PNG

比如我们看一个最常用的从http/https uris下载数据的UrlUriLoader, 先看它的接口方法handles,如果scheme类型是http/https类型的就返回true;另外一个接口方法buildLoadData通过字段urlloader返回一个LoadData,还有一个内部工厂类StramFactory, UrlUriLoader不建议外面通过构造函数实例化,而是通过这个工厂类的build方法进行实例化。基本上ModelLoader的实现类都是这种设计模式。

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

  /**
   * Loads {@link java.io.InputStream InputStreams} from {@link android.net.Uri Uris} with http
   * or https schemes.
   */
  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.
    }
  }
}

StreamFactory也是实现一个工厂接口ModelLoaderFactory,具体看下这个接口代码:

// ModelLoaderFactory.java
/**
 * An interface for creating a {@link ModelLoader} for a given model type.
 *
 * @param <T> The type of the model the {@link com.bumptech.glide.load.model.ModelLoader}s built by
 *            this factory can handle
 * @param <Y> The type of data the {@link com.bumptech.glide.load.model.ModelLoader}s built by this
 *            factory can load.
 */
public interface ModelLoaderFactory<T, Y> {

  /**
   * Build a concrete ModelLoader for this model type.
   *
   * @param multiFactory A map of classes to factories that can be used to construct additional
   *                     {@link ModelLoader}s that this factory's {@link ModelLoader} may depend on
   * @return A new {@link ModelLoader}
   */
  @NonNull
  ModelLoader<T, Y> build(@NonNull MultiModelLoaderFactory multiFactory);

  /**
   * A lifecycle method that will be called when this factory is about to replaced.
   */
  void teardown();
}

接口比较简单,比如特殊的是还有一个工厂MultiModelLoaderFactory,其实也可以看成一种代理模式,真正去buildLoadData的是通过工厂类StreamFactory构造返回的urlLoader,它能处理的Model类型是GlideUrl,返回的类型是InputStream,而urlloader本身是通过MultiModelLoaderFactory构造:

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

所以我们总结下下ModelLoader,UrlUriLoader,StreamFactory,ModelLoaderFactory,MultiModelLoaderFactory的关系, 具体的ModelLoader里面有一个代理的ModelLoader,这个代理的ModelLoder由MultiModelLoaderFactory实例化,不管是外层的ModelLoader或者是代理ModelLoader都是由Factory这种方式生成,外层的ModelLoaderFactory在自己的类名下面,而代理ModelLoader统一都由MultiModelLoaderFactory这个工厂生成,类似门面模式。

ModelLoaderUML.png

StreamFactory是在Glide初始化的时候注册到ModelLoaderRegistry中:

//Glide.java
registry.append(URL.class, InputStream.class, new UrlLoader.StreamFactory())

ModelLoaderRegistry其实只是MultiModelLoaderFactory的外壳,它会把Glide初始化注册的所有ModelFactory保存到MultiModelLoaderFactory:

// ModelLoaderRegistry.java
private final MultiModelLoaderFactory multiModelLoaderFactory;
private final ModelLoaderCache cache = new ModelLoaderCache();
public synchronized <Model, Data> void append(
      @NonNull Class<Model> modelClass,
      @NonNull Class<Data> dataClass,
      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {
    multiModelLoaderFactory.append(modelClass, dataClass, factory);
    cache.clear();
  }

按照上面的逻辑,再看一个具体的ModelLoader实现类FileLoader<Data> implements ModelLoader<File, Data>, 实现从File加载Data,这里Data有两个类型java.io.InputStreamjava.io.FileDescriptor,看下具体实现。实际工作会通过内部实例变量FileOpener完成,在FileFetcher loadData中通过FileOpener打开文件,返回需要的数据类型,所以关键就在这个FileOpener,在这里是对文件打开关闭的一个封装接口,根据具体的返回类型构造不同的FileOpener.

// FileLoader.java
public class FileLoader<Data> implements ModelLoader<File, Data> {
  private static final String TAG = "FileLoader";

  private final FileOpener<Data> fileOpener;

  // Public API.
  @SuppressWarnings("WeakerAccess")
  public FileLoader(FileOpener<Data> fileOpener) {
    this.fileOpener = fileOpener;
  }

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

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

  /**
   * 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();
  }

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

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

    @Override
    public void loadData(@NonNull Priority priority, @NonNull 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.
        }
      }
    }

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

    @NonNull
    @Override
    public Class<Data> getDataClass() {
      return opener.getDataClass();
    }

    @NonNull
    @Override
    public DataSource getDataSource() {
      return DataSource.LOCAL;
    }
  }

  /**
   * Base factory for loading data from {@link java.io.File files}.
   * @param <Data> The type of data that will be loaded for a given {@link java.io.File}.
   */
  public static class Factory<Data> implements ModelLoaderFactory<File, Data> {
    private final FileOpener<Data> opener;

    public Factory(FileOpener<Data> opener) {
      this.opener = opener;
    }

    @NonNull
    @Override
    public final ModelLoader<File, Data> build(@NonNull MultiModelLoaderFactory multiFactory) {
      return new FileLoader<>(opener);
    }

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

FileLoader内部有两个静态内部类StreamFactoryFileDescriptorFactory分别返回两个数据类型java.io.InputStreamjava.io.FileDescriptor。分别构造两种类型的FileOpener,一个返回InputStream类型,另外一个返回ParcelFileDescriptor.

  /**
   * Factory for loading {@link InputStream}s from {@link File}s.
   */
  public static class StreamFactory extends Factory<InputStream> {
    public StreamFactory() {
      super(new FileOpener<InputStream>() {
        @Override
        public InputStream open(File file) throws FileNotFoundException {
          return new FileInputStream(file);
        }

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

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

  /**
   * Factory for loading {@link ParcelFileDescriptor}s from {@link File}s.
   */
  public static class FileDescriptorFactory extends Factory<ParcelFileDescriptor> {

    public FileDescriptorFactory() {
      super(new FileOpener<ParcelFileDescriptor>() {
        @Override
        public ParcelFileDescriptor open(File file) throws FileNotFoundException {
          return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
        }

        @Override
        public void close(ParcelFileDescriptor parcelFileDescriptor) throws IOException {
          parcelFileDescriptor.close();
        }

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

上面两个Factory也是Glide初始化的时候注册到ModelLoaderRegistry中:

.append(File.class, InputStream.class, new FileLoader.StreamFactory())
.append(File.class, ParcelFileDescriptor.class, new FileLoader.FileDescriptorFactory())

2. DataFetcher

从前面ModelLoader<Model, Data>中有提到过DataFetcher<Data>这个类,这是真正做数据拉取的功能接口,基本上对应不同的ModelLoader会有对应的DataFetcher去实现拉取数据的工作,看下DataFetcher<Data>这个类的接口定义。里面有个内部接口DataCallback,在数据拉取成功后会通过回调接口返回。loadData就是真正发起数据请求的地方,getDataSource返回数据来源,这是个枚举。

// DataSource.java
public interface DataFetcher<T> {

  /**
   * Callback that must be called when data has been loaded and is available, or when the load
   * fails.
   *
   * @param <T> The type of data that will be loaded.
   */
  interface DataCallback<T> {

    /**
     * Called with the loaded data if the load succeeded, or with {@code null} if the load failed.
     */
    void onDataReady(@Nullable T data);

    /**
     * Called when the load fails.
     *
     * @param e a non-null {@link Exception} indicating why the load failed.
     */
    void onLoadFailed(@NonNull Exception e);
  }

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


  void cleanup();

  void cancel();

  /**
   * Returns the class of the data this fetcher will attempt to obtain.
   */
  @NonNull
  Class<T> getDataClass();

  /**
   * Returns the {@link com.bumptech.glide.load.DataSource} this fetcher will return data from.
   */
  @NonNull
  DataSource getDataSource();
}

看下在Glide中DataFetcher的继承结构,实现类还是很多,可以从AssetPath/Url/File/Uri等地方拉取数据。

DataFetcher.png

看下比较常用的HttpUrlFetcher,实现从Url加载返回一个InputStream:

/**
 * A DataFetcher that retrieves an {@link java.io.InputStream} for a Url.
 */
public class HttpUrlFetcher implements DataFetcher<InputStream>

loadData方法中,通过loadDataWithRedirects加载url返回result,如果成功就通过onDataReady返回数据:

// HttpUrlFetcher.java
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
callback.onDataReady(result);

loadDataWithRedirects中通过HttpURLConnection发起网络连接:

// HttpUrlFetcher.java    
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);
urlConnection.setInstanceFollowRedirects(false);

// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
// Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
stream = urlConnection.getInputStream();

看下另外几个接口方法的实现,这里DataSource明显就是REMOTE,返回的数据类型是InputStream.class,在cleanup中就是把sream和connection关闭:

  @Override
  public void cleanup() {
    if (stream != null) {
      try {
        stream.close();
      } catch (IOException e) {
        // Ignore
      }
    }
    if (urlConnection != null) {
      urlConnection.disconnect();
    }
    urlConnection = null;
  }

  @Override
  public void cancel() {
    isCancelled = true;
  }

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

  @NonNull
  @Override
  public DataSource getDataSource() {
    return DataSource.REMOTE;
  }

3.总结

再回头看前面提的问题,Glide提供很多ModelLoader接口的实现类来实现不同资源的数据加载,每个ModelLoader不是直接通过构造函数实例化,而是每个Loader内部提供的工厂类进行实例化,在Glide初始化的时候,会把这些工厂类都注册到ModelLoaderRegistry中,构造的时候从里面取出对应的工厂实例化ModelLoaderModelLoader下载数据会通过DataFetcherDataFetcher的实现类基本和ModelLoader对应。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271