上一篇文章我们分析了一个最简单的图片加载的流程,整个过程从代码上来看虽然不难,但是比较绕,今天我们继续昨天的分析来看看Engine是如何加载图片的,
惯例还是和上一篇文章一样,先介绍一下在Engine加载过程中一些重要的角色
1.Key 请求的标识,如果默认为 EmptySignature ,
2.EngineKey 根据Url key width height transformations 生成的标识,用这么多去进行hashcode 是为了尽量保证EngineKey 的唯一性,
3.MemoryCache 一个接口,定义了一些管理资源的方法,子类通过继承LruCache 并实现这些方法,内存缓存策略
4.ActiveResources 顾名思义活动的资源, 一种内存缓存的策略,
ActiveResources 和MemoryCache 同样都是内存缓存,还是有一定的区别的,本质的区别就是MemoryCache 缓存的数量要远大于ActiveResources 的数量,为什么这么说,我们先来简单说一下LruCache ,LruCache 是通过设置最大内存来限制图片的存储量的,如果我们加载的图片比较小,那么整个LruCache 中所存储的图片数据就会比较多,但是并不是多有的资源都是和当前页面有关系,如果将所有的数据都放在LruCache 缓存中,那么所有的新数据都应该在队尾,这样就产生了一些暂时用不到的数据来干扰我们匹配内存缓存的速度,而ActiveResources 的引入就是为了解决这个问题,我们知道RequestManager是与生命周期绑定的,我们将与当前RequestManager绑定的图片资源放入到ActiveResources中,那么在匹配的过程中就不会去所有的缓存中去找,从而减少匹配时间, 那么他是何时将数据添加到ActiveResources 中,什么时候将它移出的呢,这个问题我们先保留一下,下面我们会在必要的时候做一下详细的介绍
EngineJob 加载调度类,
EngineJob任务正在进行中的中间类,如果有那么会在进行中的会将callback添加到EngineJob 中的callback List中,在请求结束后会遍历这个list通知所有上层加载完毕,同时也进行着线程池的调度
Jobs 加载调度管理类,管理着EngineJob
DecodeJob 解码资源和转换的类
ResourceRecycler 资源回收类
LazyDiskCacheProvider 磁盘资源管理类
功能类介绍完了,我们就开始分析engine, 先看一下他的构造方法
Engine(MemoryCache cache,
DiskCache.Factory diskCacheFactory,
GlideExecutor diskCacheExecutor,
GlideExecutor sourceExecutor,
GlideExecutor sourceUnlimitedExecutor,
GlideExecutor animationExecutor,
Jobs jobs,
EngineKeyFactory keyFactory,
ActiveResources activeResources,
EngineJobFactory engineJobFactory,
DecodeJobFactory decodeJobFactory,
ResourceRecycler resourceRecycler,
boolean isActiveResourceRetentionAllowed) {
this.cache = cache;
this.diskCacheProvider = new LazyDiskCacheProvider(diskCacheFactory);
if (activeResources == null) {
activeResources = new ActiveResources(isActiveResourceRetentionAllowed);
}
this.activeResources = activeResources;
activeResources.setListener(this);
if (keyFactory == null) {
keyFactory = new EngineKeyFactory();
}
this.keyFactory = keyFactory;
if (jobs == null) {
jobs = new Jobs();
}
this.jobs = jobs;
if (engineJobFactory == null) {
engineJobFactory =
new EngineJobFactory(
diskCacheExecutor, sourceExecutor, sourceUnlimitedExecutor, animationExecutor, this);
}
this.engineJobFactory = engineJobFactory;
if (decodeJobFactory == null) {
decodeJobFactory = new DecodeJobFactory(diskCacheProvider);
}
this.decodeJobFactory = decodeJobFactory;
if (resourceRecycler == null) {
resourceRecycler = new ResourceRecycler();
}
this.resourceRecycler = resourceRecycler;
cache.setResourceRemovedListener(this);
}
可以看到初始化了很多factory , 设置了一些监听,我们从上一篇文章已经Engine 的初始化是在Glide中进行的,也就是整个工程会共享同一个,
我们再来看一下加载最重要的部分load方法
public synchronized <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb,
Executor callbackExecutor) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);///先构造唯一key
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);///首先去ActiveResources 中查找
if (active != null) {//存在则返回
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);///再去cache 中查找
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);///查看是否在加载中,或者已经加载过
if (current != null) {//如果在加载中,直接将回调放入回调list中
current.addCallback(cb, callbackExecutor);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
///最后去加载
engineJob.start(decodeJob);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
整个load的逻辑非常的简单,我们先将它分为5步进行,
1. 构造EngineKey 唯一key ,方法
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);///先构造唯一key
EngineKeyFactory.buildKey
class EngineKeyFactory {
@SuppressWarnings("rawtypes")
EngineKey buildKey(Object model, Key signature, int width, int height,
Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass,
Class<?> transcodeClass, Options options) {
return new EngineKey(model, signature, width, height, transformations, resourceClass,
transcodeClass, options);
}
}
这里就是利用EngineKeyFactory 来new 这个唯一key,那么他是如何比对这个key来判断他是否是唯一,继续看一下EngineKey 的equals 和 hashCode 方法
EngineKey equals和hashCode
@Override
public boolean equals(Object o) {
if (o instanceof EngineKey) {
EngineKey other = (EngineKey) o;
return model.equals(other.model)
&& signature.equals(other.signature)
&& height == other.height
&& width == other.width
&& transformations.equals(other.transformations)
&& resourceClass.equals(other.resourceClass)
&& transcodeClass.equals(other.transcodeClass)
&& options.equals(other.options);
}
return false;
}
@Override
public int hashCode() {
if (hashCode == 0) {
hashCode = model.hashCode();
hashCode = 31 * hashCode + signature.hashCode();
hashCode = 31 * hashCode + width;
hashCode = 31 * hashCode + height;
hashCode = 31 * hashCode + transformations.hashCode();
hashCode = 31 * hashCode + resourceClass.hashCode();
hashCode = 31 * hashCode + transcodeClass.hashCode();
hashCode = 31 * hashCode + options.hashCode();
}
return hashCode;
}
两个key的所有参数都必须相同才算作相等,在hashCode 方法中 频繁的使用了31这个数字,那么为什么要用31而不是用其他的数字呢,想要让这个hashCode不重复,我们应该尽量使用质数,也就是没有约数的数,同时计算机在计算过程中所有的计算都是通过二进制进行计算的, 而31正好是2<<5 -1 (2向左移5位减1),表达式够简单 同时也只占5个字节,内存比较省,
2. 构造完key后去ActiveResources中查找,内存查找的一种,他的具体的思路我们在上面介绍ActiveResources的时候已经说过了,这里就不再说明了,直接看代码
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);///首先去ActiveResources 中查找
if (active != null) {//存在则返回
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
直接调用了loadFromActiveResources 这个方法,
Engine.loadFromActiveResources
@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
这里判断了是否启用了内存缓存,如果没有启用内存缓存,直接返回null,否则 返回 ActiveResources.get()
ActiveResources.get
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
@Nullable
synchronized EngineResource<?> get(Key key) {
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
EngineResource<?> active = activeRef.get();
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}
这里看到 activeEngineResources 是一个map,而他的value 则是一个弱引用对象,说明只要执行了gc,如果只有弱引用引用了这个对象,那么这个对象的内存都会被回收掉,而gc是一个优先级很低的线程,所以不会频繁的执行,我们需要做的就是防止内存抖动引起gc,说白了就是不要频繁的创建和销毁对象,因为创建对象要给这个对象分配一块内存,频繁的创建就会导致内存中的对象数量暴涨,而对象所占据的内存在总的内存中并不是连续的,也就是一共100 单位的内存,你创建了5 个10单位的对象,剩下的50单位的内存并不能分配给一个需要40单位的对象,这样就会导致频繁的执行gc,
这样我们继续来分析上面代码,如果根据这个key就没有配置到 Resource,那么返回null,如果能匹配到但是匹配的对象被回收了,则移除这个对象,
ActiveResources.cleanupActiveReference
@Synthetic
void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
// Fixes a deadlock where we normally acquire the Engine lock and then the ActiveResources lock
// but reverse that order in this one particular test. This is definitely a bit of a hack...
synchronized (listener) {
synchronized (this) {
activeEngineResources.remove(ref.key);
if (!ref.isCacheable || ref.resource == null) {
return;
}
EngineResource<?> newResource =
new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false);
newResource.setResourceListener(ref.key, listener);
listener.onResourceReleased(ref.key, newResource);
}
}
}
在移除的过程中如果原来的资源找不到到了,那么就此结束了,如果还能找到,则重新包装这个Resource为EngineResource,将它交给listener,
在Engine 创建的过程中将自己作为listener 交给了ActiveResources,所以这个回调会回到 Engine中,
Engine.onResourceReleased
@Override
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
如果可以被回收,那么则将它放入到内存缓存中,不行则将资源释放,这个ActiveResources 的流程到这里就整理完了,通过查看源码我们了解了如果资源在ActiveResources中被回收了,但是这个资源的对象还在,那么就会将它放入到cache的内存缓存中,其实还有一个地方也清理了ActiveResources的对象,前面我们说到过很多次RequestManager 的生命周期是和Acitivity 绑定的,那么在RequestManager 的onDestory中是否对ActiveResources做了回收呢,我们过去看一下
RequestManager.onDestory
@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);
}
这里面很多东西都没有介绍过,不要在意并不影响我们猜, 猜测回收的方法肯定是在targetTracker.onDestroy();或者 clear(target); 这两个地方进行的,实际查看后发现targetTracker.onDestroy();里面是清除Tracker中的资源,而clear才是释放
RequestManager.clear
public synchronized void clear(@Nullable final Target<?> target) {
if (target == null) {
return;
}
untrackOrDelegate(target);
}
private void untrackOrDelegate(@NonNull Target<?> target) {
boolean isOwnedByUs = untrack(target);
if (!isOwnedByUs && !glide.removeFromManagers(target) && target.getRequest() != null) {
Request request = target.getRequest();
target.setRequest(null);
request.clear();
}
}
这里是遍历的request ,然后对request进行clear,我们在上一篇中使用的是SingleRequest,去看看他的实现
SingleRequest.clear
@Override
public synchronized void clear() {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
if (status == Status.CLEARED) {
return;
}
cancel();
// Resource must be released before canNotifyStatusChanged is called.
if (resource != null) {
releaseResource(resource);
}
if (canNotifyCleared()) {
target.onLoadCleared(getPlaceholderDrawable());
}
status = Status.CLEARED;
}
如果resource不为null,就会执行 releaseResource(resource);继续看
SingleRequest.releaseResource
private void releaseResource(Resource<?> resource) {
engine.release(resource);
this.resource = null;
}
Engine.release
public void release(Resource<?> resource) {
if (resource instanceof EngineResource) {
((EngineResource<?>) resource).release();
} else {
throw new IllegalArgumentException("Cannot release anything but an EngineResource");
}
}
在这里同样执行了我们在上面看到的 EngineResource的release方法,如果可以缓存,就缓存到内存中,现在再回想一下ActiveResources 和Cache的区别大家可能就能理解了,正常情况下 如果一个Activity 加载了一个图片,那么这个图片在当前Actvity没有关闭的时候就会被放到ActiveResources中,如果Activity 关闭引起RequestManager 响应Activity 的onDestory 的方法,就会将这个Resouse 从ActiveResources中移除,或者在ActiveResources中可以找到Resources,但是这个Resources不能使用了同样会被移除,,如果可以缓存就放入到内存中,否则就会被recycle,释放资源,这里我们就把上面介绍ActiveResources时所提出的疑问解答完毕了,想必大家已经心里有数了
3.如果上面步骤查找失败,则使用MemoryCache 查找,扩大查找范围
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;
}
直接调用的loadFromCache
Engine.loadFromCache
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
和ActivityResouses同样的原理,如果禁用了内存缓存,值机返回null,否则调用getEngineResourceFromCache,得到的结果重新放入到ActivityResouses当中
Engine.getEngineResourceFromCache
private EngineResource<?> getEngineResourceFromCache(Key key) {
Resource<?> cached = cache.remove(key);
final EngineResource<?> result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
// Save an object allocation if we've cached an EngineResource (the typical case).
result = (EngineResource<?>) cached;
} else {
result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
}
return result;
}
这里的cache使用的是LruResourceCache 这个类,具体的实现思路就是LruCache,有兴趣的大家可以百度一下,本来最开始的设想是在写Glide之前就先介绍这个LruCache,但是由于它的功能是由LinkedHashMap 来完成,那就必须要介绍LinkedHashMap ,而LinkedHashMap 与HashMap 又密不可分,其中的Node相互继承,还要先写关于HashMap 的东西,而HashMap 在jdk1.8后,又引入了自平衡红黑树的概念,根本就不是一两篇能写完的,而且我在写的过程中如果遇到原来理解不是很透彻的地方会刨根问底,就导致写一篇文章其实需要的时间往往都需要非常多,差不多5个小时不止吧,这就导致我要是写LruCache就得再从头看一遍数据结构,直接放弃了,
4.如果上面还是没有找到,那么就会查看是否有当前正在进行的同一个任务,
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
这里先这样理解,那就是这个任务正在进行,如果是进行中的任务,我们其实只需要添加回调入口就好了,其他的在上一个请求中已经都做好了,
为什么说我们要将jobs里面获取到的任务理解为正在进行的任务呢,我们先看看第5点再来说
5.开始构建解码器和加载图片
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
engineJob.start(decodeJob);
如果从以上3中方案都没有找到,则会重新创建一个EngineJob,将它放入到jobs中,现在再自己琢磨一下,jobs.get方法是不是获取正在进行的任务,在加载图片之前他事先构造了一个DecodeJob 解码器,那么他是如何创建的,干什么用的,怎么用,由于时间和篇幅的原因只能等待下一篇了
网友评论