敞开生长之旅!这是我参加「日新方案 12 月更文挑战」的第21天,点击查看活动概况
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
Glide.with(this).load(url).into(imageView)
上面这行代码,是 Glide 最简略的运用方式了,下面咱们来一个个拆解下。
with
with 便是依据传入的 context 来获取图片恳求管理器 RequestManager,用来发动和管理图片恳求。
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
context 能够传入 Application,Activity 和 Fragment,这关系着图片恳求的生命周期。通常运用当前页面的 context,这样当咱们翻开一个页面加载图片,然后退出页面时,图片恳求会跟随页面的毁掉而被撤销,而不是持续加载浪费资源。
当 context 是 Application 时,取得的 RequestManager 是一个大局单例,图片恳求的生命周期会跟随整个 APP 。
假如 with 发生在子线程,不管 context 是谁,都回来运用级别的 RequestManager 单例。
private RequestManager getApplicationManager(@NonNull Context context) {
// Either an application context or we're on a background thread.
if (applicationManager == null) {
synchronized (this) {
if (applicationManager == null) {
// Normally pause/resume is taken care of by the fragment we add to the fragment or
// activity. However, in this case since the manager attached to the application will not
// receive lifecycle events, we must force the manager to start resumed using
// ApplicationLifecycle.
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context.getApplicationContext());
applicationManager =
factory.build(
glide,
new ApplicationLifecycle(),
new EmptyRequestManagerTreeNode(),
context.getApplicationContext());
}
}
}
return applicationManager;
}
当 context 是 Activity 时,会创立一个无界面的 Fragment 添加到 Activity,用于感知 Activity 的生命周期,同时创立 RequestManager 给该 Fragment 持有。
private RequestManager supportFragmentGet(
@NonNull Context context,
@NonNull FragmentManager fm,
@Nullable Fragment parentHint,
boolean isParentVisible) {
SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
// This is a bit of hack, we're going to start the RequestManager, but not the
// corresponding Lifecycle. It's safe to start the RequestManager, but starting the
// Lifecycle might trigger memory leaks. See b/154405040
if (isParentVisible) {
requestManager.onStart();
}
current.setRequestManager(requestManager);
}
return requestManager;
}
load
load 方法会得到一个图片恳求构建器 RequestBuilder,用来创立图片恳求。
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
into
首先是依据 ImageView 的 ScaleType,来装备参数.
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
BaseRequestOptions<?> requestOptions = this;
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
// Clone in this method so that if we use this RequestBuilder to load into a View and then
// into a different target, we don't retain the transformation applied based on the previous
// View's scale type.
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions = requestOptions.clone().optionalCenterCrop();
break;
case CENTER_INSIDE:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
requestOptions = requestOptions.clone().optionalFitCenter();
break;
case FIT_XY:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case CENTER:
case MATRIX:
default:
// Do nothing.
}
}
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
Executors.mainThreadExecutor());
}
持续跟进 into,会创立图片恳求,获取 Target 载体已有的恳求,对比两个恳求,假如等效,发动异步恳求,然后,图片载体绑定图片恳求,也便是 ImageView setTag 为 request 。
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
Request request = buildRequest(target, targetListener, options, callbackExecutor);
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
// If the request is completed, beginning again will ensure the result is re-delivered,
// triggering RequestListeners and Targets. If the request is failed, beginning again will
// restart the request, giving it another chance to complete. If the request is already
// running, we can let it continue running without interruption.
if (!Preconditions.checkNotNull(previous).isRunning()) {
// Use the previous request rather than the new one to allow for optimizations like skipping
// setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
// that are done in the individual Request.
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
持续跟进异步恳求 requestManager.track(target, request)
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
request.begin();//敞开图片恳求
} else {
request.clear();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);//假如是暂停状态,就把恳求存起来。
}
}
到这里就发动了图片恳求了,咱们持续跟进 request.begin()
public void begin() {
synchronized (requestLock) {
//......
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
//假如有尺度,开端加载
onSizeReady(overrideWidth, overrideHeight);
} else {
//假如无尺度就先去获取
target.getSize(this);
}
//......
}
}
然后持续瞧瞧 onSizeReady
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
synchronized (requestLock) {
//......
loadStatus =
engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this,
callbackExecutor);
//......
}
}
跟进 engine.load
public <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);
EngineResource<?> memoryResource;
synchronized (this) {
//从内存加载
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) { //假如内存里没有
return waitForExistingOrStartNewJob(
glideContext,
model,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
options,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache,
cb,
callbackExecutor,
key,
startTime);
}
}
cb.onResourceReady(
memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
return null;
}
private <R> LoadStatus waitForExistingOrStartNewJob(
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,
EngineKey key,
long startTime) {
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);
}
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);
}
DecodeJob 是一个 Runnable,它经过一系列的调用,会来到 HttpUrlFetcher 的 loadData 方法。
public void loadData(
@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
//获取输入流,此处运用的是 HttpURLConnection
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
//回调出去
callback.onDataReady(result);
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to load data for url", e);
}
callback.onLoadFailed(e);
} finally {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
}
}
}
至此,网络恳求完毕,最终把图片设置上去就行了,在 SingleRequest 的 onResourceReady 方法,它会把成果回调给 Target 载体。
target.onResourceReady(result, animation);
持续跟进它,最终会执行 setResource,把图片设置上去。
protected void setResource(@Nullable Drawable resource) {
view.setImageDrawable(resource);
}
总结
with 依据传入的 context 获取图片恳求管理器 RequestManager,当传入的 context 是 Application 时,图片恳求的生命周期会跟随运用,当传入的是 Activity 时,会创立一个无界面的空 Fragment 添加到 Activity,用来感知 Activity 的生命周期。load 会得到了一个图片恳求构建器 RequestBuilder,用来创立图片恳求。into 敞开加载,先会依据 ImageView 的 ScaleType 来装备参数,创立图片恳求,图片载体绑定图片恳求,然后敞开图片恳求,先从内存中加载,假如内存里没有,会创立一个 Runnable,经过一系列的调用,运用 HttpURLConnection 获取网络输入流,把成果回调出去,最终把回调成果设置上去就行了。
缓存
Glide 三级缓存原理:读取一张图片时,次序是: 弱引证缓存,LruCache,磁盘缓存。
用 Glide 加载某张图片时,先去弱引证缓存中寻觅图片,假如有则直接取出来运用,假如没有,则去 LruCache 中寻觅,假如 LruCache 中有,则中取出运用,并将它放入弱引证缓存中,假如没有,则从磁盘缓存或网络中加载图片。
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = loadFromActiveResources(key); //从弱引证获取图片
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}
EngineResource<?> cached = loadFromCache(key); //从 LruCache 获取缓存图片
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}
不过,这会发生一个问题:Glide 加载图片时,URL 不变可是图片变了的这种状况,还是用以前的旧图片。由于 Glide 加载图片会将图片缓存到本地,假如 URL 不变则直接读取缓存不会再从网络上加载。
解决方案:
- 铲除缓存
- 让后台每次都更改图片的姓名
- 图片地址选用 ”url?key=”+随机数这种格式
LruCache
LruCache 便是保护一个缓存目标列表,其中目标列表的排列方式是依照拜访次序实现的,即一向没拜访的目标,将放在队尾,最先被筛选,而最近拜访的目标将放在队头,最终被筛选。其内部保护了一个调集 LinkedHashMap,LinkHashMap 继承 HashMap,在 HashMap 的基础上,新增了双向链表结构,每次拜访数据的时分,会更新被拜访数据的链表指针,该 LinkedHashMap 是以拜访次序排序的,当调用 put 方法时,就会在调集中添加元素,判断缓存是否已满,假如满了就删去队尾元素,即近期最少拜访的元素,当调用 LinkedHashMap 的 get 方法时,就会取得对应的调集元素,同时更新该元素到队头。
Glide 会为每个不同尺度的 Imageview 缓存一张图片,也便是说不管这张图片有没有加载过,只要 Imageview 的尺度不一样,Glide 就会重新加载一次,这时分,它会在加载 Imageview 之前从网络上重新下载,然后再缓存。举个例子,假如一个页面的 Imageview 是 100 * 100,另一个页面的 Imageview 是 800 * 800,它俩展现同一张图片的话,Glide 会下载两次图片,并且缓存两张图片,由于 Glide 缓存 Key 的生成条件之一便是控件的长宽。
由上可知,在图片加载中封闭页面,此页面也不会形成内存泄漏,由于 Glide 在加载资源的时分,假如是在 Activity 或 Fragment 这类有生命周期的组件进步行的话,会创立一个无界面的 Fragment 加入到 FragmentManager 之中,感知生命周期,当 Activity 或 Fragment 进入不行见或毁掉的时分,Glide 会中止加载资源。可是,假如是在非生命周期的组件进步行时,一般会选用 Application 的生命周期贯穿整个运用,此刻只有在运用程序封闭的时分才会中止加载。