美文网首页
Glide实现原理记录

Glide实现原理记录

作者: 饮料只喝芬达 | 来源:发表于2020-01-02 15:19 被阅读0次

Glide初始化,采用懒注册的方式,设置参数

Glide是一个单例,应用第一次使用Glide是会调用initializeGlide方法,编译期根据GlideModule或清单文件中的模块命名(已弃用)生成GeneratedAppGlideModuleImpl类,提供初始化构造参数的方法填充GlideBuilder,最终build出Glide
GlideModule注解类,提供初始化默认参数修改,AppGlideModule提供两个方法

  • isManifestParsingEnabled 是否读取清单文件中的参数
  • applyOptions 修改GlideBuilder的默认初始化参数,设置缓存参数之类
    使用方法是新建类继承这个类并且加上GlideModule注解,按照需要重写上面两个方法完成初始化参数设置
/**
Defines a set of dependencies and options to use when initializing Glide within an application.
...
*/
public abstract class AppGlideModule extends LibraryGlideModule implements AppliesOptions {
  /**
   * Returns {@code true} if Glide should check the AndroidManifest for {@link GlideModule}s.
   *
   * <p>Implementations should return {@code false} after they and their dependencies have migrated
   * to Glide's annotation processor.
   *
   * <p>Returns {@code true} by default.
   */
  public boolean isManifestParsingEnabled() {
    return true;
  }

  @Override
  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
    // Default empty impl.
  }
}
/**
 * Registers a set of components to use when initializing Glide within an app when
*/
public abstract class LibraryGlideModule implements RegistersComponents {
  @Override
  public void registerComponents(@NonNull Context context, @NonNull Glide glide,
      @NonNull Registry registry) {
    // Default empty impl.
  }
}

registerComponents可增加解析组件
例子:增加webp格式的图片加载

@GlideModule
public class WebpGlideLibraryModule extends LibraryGlideModule {

    @Override
    public void registerComponents(Context context, Glide glide, Registry registry) {

        // We should put our decoder before the build-in decoders,
        // because the Downsampler will consume arbitrary data and make the inputstream corrupt
        // on some devices
        final Resources resources = context.getResources();
        final BitmapPool bitmapPool = glide.getBitmapPool();
        final ArrayPool arrayPool = glide.getArrayPool();
        /* static webp decoders */
        WebpDownsampler webpDownsampler = new WebpDownsampler(registry.getImageHeaderParsers(),
                resources.getDisplayMetrics(), bitmapPool, arrayPool);
        AnimatedWebpBitmapDecoder bitmapDecoder = new AnimatedWebpBitmapDecoder(arrayPool, bitmapPool);
        ByteBufferBitmapWebpDecoder byteBufferBitmapDecoder = new ByteBufferBitmapWebpDecoder(webpDownsampler);
        StreamBitmapWebpDecoder streamBitmapDecoder = new StreamBitmapWebpDecoder(webpDownsampler, arrayPool);
        /* animate webp decoders */
        ByteBufferWebpDecoder byteBufferWebpDecoder =
                new ByteBufferWebpDecoder(context, arrayPool, bitmapPool);
        registry
                /* Bitmaps for static webp images */
                .prepend(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder)
                .prepend(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder)
                /* BitmapDrawables for static webp images */
                .prepend(
                        Registry.BUCKET_BITMAP_DRAWABLE,
                        ByteBuffer.class,
                        BitmapDrawable.class,
                        new BitmapDrawableDecoder<>(resources, byteBufferBitmapDecoder))
                .prepend(
                        Registry.BUCKET_BITMAP_DRAWABLE,
                        InputStream.class,
                        BitmapDrawable.class,
                        new BitmapDrawableDecoder<>(resources, streamBitmapDecoder))
                /* Bitmaps for animated webp images*/
                .prepend(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class,
                        new ByteBufferAnimatedBitmapDecoder(bitmapDecoder))
                .prepend(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class,
                        new StreamAnimatedBitmapDecoder(bitmapDecoder))
                /* Animated webp images */
                .prepend(ByteBuffer.class, WebpDrawable.class, byteBufferWebpDecoder)
                .prepend(InputStream.class, WebpDrawable.class, new StreamWebpDecoder(byteBufferWebpDecoder, arrayPool))
                .prepend(WebpDrawable.class, new WebpDrawableEncoder());
    }
}

3级缓存结构

缓存优先级 ActiveResources,MemoryCache,DiskCache
从最终的load方法可以看出,加载会先取ActiveResources,再取MemoryCache如果有的话会从MemoryCache中删除并且添加到ActiveResources中,最后创建EngineJob异步取本地缓存或网络获取,获取成功后会添加到ActiveResources中,ActiveResources利用ReferenceQueue在弱应用被回收时从ActiveResources中移除并添加到MemoryCache中。

  public <R> LoadStatus load(...) {
    ...
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }

    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }

    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

    EngineJob<R> engineJob =
        engineJobFactory.build(...);

    DecodeJob<R> decodeJob =
        decodeJobFactory.build(...);

    jobs.put(key, engineJob);

    engineJob.addCallback(cb);
    engineJob.start(decodeJob);

    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }
  • ActiveResources可以理解为是当前正在被引用的资源弱引用(WeakReference)
  • MemoryCache是内存缓存,可设置大小限制,不会包含ActiveResources中的资源
  • DiskCache 本地文件缓存,可设置缓存路径及缓存文件总大小
    DiskCache本地缓存,首次获取本地缓存时会触发本地文件缓存扫描,缓存目录会有一个journal文件
    里面记录了本地缓存文件列表及信息,如下

libcore.io.DiskLruCache
1
1
1

CLEAN b31dfe4cd710e8868de840b27c2feba6443539b378343717c28174c5ff4c3a51 8318
CLEAN 3802507bffb423d3e9455801d15a5d82937e505f82134e0675e3096ecadbea5b 10414
CLEAN 28e5a4b66341ef88252f41602144354db0aa878c1863262e9950559df4488a1f 15396

  private void readJournal() throws IOException {
    StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
    try {
      String magic = reader.readLine();
      String version = reader.readLine();
      String appVersionString = reader.readLine();
      String valueCountString = reader.readLine();
      String blank = reader.readLine();
      if (!MAGIC.equals(magic)
          || !VERSION_1.equals(version)
          || !Integer.toString(appVersion).equals(appVersionString)
          || !Integer.toString(valueCount).equals(valueCountString)
          || !"".equals(blank)) {
        throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
            + valueCountString + ", " + blank + "]");
      }
      ...
  }

journal文件前5行目前来看只是起到了判断文件是否是glide缓存目录文件的一个规则,后面才是缓存文件信息,以空格分开,分别是文件状态/文件名/文件大小,目前来看只有CLEAN状态的文件才有文件大小信息
文件状态有

  private static final String CLEAN = "CLEAN"; //干净的数据
  private static final String DIRTY = "DIRTY";  //脏数据,可能正在被修改
  private static final String REMOVE = "REMOVE"; //已被删除
  private static final String READ = "READ";  //正在被读取

文件名有后缀,journal文件中文件名信息并不完整,可能考虑以后缓存多分文件所以是以name.0开始命名
缓存文件。

    private Entry(String key) {
      this.key = key;
      this.lengths = new long[valueCount];
      cleanFiles = new File[valueCount];
      dirtyFiles = new File[valueCount];

      // The names are repetitive so re-use the same builder to avoid allocations.
      StringBuilder fileBuilder = new StringBuilder(key).append('.');
      int truncateTo = fileBuilder.length();
      for (int i = 0; i < valueCount; i++) {
          fileBuilder.append(i);
          cleanFiles[i] = new File(directory, fileBuilder.toString());
          fileBuilder.append(".tmp");
          dirtyFiles[i] = new File(directory, fileBuilder.toString());
          fileBuilder.setLength(truncateTo);
      }
    }

Glide的生命周期

Glide通过with(context)的方法创建一个RequestManager,这里要说的就是这个manager的生命周期,

  @NonNull
  public RequestManager get(@NonNull Context context) {
    if (context == null) {
      throw new IllegalArgumentException("You cannot start a load on a null Context");
    } else if (Util.isOnMainThread() && !(context instanceof Application)) {
      if (context instanceof FragmentActivity) {
        return get((FragmentActivity) context);
      } else if (context instanceof Activity) {
        return get((Activity) context);
      } else if (context instanceof ContextWrapper) {
        return get(((ContextWrapper) context).getBaseContext());
      }
    }

    return getApplicationManager(context);
  }

如上代码如果context是Application,则生命跟随进程,若为Activity或FragmentActivity,则会创建一个空SupportRequestManagerFragment加到Activity中,监听activity的生命周期

  private RequestManager supportFragmentGet(
      @NonNull Context context,
      @NonNull FragmentManager fm,
      @Nullable Fragment parentHint,
      boolean isParentVisible) {
    SupportRequestManagerFragment current =
        getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      // TODO(b/27524013): Factor out this Glide.get() call.
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }

由此即绑定了RequestManager与context的生命周期,发生变化后会通知给RequestManager中的Request和Target

  @Override
  public void onStart() {
    resumeRequests();
    targetTracker.onStart();
  }

  /**
   * Lifecycle callback that unregisters for connectivity events (if the
   * android.permission.ACCESS_NETWORK_STATE permission is present) and pauses in progress loads.
   */
  @Override
  public void onStop() {
    pauseRequests();
    targetTracker.onStop();
  }

  /**
   * Lifecycle callback that cancels all in progress requests and clears and recycles resources for
   * all completed requests.
   */
  @Override
  public void onDestroy() {
    targetTracker.onDestroy();
    for (Target<?> target : targetTracker.getAll()) {
      clear(target);
    }
    targetTracker.clear();
    requestTracker.clearRequests();
    lifecycle.removeListener(this);
    lifecycle.removeListener(connectivityMonitor);
    mainHandler.removeCallbacks(addSelfToLifecycle);
    glide.unregisterRequestManager(this);
  }

以此实现请求的暂停/继续与结束,该功能为我们实现滑动时暂停图片加载提供基础帮助。

相关文章

  • Glide实现原理记录

    Glide初始化,采用懒注册的方式,设置参数 Glide是一个单例,应用第一次使用Glide是会调用initial...

  • Glide 缓存原理实现

    Glide 缓存原理实现 专注于Android开发,分享经验总结,欢迎加入QQ群:686809487 Glide缓...

  • Glide实现原理

    一.Glide缓存机制 Glide采取的多级缓存机制,能够较为友好地实现图片、动图的加载。其主要有 内存缓存+磁盘...

  • 2019大厂Android高级面试题

    阿里巴巴面试整理 线程原理 垃圾回收机制的实现 Https原理 Handler实现线程通信 Glide对Bitma...

  • Glide如何自动管理生命周期

    Glide管理生命周期还是很有想法的,这种思路值得我们去学习和应用; 特点 实现原理 源码分析 Glide.wit...

  • 模仿Glide监听Activity的生命周期

    实现原理 由文档得知,Glide可以根据Activity的生命周期自动停止或重新加载图片,但使用Glide时并不需...

  • Glide 图片加载连接超时

    参考文章 Glide 4.x添加自定义组件原理Glide 系列-1:预热、Glide 的常用配置方式及其原理Gli...

  • Glide 4.0 缓存实现原理

    Glide的缓存分两个模块,一个是内存缓存,一个是硬盘缓存。 这两个缓存的作用各不相同,内存缓存的主要作用是防止应...

  • Glide 源码学习补漏,Glide 图片缓存原理探究

    基于 Gilde 4.3.1 上一篇Glide 源码学习,了解 Glide 图片加载原理中分析了 Glide 加载...

  • Glide源码解析(4.X版本)

    知识点汇总: 一:Glide项目概述 二:Glide加载图片的原理 三:Glide三级缓存的设计 四:Glide如...

网友评论

      本文标题:Glide实现原理记录

      本文链接:https://www.haomeiwen.com/subject/msinoctx.html