BAT 面试常被问的几个 Glide 问题,记录一下
Posted 清风Coolbreeze
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BAT 面试常被问的几个 Glide 问题,记录一下相关的知识,希望对你有一定的参考价值。
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, 表示既缓存原始图片,也缓存转换过后的图片
最后,在这里我分享一份自己收录整理上述技术体系图相关的几十套腾讯、百度、阿里、美团、字节等公司历年年的社招面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
还有 高级架构技术进阶思维图、Android开发面试专题PDF,高级进阶架构文档帮助大家学习提升进阶,也节省大家在网上搜索的时间来学习,也希望大家可以分享给身边好友一起学习。最后祝愿大家都能有个光明的未来。
有需要的朋友可以【直接加入此处打包获取】
以上是关于BAT 面试常被问的几个 Glide 问题,记录一下的主要内容,如果未能解决你的问题,请参考以下文章
Android异步消息传递机制源码分析&&相关知识常被问的面试题