Glide的三级缓存有了解过么?
- 先来了解一下我们常说的图片三级缓存
一般是强引用,软引用和文件系统,Android系统中提供了LruCache,通过维护一个LinkedHashMap来保存我们需要的各种类型数据,例如我们这里需要的Bitmap。LruCache一般我们会设置为系统最大存储空间的八分之一,而它的机制就是我们常说的最近最少使用原则
,如果Lru中的图片大小超过了默认大小,则会把最久使用的图片移除。
当图片被Lru移除时,我们需要手动将图片添加到软引用(SoftRefrence)中。需要维护一个软应用的集合在我们的项目中。
- 简单概括一下常用的三级缓存的流程:
先去Lru中找,有则直接取。
没有,则去SoftRefrence中找,有则取,同时将图片放回Lru中。
没有的话去文件系统找,有则取,同时将图片添加到Lru中。
没有就走下载图片逻辑,保存到文件系统中,并放到Lru中。
下面介绍一下Glide的缓存结构:
Glide缓存严格意义上说只有内存缓存和磁盘缓存,内存缓存中又分为Lru和弱引用缓存。
所以Glide的三级缓存可以分为:Lru缓存,弱引用缓存,磁盘缓存。
下面我们看一下Glide的读取顺序,这里有一点不同,我用的是Glide4.8版本,跟之前版本的写入顺序稍有不同。
截取部分源码:
@NonNull
Glide build(@NonNull Context context) {
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
GlideExecutor.newAnimationExecutor(),
isActiveResourceRetentionAllowed);
}
- memoryCache就是Glide使用的内存缓存,LruResourceCache类继承了LruCache,这部分可以自行查看一下源码。
通过上面可以看到,GLide#build()方法中实例化memoryCache作为Glide的内存缓存,并将其传给Engine作为构造器的入参。
- Engine.class 截取部分源码
{
//生成缓存key
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
//从弱应用中读取缓存
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;
}
//从LruCache中读取缓存
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<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
//开启线程池,加载图片
engineJob.start(decodeJob);
}
从上可知,Glide加载过程中使用loadFromActiveResources方法和loadFromCache方法来获取内存缓存的。
大致总结一下: 首先从弱引用读取缓存,没有的话通过Lru读取,有则取,并且加到弱引用中,如果没有会开启EngineJob进行后面的图片加载逻辑。
下面直接看之后的缓存部分代码:
- Engine#onEngineJobComplete()
public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
Util.assertMainThread();
// A null resource indicates that the load failed, usually due to an exception.
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
activeResources.activate(key, resource);
}
}
jobs.removeIfCurrent(key, engineJob);
}
void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key,
resource,
getReferenceQueue(),
isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
这里可以看到activeResources.activate(key, resource)
把EngineResource放到了弱引用中,至于lru的放置逻辑如下:
- EngineResource#release()
void release() {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call release on the main thread");
}
if (--acquired == 0) {
listener.onResourceReleased(key, this);
}
}
当acquired变量大于0的时候,说明图片正在使用中,也就应该放到activeResources弱引用缓存当中。而经过release()之后,如果acquired变量等于0了,说明图片已经不再被使用了,那么此时会调用listener的onResourceReleased()方法来释放资源。
- Engine#onResourceReleased()
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
Util.assertMainThread();
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
这里首先会将缓存图片从activeResources中移除,然后再将它put到LruResourceCache当中。这样也就实现了正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用LruCache来进行缓存的功能。
接下来就是Glide的磁盘缓存,磁盘缓存简单来说就是根据Key去DiskCache中取缓存,有兴趣可以自行看一下源码。
为什么选择Glide不选择其他的图片加载框架?
- Glide和Picasso
前者要更加省内存,可以按需加载图片,默认为ARGB_565,后者为ARGB_8888。
前者支持Gif,后者并不支持。
- Glide和Fresco
Fresco低版本有优势,占用部分native内存,但是高版本一样是java内存。
Fresco加载对图片大小有限制,Glide基本没有。
Fresco推荐使用SimpleDraweeView,涉及到布局文件,这就不得不考虑迁移的成本。
Fresco有很多native的实现,想改源码成本要大的多。
Glide提供对中TransFormation帮助处理图片,Fresco并没有。
Glide版本迭代相对较快。
Glide的几个显著的优点:
- 生命周期的管理
GLide#with
@NonNull
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
@NonNull
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
@NonNull
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
@NonNull
public static RequestManager with(@NonNull Fragment fragment) {
return getRetriever(fragment.getActivity()).get(fragment);
}
@Deprecated
@NonNull
public static RequestManager with(@NonNull android.app.Fragment fragment) {
return getRetriever(fragment.getActivity()).get(fragment);
}
可以看到有多个重载方法,主要对两类不同的Context进行不同的处理
- Application Context 图片加载的生命周期和应用程序一样,肯定是我们不推荐的写法。
- 其余Context,会像当前Activity创建一个隐藏的Fragment,绑定生命周期。
以Activity为例:
@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()
@NonNull
private RequestManager fragmentGet(@NonNull Context context,
@NonNull android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
//这就是绑定的Fragment,RequestManagerFragment
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
return requestManager;
}
接着看RequestManagerFragment
public class RequestManagerFragment extends Fragment {
@Override
public void onStart() {
super.onStart();
lifecycle.onStart();
}
@Override
public void onStop() {
super.onStop();
lifecycle.onStop();
}
@Override
public void onDestroy() {
super.onDestroy();
lifecycle.onDestroy();
unregisterFragmentWithRoot();
}
}
关联lifeCycle相应的方法。
简单来说就是通过#with()方法根据穿过来的不同的Context绑定生命周期。
- Bitmap对象池
Glide提供了一个BitmapPool来保存Bitmap。 简单来说就是当需要加载一个bitmap的时候,会根据图片的参数去池子里找到一个合适的bitmap,如果没有就重新创建。BitMapPool同样是根据Lru算法来工作的。从而提高性能。
- 高效缓存
缓存相关可以看上文描述,内存和磁盘,磁盘缓存也提供了几种缓存策略。
- NONE,表示不缓存任何内容
- SOURCE,表示只缓存原始图片
- RESULT,表示只缓存转换过后的图片(默认选项)
- ALL, 表示既缓存原始图片,也缓存转换过后的图片
文末
好了,今天的文章就到这里,感谢阅读,喜欢的话不要忘了三连。大家的支持和认可,是我分享的最大动力。
Android架构师系统进阶学习路线、58万字学习笔记、教学视频免费分享地址:点击这里免费获取
分享Android干货,交流Android技术。
对文章有何见解,或者有何技术问题,都可以在评论区一起留言讨论,我会虔诚为你解答。