Glide分析
一、glide 的流程分析:
Glide整体简化图.png1、第一条主线
加入队列流程:
RequestManager with = Glide.with(this);
RequestBuilder<Drawable> load = with.load(url);
load.into(iv); // 前面的暂时先不看,当调用into方法后,说明加载图片的请求才真正开始
继续调用
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions);
继续跟踪,会发现以下代码
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);//发送请求开始的地方
void track(Target<?> target, Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);//从名字看叫运行请求
}
继续跟踪
通过该方法得知Glide也有两个队列;运行队列和等待队列;
public void runRequest(Request request) {
requests.add(request);//加入运行队列;
if (!isPaused) {
request.begin();//开始执行
} else {
pendingRequests.add(request);//加入等待队列
}
}
2、第二条主线
请求如何运行?
在第一条主线中,request.begin()
方法就是真正开始执行请求的时候;先找到request的实现类:SingleRequest
,找到其begin
方法;
为什么找到的是SingleRequest?
在第一条主线的RequestBuilder.into方法中有一句代码;
Request request = buildRequest(target, targetListener, options);
继续跟踪它
buildRequestRecursive() 找到构建request的方法;
在该方法中,又能跟踪到
Request mainRequest = buildThumbnailRequestRecursive()
继续跟踪
Request fullRequest =
obtainRequest(
target,
targetListener,
requestOptions,
coordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight);
上述代码块最终调用的是SingleRequest.obtain()
方法,从而得到一个SingleRequest对象;所以能得出结论,request.begin()
方法被调用时,即调用了SingleRequest
的begin
方法;继续跟踪begin方法,会发现onSizeReady
方法;
onSizeReady(overrideWidth, overrideHeight);
在begin
方法中跟踪到engine.load
方法,如下(只抽取了部分代码):
// 从活动缓存中获取
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
// 从内存缓存中获取
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
// 硬盘缓存,硬盘缓存也是io操作,所以也使用了线程池;动画线程池
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
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);
engineJob.start(decodeJob); //具体的加载,engineJob为加载管理类,decodeJob则为将返回的图片数据进行编码管理的类;
调用engineJob.start()
方法后,则会执行以下代码:
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
继续跟踪找到DecodeJob
的run
方法;
DecodeJob.run() 继续调用 runWrapped(); 再继续调用getNextGenerator()
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
// 根据主线我们目前都先不去处理跟Cache相关的类,直接进入SourceGenerator;这里使用了设计模式-状态模式;(请自行根据第二节内容进行查询)
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
继续跟踪到SourceGenerator
类中的startNext
方法;
loadData.fetcher.loadData(helper.getPriority(), this);
根据fetcher
找到HttpUrlFetcher
,并找到对应的loadData
方法;最终发现Glide是通过HttpUrlConnection
访问的服务器,并返回最终的stream;
问题来了?我怎么知道是这个类的?为什么不是其他类?在这里代码就看不懂了,怎么办?猜测;
既然应该不是再继续从缓存拿,而应该要去访问网络了;所以找到具体访问网络的;发现找不到,怎么办?
找它的实现类,有一个HttpUrlFetcher,那它在哪里初始化的?
image.png通过Find Usages
找到哪里调用了--->找到了HttpGlideUrlLoader
;
再看这个方法HttpGlideUrlLoader
哪里调用了;
找到了Glide,继续往上寻找,找打了Glide种的build
方法 ,找就能找到Glide.get(context);
方法
3、第三条主线
队列怎么维护的?在MainActivity中我们调用了如下代码:
RequestManager with = Glide.with(this);
继续跟踪到
getRetriever(activity).get(activity)//这里得到了一个RequestManagerRetriever对象,再通过RequestManagerRetriever调用get方法得到RequestManager
继续往下
androidx.fragment.app.FragmentManager fm = activity.getSupportFragmentManager();
return this.supportFragmentGet(activity, fm, (Fragment)null);
通过this.supportFragmentGet
方法(如下代码),最终我们得到SupportRequestManagerFragment
对象;
private RequestManager supportFragmentGet(@NonNull Context context, @NonNull androidx.fragment.app.FragmentManager fm, @Nullable Fragment parentHint) {
SupportRequestManagerFragment current = this.getSupportRequestManagerFragment(fm, parentHint);//这段代码的内部如果能够得到Fragment就得到,得不到就重新new一个,并且这个fragment中没有进行任何的UI处理;
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
Glide glide = Glide.get(context);
requestManager = this.factory.build(glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
得到Fragment
对象后,再将RequestManager
对象赋值进去,如果RequestManager
为空,则帮助创建;
而RequestManager
对象则是生命周期管理的重要一环,因为它实现了LifecycleListener
接口,并且在创建RequestManager
的时候,会将这个接口设置给自己;也就意味着,Glide创建了一个无UI的fragment
,这个fragment又与RequestManager
进行绑定;当用户的activity或者fragment
被调用,系统会自动调用fragment的生命周期方法;而生命周期方法中又会回调LifecycleListener
的方法,进而调用RequestManager
,RequestManager
则也拥有了生命周期;
当RequestManager的onStart方法
被调用后,会通过一系列的调用,将运行中的请求全部放开,进行访问;
当onStop
方法被调用时,则将运行中队列的数据取出来,如果当前请求正在运行则暂停,然后将所有的数据从运行队列中添加到等待队列中去;
当onDestory
方法被调用时,则将运行队列和等待队列中的数据全部清除;再将监听移除;将requestManager
从Glide中的绑定关系解除;
二、常见的面试题:
1、glide四级缓存
缓存.png展示图片步骤:
1、先去活动缓存查找,存在及显示;
2、活动缓存不存在,去内存缓存寻找,存在把内存缓存移动到活动缓存;
3、Activity或者Fragment生命周期结束,把活动缓存数据,移动到内存缓存;
4、活动缓存不存在,内存缓存不存在,去磁盘缓存查找,查找到把数据保存一份到活动缓存;
5、查找不到,去网络请求,http网络请求成功,会保存到本地磁盘,再放一份到活动缓存;
Glide 是一款强大的 Android 图片加载库,它支持四级缓存:活动缓存、内存缓存、磁盘缓存和网络缓存。
- 活动缓存(Activity Cache):活动缓存是 Glide 提供的最小限度的缓存机制。它保存了当前正在运行的 Activity 的图片请求结果,以便在用户进行快速滑动等操作时能够更快地获取图片,提升用户体验。但是,当 Activity 销毁时,活动缓存也会随之清空。
- 内存缓存(Memory Cache):内存缓存是指将图片数据存储在应用程序的内存中,这样可以快速访问图片而不需要再次从磁盘或网络中获取。Glide 内部使用了 LruCache 技术实现内存缓存,即根据最近最少使用原则来淘汰不常用的缓存数据,防止内存泄漏和 OOM 异常。
- 磁盘缓存(Disk Cache):磁盘缓存是指将图片数据存储在设备的本地磁盘上,以便在下次访问相同图片时无需再次下载。Glide 默认使用硬盘缓存技术将图片以文件形式存储在设备的磁盘上。这种方式可以减少重复下载图片的时间和流量消耗,并且可以在无网络情况下正常加载图片。
- 网络缓存(Network Cache):网络缓存是指将图片数据存储在远程服务器上,以便在下次访问相同图片时无需再次请求。Glide 会根据 HTTP 头部信息来判断是否启用网络缓存,如果服务器返回了合适的缓存头信息,那么 Glide 就会自动使用这个数据来展示图片,而不需要再次从网络中下载。
2、Glide 对图片加载做了以下优化:
- 1、图片缩放和裁剪:Glide 可以根据ImageView的尺寸自动对加载的图片进行裁剪和缩放,以适应不同大小的ImageView,避免了浪费内存和网络带宽。
- 2、内存和磁盘缓存机制:Glide 提供了内存缓存和磁盘缓存机制来缓存已经加载的图片,可以避免重复下载相同的图片,节省了网络资源。同时,内存缓存和磁盘缓存也可以加速图片加载过程,提高了用户体验。
- 3、自适应网络传输格式:Glide 会根据网络状态选择最佳的图片压缩格式,如 WebP、PNG、JPEG 等,可以有效降低图片在网络传输中的大小,减少网络流量消耗。
- 4、图片解码优化:在图片解码方面,Glide 使用了 Skia 和 BitmapFactory 两种解码库,并使用了多线程技术,从而快速地将图片解码为 Bitmap,提高了图片展示速度,减少了内存占用。
- 5、图片请求优先级设置:Glide 支持设置图片请求的优先级,以确保在并发加载大量图片时,重要的图片优先加载,提高了用户体验。
综上所述,Glide 对图片的加载、显示、缓存和解码等方面都做了很多优化,使得应用程序对图片的处理更加高效、快速、省流量、低内存占用、用户体验更好。
3、项目中大量的使用Glide,偶尔会出现内存溢出问题,请说说大概是什么原因?
1、尽量在使用with的时候,传入有生命周期的上下文,Application的上下文,尽量不要在子线程加载图片;
2、内存缓存不足:Glide 会将加载的图片缓存到内存中,如果内存缓存空间不足,会导致内存溢出问题。
3、图片太大:如果加载的图片太大,单个图片占用的内存就会很大,导致内存溢出问题。
4、频繁加载图片:如果频繁地加载图片,可能会导致内存中缓存的图片过多,从而导致内存溢出问题。
针对这些问题,您可以尝试以下方法解决:
1、调整内存缓存大小:可以通过修改 Glide 的内存缓存大小来解决内存溢出问题。
2、使用更小的图片:可以通过对图片进行压缩处理,或者使用更小的图片来解决内存溢出问题。
3、避免频繁加载图片:可以通过使用缩略图或者占位符来延迟加载图片,避免频繁加载导致内存溢出问题。
4、Glide为什么要加入网络权限
1、在 Android 平台上,网络通信需要申请网络权限。Glide 是一个强大的图片加载库,需要从网络上下载图片并展示在应用程序中,因此需要使用到网络权限。
2、具体来说,Glide 在加载图片时需要访问网络,从服务器上下载图片并缓存到本地。如果没有网络权限,Glide 无法访问网络,也就无法加载图片。因此,为了让 Glide 能够正常工作,必须在 AndroidManifest.xml 文件中添加网络权限。
3、当然,对于更严格的安全要求,我们应该在添加网络权限的同时,加入网络安全配置,并尽可能通过 HTTPS 加密传输数据。
5、使用Glide时,with函数在子线程调用,会发生什么?
子线程不会去添加生命周期机制,主线程才会添加一个空白的Fragment,监听Fragment生命周期的变化;
特别注意: 不能在子线程显示图片,否则会报:You must call this method on the main thread
6、Glide源码里,为什么会有活动缓存还需要内存缓存?
1、活动缓存和内存缓存都属于 Glide 中的内存缓存策略,它们的作用不同。
在 Glide 中,活动缓存和内存缓存都是用来缓存图片的。
区别是:
- 活动缓存只在当前图片对象被使用时才有效,当图片不再被使用时,它会被释放;
- 内存缓存则始终保留在内存中,即使当前图片对象不再被使用,也可以继续使用内存缓存中的图片对象,以加快图片加载速度。
活动缓存和内存缓存的主要作用如下:
- 活动缓存可以确保当前正在使用的图片对象在内存中保持有效,从而避免频繁的 IO 操作和内存占用。
- 内存缓存可以缓存最近使用过的图片对象,从而加快重复加载同一张图片对象的速度。
通过同时使用活动缓存和内存缓存,Glide 可以兼顾内存使用和性能,提供更优秀的图片加载体验。当一个新图片被加载时,Glide 会首先检查活动缓存是否存在该图片的对象,如果存在则直接使用,否则再检查内存缓存是否存在该图片的对象,如果存在就可以直接使用,否则再从磁盘中加载该图片,加载完成后再放入活动缓存和内存缓存中供以后使用。这样,就可以尽可能地减少加载延迟和内存占用。
7、你能解释一下与其他库相比,使用Glide的主要好处吗?
1、简单易用:Glide 具有简单易用的 API,可以轻松地从内存缓存、本地磁盘和网络中加载图像,甚至可以自动识别和加载视频缩略图。
2、自适应:Glide 可以自动根据设备的分辨率和网络速度来自适应加载图像的质量和大小,从而实现更快的加载速度和更好的图片质量。
3、缓存功能:Glide 具有灵活且高效的缓存功能,可以在内存和磁盘中缓存图像,从而避免频繁的网络请求和减少内存使用,提高用户体验。
4、生命周期处理:Glide 可以自动地处理 Android 应用程序的生命周期,在应用程序退出或 Activity 销毁时停止加载,从而避免在后台消耗资源和导致内存泄漏。
相比于其他图片加载库,使用 Glide 的主要好处包括:
- 更快的加载速度和更好的图片质量。
- 更灵活且高效的缓存功能。
- 更全面的图片加载功能,包括资源、网络、本地和视频缩略图加载。
8、你如何在Android应用程序中优化Glide的性能,尤其是在处理大图像和慢速网络连接时?
要在 Android 应用程序中优化 Glide 的性能,您可以考虑以下几个方面:
1、加载策略:Glide 提供了多种加载策略,例如 centerCrop()、fitCenter() 等,可以根据所需的显示效果来选择加载策略。具体来说,在加载大图像时,使用 centerCrop() 可以将图像居中裁剪,从而减少内存使用和提高性能。而在加载慢速网络连接时,可以使用 placeholder() 方法设置一个占位符,从而让用户在等待图像加载完成之前看到一个默认的图片,减少用户等待时间。
2、内存缓存:Glide 使用内存缓存来提高性能,但如果您加载的图像过大,则可能会导致内存溢出。一种解决方法是使用 diskCacheStrategy(DiskCacheStrategy.RESOURCE) 方法,将缓存策略设置为只使用资源缓存,而不是尝试将完整的图像缓存在内存中。
3、网络连接:在处理慢速网络连接时,可以使用 crossFade() 方法设置一个平滑过渡的动画,从而让用户感受到图像正在加载。您还可以使用 thumbnail() 方法设置预览图像,让用户在等待高分辨率图像加载完成之前看到一个低分辨率的预览图像。
4、图像尺寸:在加载大图像时,可以使用 override() 方法设置加载的图像大小,从而减少内存使用量和提高性能。同样,当您需要在 ImageView 中显示较小的图像时,可以使用 override() 方法将图像大小调整为 ImageView 大小,从而在显示图像时消耗更少的内存。
总的来说,Glide 本身已经具备了很多优化性能的功能,您可以灵活地选择适合您应用程序的加载策略和缓存策略来提高性能。同时,一定要注意内存管理,并确保在处理大图像和慢速网络连接时采取相应的优化措施。
9、在项目中使用Glide时,您是否遇到过缓存或内存管理方面的问题?你是如何解决的?
1、缓存问题:Glide使用磁盘和内存缓存来提高图片加载性能,但是如果设置不当,就可能导致缓存过多。为了避免这种情况,您可以使用diskCacheStrategy()方法来设置缓存策略,例如,设置只缓存原始图片,而不是缓存每一个变换后的图片。
2、内存管理问题:如果您的应用会同时加载多个高分辨率的图片,就需要特别关注内存管理。为了避免OOM(OutOfMemoryError)错误,您可以使用override()方法来设置加载的图片大小,避免加载过大的图片。另外,您也可以使用Glide提供的内存缓存策略,避免重复加载已经存在的图片,从而减少内存占用。
3、长期存储问题:Glide使用磁盘缓存来提供长期存储,以避免重复下载网络图片。但是,如果您没有配置磁盘缓存,或者缓存大小设置不当,就可能导致磁盘空间耗尽。为了避免这种情况,您可以使用diskCacheSize()方法和diskCacheStrategy()方法来控制缓存大小和策略,以适应您的应用程序需求。
10、你是如何在你的一个项目中使用Glide进行转换、调整大小或图像压缩的?
1、转换:Glide提供多个转换选项,例如将图像转为圆形、灰度或高斯模糊。要使用这些选项,您可以在Glide请求中使用transform()方法,传递一个转换对象。
2、调整大小:要调整图像大小,您可以使用override()方法。这个方法允许您手动指定要加载的图像大小,以适应您需要的大小和比例。
3、图像压缩:为了提高应用程序性能和用户体验,您可以压缩图像以减少其文件大小。Glide提供了多个选项来压缩图像,包括allowing hardware bitmap、quality和format等。您可以使用asBitmap()方法将图像加载为位图,并使用compressionQuality()方法设置压缩质量,最后使用diskCacheStrategy()方法缓存压缩后的图像。
以下是一个使用Glide转换、调整大小和压缩图像的示例代码:
Glide.with(this)
.load("your-image-url")
.transform(new CircleCrop()) // 转换为圆形
.override(200, 200) // 调整为200 x 200大小
.asBitmap() // 转换为位图
.dontAnimate()
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.format(DecodeFormat.PREFER_RGB_565)
.encodeFormat(Bitmap.CompressFormat.PNG) // 设置压缩格式为PNG
.compressionQuality(80) // 设置压缩质量为80%
.into(imageView);
11、您对使用Glide加载图像时可能出现的处理错误有多熟悉?你能给我们举个例子说明你是如何处理这种情况的吗?
在使用Glide加载图像时,常见的错误可能包括缩略图加载失败、图片格式无法识别或者图片大小超过限制等。如果出现这类问题,一般可以通过以下方式进行处理:
1、确认图片地址是否正确,确保网络连接正常。
2、检查图片格式是否正确,如PNG、JPEG等。如果格式不正确,需要将图片转换为正确的格式再进行加载。
3、检查图片大小是否超过限制,如超过内存大小或硬件支持的最大分辨率等。
4、如果图片过大,则需要进行压缩或者调整大小等操作。Glide提供了相关的API,可以根据需要进行设置。如果图片本身存在问题,如被损坏或损失部分信息,可能需要使用其他软件或工具进行修复或处理。
12、在需要同时加载多个图像的RecyclerView或ListView中,如何使用Glide实现图像的延迟加载?
在使用Glide加载RecyclerView或ListView中的图像时,为了避免过多的网络请求和内存占用,我们通常会使用Glide的延迟加载功能,也称为Lazy Loading。
延迟加载的主要思路是:
只有当用户滑动到某个特定的位置时,再开始加载对应的图片,而不是一开始就把全部图片都加载出来。
具体实现可以参考以下步骤:
- 在RecyclerView或者ListView的Adapter中,重写getView或onBindViewHolder方法,在其中为每个图片设置一个初始的占位图片(placeholder)。
- 在设置ImageView的图片URL时,使用Glide中的with()方法获取Glide实例,然后调用load()方法传入图片URL加载图片。
- 在load()方法之后可以对图片进行特殊处理,如大小调整、压缩、旋转等,再使用into()方法将处理后的图片显示到ImageView中。
- 最后,使用override()方法指定图片的大小,避免占用过多内存。
为了实现延迟加载,可以在onBindViewHolder方法中根据需要设置图片请求是否立即执行。
例如,当用户在滑动时,可以使用Glide中的resumeRequests()方法开始执行图片请求;当用户滑动停止时,可以使用pauseRequests()方法暂停图片请求。这样就可以有效减少网络请求和内存使用,提高应用的性能和流畅度。同时,Glide还提供了很多其他的优化技巧,如缓存策略的设置、图片质量的优化等,可以根据具体的需求进行选择和应用。
13、您认为在使用Glide时要避免哪些常见错误或陷阱,以及如何确保您的代码随着时间的推移是可维护和可扩展的?
在使用Glide时,有一些常见的错误和陷阱需要避免,而针对代码的可维护性和可扩展性,则需要考虑以下几点建议:
1、避免内存泄漏:在使用Glide时,由于图片文件较大,很容易出现内存泄漏的问题。因此,最好在Activity或Fragment等生命周期结束时,及时清理Glide的缓存和资源,避免造成内存泄漏。
2、建议使用占位符:Glide支持占位符,在加载图片时,可以为ImageView设置一个默认显示的图片。这样,在图片下载时间较长时,用户也能够感受到应用有所反应。
3、避免重复请求:在使用Glide时,很容易发生重复请求的问题,这会导致不必要的网络消耗和卡顿。解决这个问题的方法是,在Adapter重复设置图片URL的时候,判断URL是否已经存在,如果存在就不再发起请求。
4、使用缓存策略:Glide支持多种缓存策略,可以根据图片的类型、大小、清晰度等特点进行选择。选择合适的缓存策略可以减少网络请求次数,提高应用性能。
5、避免过多的内存消耗:当一次需要处理大量图片时,为避免过多的内存消耗,建议使用异步加载框架,并设置适当的缓存大小。
6、让代码具备可维护性和可扩展性:在使用Glide时,建议遵循MVP或MVVM等设计模式,将数据区分model层、视图层和控制层,并尽量避免模块之间的耦合,以确保代码的可维护性和可扩展性。
总的来说,为了确保在使用Glide时避免常见错误或陷阱,并让代码具备可维护性和可扩展性,需要我们在编写代码时注重细节,紧抓重点,优化算法,并在实践中不断寻求实践经验的积累和总结,不断完善和发展代码。
14、Glide中什么是活动缓存?
- 在Glide中,活动缓存指的是能够立即被重用的位图和它们的相关资源(如上下文和配置)的内存缓存。活动缓存中存储了一些已经被解码且尺寸合适的位图,这些位图可以被直接用于显示UI界面上的图片,从而避免频繁的IO操作和网络请求。
- Glide的活动缓存分为两种,一种是内存缓存,另外一种是共享的内存缓存。其中,内存缓存是直接存储在应用程序内存中的,共享的内存缓存则存储在应用进程之外的共享内存中,对不同进程的应用都可见。
- 在Glide中,默认的活动缓存大小是应用程序内存的1/7,假设你的应用程序的内存分配是128MB,那么默认的活动缓存大小就会是128/7=18MB左右。当然,开发者可以根据自己的需要,自定义某个特定活动缓存以及所有活动缓存的大小,以适应不同的场景需求。需要注意的是,在使用Glide时,避免让活动缓存过大,否则可能导致内存占用过高,影响系统的稳定性和性能。同时,我们也可以通过清除活动缓存、调整缓存大小、限制图片尺寸等方式,从根本上避免此类问题的发生。