Glide 作为一个出色的图片加载框架,对其剖析的文章数不胜数。而若像大多数文章一样常规的去分析源码就没什么新意了,本文旨在发掘一些新的或相对陌生的知识点,以完善对 Glide 的认知,涉及源码基于 v4.8.0。
主要内容:
- 1.磁盘缓存
- 2.内存缓存
- 3.网络请求
- 4.图片转换
- 5.感知生命周期
- 6.下载及预加载
- 7.加载图片到通知栏和应用小部件中
- 8.图片格式及内存优化
- 9.请求优先级及原理
- 10.缩略图使用及原理
- 11.展示 gif 原理
- 12.自定义模块及延伸
- 13.兼容3.x写法
1.磁盘缓存
Glide 提供了五种磁盘缓存策略:
- DiskCacheStrategy.AUTOMATIC
- DiskCacheStrategy.ALL
- DiskCacheStrategy.RESOURCE
- DiskCacheStrategy.DATA
- DiskCacheStrategy.NONE
默认为 AUTOMATIC 策略,即自动采用最佳策略,它会针对本地和远程图片使用不同的策略。当你加载远程数据时,AUTOMATIC 策略仅会存储未被你的加载过程修改过(比如变换、裁剪)的原始数据,因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图,因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。即:
本地数据 -> DiskCacheStrategy.RESOURCE
远程数据 -> DiskCacheStrategy.DATA
源码中是在何处实现这一逻辑的呢?
2.内存缓存
3.网络请求
4.图片转换
5.感知生命周期
发起一个图片加载请求后,我们期望当该请求所处的界面 onStop 时请求也随之停止,再次 onStart 时请求能够随之继续, onDestroy 时请求能够随之销毁。这就需要能够感知当前 Activity 的生命周期变化,由于 Fragment 在 onAttach 之后与 Activity 有相同的生命周期,glide 利用这一点,通过给 Activity 添加一个无界面的 Fragment 实现感知。
发起请求时通过 with 方法传入上下文,此方法会返回一个 RequestManager,RequestManager 用于管理和启动图片加载请求,可以感知外部 Activity 的生命周期,从而管理请求随之启动、停止和重启。
先来分析一个较为简单的流程:with 方法传入 Activity,会调用到 RequestManagerRetriever 的 get 方法:
@NonNull
public RequestManager get(@NonNull Activity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(
activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
其中调用 fragmentGet 方法去新建 RequestManager :
@NonNull
private RequestManager fragmentGet(@NonNull Context context,
@NonNull android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
//这里新建了一个无界面的 Fragment,并添加到该界面
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
Glide glide = Glide.get(context);
requestManager =
//这里新建了一个 requestManager,并将无界面 Fragment 的生命周期暴露给 requestManager
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
当无界面 Fragment 生命周期变化时,通过接口回调出去给 requestManager,这样 requestManager 就实现了随外部生命周期变化自动启动、停止和重启请求。with 方法若传入其他参数,流程上也是大同小异,都是找到当前 Activity 或 Fragment ,给其添加一个无界面 Fragment 罢了。
而不管传入何参数,都有这样一个逻辑:
if (Util.isOnBackgroundThread()) {
return get(view.getContext().getApplicationContext());
}
若当前处于非主线程,则一律基于应用生命周期请求,不再关心所在 Fragment 或 Activity 的生命周期,这是因为子线程中执行的任务本身就是跟所在界面生命周期无关的。
在分析这一块时涉及 ContextWrapper 相关的逻辑,若不太熟悉,可参考:ContextWrapper 。
Glide 推出时谷歌还未发布 Architecture Components,而现在若要实现一个可感知生命周期的逻辑,大可不必像 Glide 一样添加一个 Fragment ,直接使用 Architecture Components 中的 Lifecycle 组件就可以很方便的实现了。
6.下载及预加载
下载的标准写法如下,也是官方示例写法:
@WorkerThread
private void downloadFile() {
FutureTarget<File> target = null;
try {
target = Glide.with(context)
.downloadOnly()
.load(imgUrl)
.submit();
final File cacheFile = target.get();
/*
* 默认会下载到磁盘缓存中,理论上不应对缓存文件进行编辑、删除
*/
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "download: ", e);
} finally {
// 这里要调用cancel方法取消等待操作并释放资源
if (target != null) {
target.cancel(true); // 若传true则允许中断操作
}
}
}
此方式要自行开子线程,你可能会觉得稍显麻烦,直接调用 listener 方法监听 onResourceReady 回调岂不是更简单?其实不是的,由于要拿到 FutureTarget 调用其 cancel 方法,若监听 onResourceReady 代码逻辑会更复杂。
对于 FutureTarget.get() 方法,并不是调用时才会去加载数据,调用 submit 方法后就已经开始去加载数据了,get 方法最终会调用到 RequestFutureTarget 的 doGet 方法如下:
private synchronized R doGet(Long timeoutMillis)
throws ExecutionException, InterruptedException, TimeoutException {
if (assertBackgroundThread && !isDone()) {
Util.assertBackgroundThread();
}
if (isCancelled) {
throw new CancellationException();
} else if (loadFailed) {
throw new ExecutionException(exception);
} else if (resultReceived) {
return resource;
}
if (timeoutMillis == null) {
waiter.waitForTimeout(this, 0);
} else if (timeoutMillis > 0) {
long now = System.currentTimeMillis();
long deadline = now + timeoutMillis;
while (!isDone() && now < deadline) {
waiter.waitForTimeout(this, deadline - now);
now = System.currentTimeMillis();
}
}
if (Thread.interrupted()) {
throw new InterruptedException();
} else if (loadFailed) {
throw new ExecutionException(exception);
} else if (isCancelled) {
throw new CancellationException();
} else if (!resultReceived) {
throw new TimeoutException();
}
return resource;
}
可以看到 get 方法内部并没有加载数据的逻辑, RequestFutureTarget 内部通过锁实现了 get 方法的阻塞调用,当资源加载完毕后 onResourceReady 中会解除阻塞:
@Override
public synchronized boolean onResourceReady(
R resource, Object model, Target<R> target, DataSource dataSource, boolean isFirstResource) {
// We might get a null result.
resultReceived = true;
this.resource = resource;
waiter.notifyAll(this);
return false;
}
除了下载 File 类型以外,还可以指定下载类型,比如下载 Bitmap:
@WorkerThread
private void downloadBitmap() {
RequestOptions DOWNLOAD_ONLY_OPTIONS = RequestOptions
.diskCacheStrategyOf(DiskCacheStrategy.DATA) //这边其实可以根据业务场景配置,如果是网络图片一般需要缓存
.priority(Priority.LOW) // 设置优先级
.skipMemoryCache(true);
FutureTarget<Bitmap> target = null;