注册

Android面试:80%的面试官关于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 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算法来工作的。从而提高性能。



  • 高效缓存

缓存相关可以看上文描述,内存和磁盘,磁盘缓存也提供了几种缓存策略。



  1. NONE,表示不缓存任何内容
  2. SOURCE,表示只缓存原始图片
  3. RESULT,表示只缓存转换过后的图片(默认选项)
  4. ALL, 表示既缓存原始图片,也缓存转换过后的图片

文末


好了,今天的文章就到这里,感谢阅读,喜欢的话不要忘了三连。大家的支持和认可,是我分享的最大动力。


对文章有何见解,或者有何技术问题,都可以在评论区一起留言讨论,我会虔诚为你解答。


0 个评论

要回复文章请先登录注册