Android - thirdlib - glide

Glide 是一款 Android 图片加载框架。

本文基于 Glide 4.11.0 展开说明

关键字

线程池、LruCache、DiskLruCache、LinkedHashMap、OOM

1. Glide 的使用

1.1 常规使用

RequestOptions requestOptions = new RequestOptions()
        .transform(new CircleCrop()); // 图片转换,圆角/模糊等,可模仿自定义 transform, implementation 'jp.wasabeef:glide-transformations:3.0.1'
        .placeholder(R.drawable.ic_launcher_background)
        .error(R.drawable.ic_launcher_foreground)
        .override(Target.SIZE_ORIGINAL) // 设置显示大小
        .skipMemoryCache(false) // 设置内存缓存
        .diskCacheStrategy(DiskCacheStrategy.ALL); // 设置磁盘缓存

Glide.with(this)
        .asDrawable() // asBitmap() / asGif() / asFile()
        .load(url) // load(new GlideUrl(url)) / load(file) / load(bitmap) / load(drawable) 
        .apply(requestOptions)
        .into(imageView);

1.2 监听回调使用

// 1. 仅预加载不显示,目的是缓存
Glide.with(this)
        .load(url)
        .preload(); 

// 2. into 自定义Target,自己进行图片显示操作
Glide.with(this)
        .load(url)
        .into(new CustomTarget<Drawable>() {
            @Override
            public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
                imageView.setImageDrawable(resource);
            }
            ...
        });

// 3. listener 回调监听
Glide.with(this)
        .load(url)
        .listener(new RequestListener<Drawable>() {
            @Override
            public boolean onLoadFailed(GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                return false;
            }

            @Override
            public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                return true; // true 表拦截
            }
        })
        .into(imageView);
        
// 4. submit 属于阻塞操作
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        FutureTarget<File> submit = Glide.with(Util.getAppContext())
                .asFile()
                .load(url)
                .submit();
        try {
            File file = submit.get();
        } catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
        }
    }
};
Thread thread = new Thread(runnable);
thread.start();

1.3 配置使用

  1. 对 Glide 进行配置
  2. 注册组件,如切换网络请求模块为 OkHttp
  3. 通过使用 Generated API 功能可恢复 Glide 3.0 使用方式,可自查。(@GlideExtension / @GlideOption)
@GlideModule
public class MyGlideModule extends AppGlideModule {
    public static final int DISK_CACHE_SIZE = 500 * 1024 * 1024;

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        builder
                .setMemoryCache(new LruResourceCache(1024)) // 设置内存缓存大小
                .setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context, DISK_CACHE_SIZE)) // 设置硬盘缓存大小
                .setDiskCacheExecutor(GlideExecutor.newDiskCacheExecutor()) // 设置硬盘存储位置
                .setDefaultRequestOptions(new Glide.RequestOptionsFactory() {
                    @NonNull
                    @Override
                    public RequestOptions build() {
                        return new RequestOptions().format(DecodeFormat.PREFER_RGB_565); // 设置图片存储格式
                    }
                });
    }

    @Override
    public void registerComponents(Context context, Glide glide, Registry registry) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.addInterceptor(new ProgressInterceptor());
        OkHttpClient okHttpClient = builder.build();
        // 更换网络请求组件
        registry.replace(GlideUrl.class, InputStream.class,
                new OkHttpGlideUrlLoader.Factory(okHttpClient));
    }
}

2 图片库框架对比

Glide特点:

  1. 拥有生命周期(采用提供隐藏Fragment方式)
    1. 注意:非主线程都采用 ApplicationContext
  2. 高效处理Bitmap(Bitmap的复用和主动回收,减少系统回收压力)
  3. 缓存策略
    1. 多种尺寸
    2. 多种格式(WebP、Gif、Video)
    3. 三级缓存(弱引用,内存,磁盘)

Fresco特点:

  1. 最大优势在5.0以下系统,图片会存放在一个特别的内存区域(Ashmem区,共享内存区属于native堆)
  2. Native层对OOM进行处理,不占用App内存
  3. 适用于加载大量图片场景

Android 图片存储位置

2 手写图片加载框架涉

结合 Glide 实现分析

  1. 异步加载:线程池
  2. 线程切换:Handler
  3. 缓存功能:LruCache、DiskLruCache
  4. 防止OOM:软引用、LruCache、图片压缩、Bitmap像素存储位置(native/java)
  5. 内存泄漏:ImageView正确引用、生命周期管理
  6. 列表错位:加载错乱、队满任务过多问题

2.1 异步加载

线程池,多少个?

基本:读网络一个,读硬盘一个。

public final class GlideBuilder {
  ...
  private GlideExecutor sourceExecutor; //加载源文件的线程池,包括网络加载
  private GlideExecutor diskCacheExecutor; //加载硬盘缓存的线程池
  ...
  private GlideExecutor animationExecutor; //动画线程池

Glide 使用了三个线程池:网络、硬盘、动画。

网络请求会阻塞,所以单独采用一个线程池。可以替换OkHttp内置线程池。

硬盘加载采用一个线程池。

2.2 切换线程 Handler

异步加载成功,主线程更新 ImageView。

RxJava、EventBus、Glide等切换线程都采用 Handler 方式。

.into(
   glideContext.buildImageViewTarget(view, transcodeClass),
   /*targetListener=*/ null,
   requestOptions,
   Executors.mainThreadExecutor());

public final class Executors {
  ...
  private static final Executor MAIN_THREAD_EXECUTOR =
      new Executor() {
        private final Handler handler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(@NonNull Runnable command) {
          handler.post(command);
        }
      };
  ...
  /** Posts executions to the main thread. */
  public static Executor mainThreadExecutor() {
    return MAIN_THREAD_EXECUTOR;
  }

2.3 缓存

图片的三级缓存:内存缓存、硬盘缓存、网络缓存。

2.3.1 内存缓存 LruCache

一般采用 LruCache,Glide 还结合了一种弱引用机制,共同完成内存缓存功能。

public class Engine ... {
  ...
  private final ActiveResources activeResources;
  ...
  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key) {
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }
    return active;
  }

  private EngineResource<?> loadFromCache(Key key) {
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }
  ...

Glide 自己实现了 LruCache,没有采用 java.util.LruCache

public class LruCache<T, Y> {
  // initialCapacity为100,java.util.LruCache为0,HashMap默认大小为16
  private final Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
  ...

补充:LruCache 源码

LruCache 内部采用 LinkedHashMap,accessOrder 参数传true,表示按照访问顺序排序。

LinkedHashMap 继承 HashMap 数组+链表(红黑树)的结构

HashMap 的 newNode 返回 HashMap.Node
LinkedHashMap 重写了 newNode() 返回 LinkedHashMapEntry (继承 HashMap.Node,并添加了 before/after)

超过一定大小,则移除最老的数据 trimToSize(maxSize)

2.3.2 磁盘缓存 DiskLruCache

DiskLruCache 跟 LruCache 实现思路是差不多的,一样是设置一个总大小,每次往硬盘写文件,总大小超过阈值,就会将旧的文件删除。

同样是利用LinkHashMap的特点,只不过数组里面存的 Entry 有点变化,Editor 用于操作文件。

2.4 防止OOM

Bitmap 之所以占用内存,不是因为对象大,而是因为 Bitmap 的像素数据。

Bitmap 的像素数据大小 = 宽 * 高 * 1像素占用的内存

宽 / 高: 一般字缩放后的宽高。(依赖 BitmapFactory.Options# inJustDecodeBounds的值,若 true 为原图宽高 )
1像素占用内存: RGB_565格式占用 2 byte;ARGB_8888格式占用 4 byte;

  1. 软引用
    1. 软 OOM 才回收
    2. 弱 发生 GC 即回收
  2. onTrimMemory / onLowMemory
// Glide
  @Override
  public void onLowMemory() {
    clearMemory();
  }
  
  public void clearMemory() {
    // Engine asserts this anyway when removing resources, fail faster and consistently
    Util.assertMainThread();
    // memory cache needs to be cleared before bitmap pool to clear re-pooled Bitmaps too. See #687.
    memoryCache.clearMemory();
    bitmapPool.clearMemory();
    arrayPool.clearMemory();
  }
  1. 像素存储位置
    1. 系统为每个进程,即每个虚拟机分配的内存是有限的
    2. 虚拟机内存主要划分 5 部分:虚拟机栈、本地方法栈、程序计数器、方法区、堆(是JVM最大的一块内存,OOM发生地)
    3. 像素数据 8.0 以后在 naitve 堆,8.0以下在 java 堆
    4. finalize 6.0以前 Bitmap 释放 native 层对象

2.5 内存泄漏

  1. 采用弱引用,简单粗暴
  2. 生命周期管理,ImageView回收 / 加载任务取消 / 未执行队列移除
// Glide的RequestManager
  @Override
  public synchronized 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);
  }

2.6 列表加载问题

  1. 图片错乱
    1. ImageView 设置tag(一般是图片url),更新ImageView时判断tag和url是否一致
    2. item列表消失,取消对应图片加载任务(考虑在图片框架做 还是 app处理)
  2. 线程池任务过多
    1. 列表滑动,第一次没有缓存会有大量请求,判断队列中是否有该任务,存在则不添加队列。

5 参考资料

Glide 最全解析 | 自:郭霖
面试官:简历上最好不要写Glide,不是问源码那么简单 | 自:蓝师傅_Android

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

推荐阅读更多精彩内容