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 配置使用
- 对 Glide 进行配置
- 注册组件,如切换网络请求模块为 OkHttp
- 通过使用 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特点:
- 拥有生命周期(采用提供隐藏Fragment方式)
- 注意:非主线程都采用 ApplicationContext
- 高效处理Bitmap(Bitmap的复用和主动回收,减少系统回收压力)
- 缓存策略
- 多种尺寸
- 多种格式(WebP、Gif、Video)
- 三级缓存(弱引用,内存,磁盘)
Fresco特点:
- 最大优势在5.0以下系统,图片会存放在一个特别的内存区域(Ashmem区,共享内存区属于native堆)
- Native层对OOM进行处理,不占用App内存
- 适用于加载大量图片场景
Android 图片存储位置
2 手写图片加载框架涉
结合 Glide 实现分析
- 异步加载:线程池
- 线程切换:Handler
- 缓存功能:LruCache、DiskLruCache
- 防止OOM:软引用、LruCache、图片压缩、Bitmap像素存储位置(native/java)
- 内存泄漏:ImageView正确引用、生命周期管理
- 列表错位:加载错乱、队满任务过多问题
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;
- 软引用
- 强
- 软 OOM 才回收
- 弱 发生 GC 即回收
- 虚
- 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();
}
- 像素存储位置
- 系统为每个进程,即每个虚拟机分配的内存是有限的
- 虚拟机内存主要划分 5 部分:虚拟机栈、本地方法栈、程序计数器、方法区、堆(是JVM最大的一块内存,OOM发生地)
- 像素数据 8.0 以后在 naitve 堆,8.0以下在 java 堆
- finalize 6.0以前 Bitmap 释放 native 层对象
2.5 内存泄漏
- 采用弱引用,简单粗暴
- 生命周期管理,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 列表加载问题
- 图片错乱
- ImageView 设置tag(一般是图片url),更新ImageView时判断tag和url是否一致
- item列表消失,取消对应图片加载任务(考虑在图片框架做 还是 app处理)
- 线程池任务过多
- 列表滑动,第一次没有缓存会有大量请求,判断队列中是否有该任务,存在则不添加队列。
5 参考资料
Glide 最全解析 | 自:郭霖
面试官:简历上最好不要写Glide,不是问源码那么简单 | 自:蓝师傅_Android
网友评论