🌺周鑫
一、Glide总结
Glide加载流程可以分三步
步骤一、Glide.with
1、调用Glide.with方法获得RequestManager实例。这一步主要实例化glide,并对glide一些参数进行初始化和解析我们自定义GlideModule。其次就是管理glide请求。
With有多个重载方法,可以使用context、Activity、Fragment。如果是ApplicationContext,那么会返回单例RequestManager。如果是Activity或Fragment,会创建无ui的RequestManagerFragment。当Activity生命周期改变的时候,基于观察者模式,通过LifeCycle通知观察者LifeCycleListener,RequestManager实现了LifecycleListener接口,从而使RequestManager实现管理glide请求的目的。
步骤二、RequestManager的load
2、通过RequestManager调用load方法创建RequestBuilder。作用是:将资源数据url解析赋值给RequestBuilder的model变量,并对SingleRequest初始化赋值。SingleRequest负责之后的加载请求。我们可以通过设置RequestOptions来为RequestBuilder配置当前图片的加载参数。
步骤三、RequestBuilder的into
3、调用RequestBuilder的into方法将图片显示在ImageView上。
(1)将 ImageView 封装成 ImageViewTarget,target继承LifecycleListener接口,重写onStart、onStop、onDestroy方法,监听Activity的生命周期,来灵活的控制图片加载的取消等操作。
(2)将ImageView的ScaleType和请求参数进行解析,赋值给RequestOptions。
(3)执行RequestBuilder的into方法,在这里面创建SingleRequset实例request,通过RequestManager跟踪request,并执行request的begin方法。
(4)在SingleRequset的begin方法中,判断是否设置了图片大小,如果没有设置,则通过我们创建target调用getSize方法,获得ImageView的大小。来调用onSizeReady方法。接下来会执行Engine的load方法。
(5)在Engine的load,会先从三级缓存中查找数据。
1)先从ActiveResources(活动资源)中查找,ActiveResources是HashMap,value弱引用资源。当ActiveResources释放资源onResourceReleased的时候,会把source存储到MemoryCache中。
2)其次从MemoryCache内存缓存查找,MemoryCache使用LruCache算法。如果有会存放到ActiveResources中。
3)如果没有,会在线程池内执行DecodeJob的run方法。DecodeJob继承了Runnable。最终会会执行HttpUrlFetcher的loadData方法,从网络获取InputStream输入流。之后将数据存储到DiskCache。
4)调用BitmapFactory.decodeStream将输入流转换为bitmap。
5)通过handler发送MSG_COMPLETE,切换到主线程。将数据缓存到activeResources活动资源中。调用BitmapImageViewTarget.setResource方法回调ImageView的setImageBitmap()方法将数据展示在ImageView上。
二、Glide加载流程分析
Glide.with(this) .load(url).into(img_1)
一、Glide.with(context)
解析:在SupportRequestManagerFragment我们创建了lifecycle,lifecycle这个对象里维护了lifecycleListeners集合,当fragment调用生命周期方法时,会调用lifecycle方法,遍历集合里的listener,调用listener对应的生命周期方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-45fKaQTD-1611411466281)(https://uploader.shimo.im/f/KpOtmGFYJods7eVK.png!thumbnail?fileGuid=JKhRVxytwJDgGVrD)]
二、RequestManager的load方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zcvRvbJI-1611411466283)(https://uploader.shimo.im/f/SbVOqKIvOcf5d4EM.png!thumbnail?fileGuid=JKhRVxytwJDgGVrD)]
三、RequestBuilder的into方法
解析:调用RequestBuilder的into方法将图片显示在ImageView上。
(1)将 ImageView 封装成 ImageViewTarget,target继承LifecycleListener接口,重写onStart、onStop、onDestroy方法,监听Activity的生命周期,来灵活的控制图片加载的取消等操作。
(2)将ImageView的ScaleType和请求参数进行解析,赋值给RequestOptions。
(3)执行RequestBuilder的into方法,在这里面创建SingleRequset实例request,通过RequestManager跟踪request,并执行request的begin方法。
(4)在SingleRequset的begin方法中,判断是否设置了图片大小,如果没有设置,则通过我们创建target调用getSize方法,获得ImageView的大小。来调用onSizeReady方法。接下来会执行Engine的load方法。
(5)在Engine的load,会先从三级缓存中查找数据。
1)通过EngineKeyFactory根据图片资源、宽高、options等相关参数创建Key。
2)先从ActiveResources(活动资源)中查找,ActiveResources是HashMap,value弱引用资源。当ActiveResources释放资源onResourceReleased的时候,会把source存储到MemoryCache中。
3)其次从MemoryCache内存缓存查找,MemoryCache使用LruCache算法。如果有会存放到ActiveResources中。
4)如果没有,会调用engineJob.start(decodeJob);EngineJob 内部维护了线程池,用来管理资源加载。会在线程池内执行DecodeJob的run方法。DecodeJob继承了Runnable。接下来执行DecodeJob的run方法->runGenerators->SourceGenerator 的 startNext() 。
我们加载的网络图片url,所以会执行HttpUrlFetcher的loadData方法, 调用loadDataWithRedirects() 方法使用 HttpURLConnection 打开网络流的过程从网络获取InputStream输入流。之后回调SourceGenerator的onDataReady(),然后执行 cb.reschedule(),最后又会执行到 DecodeJob的 run方法,最后执行到SourceGenerator 的 startNext() 方法的。因为现在有缓存了,所以走SourceGenerator的cacheData方法对数据进行缓存并存储数据到DiskCache。
5)现在调用loadData方法。因为有缓存了,所以执行ByteBufferFileLoader的loadData方法
6)在ByteBufferFileLoader的loadData方法中,从DiskCache中通过originalKey找到file,然后读取file中的result数据。回调到DecodeJob的decodeFromRetrievedData方法中。
7)先将数据转换为InputStream,然后调用BitmapFactory.decodeStream将输入流转换为bitmap。
(6)通过handler发送MSG_COMPLETE,调用EngineJob的handleResultOnMainThread,通知主线程数据准备好了。
在这方法中,主要做两件事。
一:调用Engine的onEngineJobComplete方法,将数据缓存到activeResources活动资源中。
二:调用ImageViewTarget的onResourceReady方法,通知
Target数据准备好了,然后调用BitmapImageViewTarget.setResource方法,调用ImageView的setImageBitmap(resource)将数据展示在ImageView上。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BR9MCqix-1611411466284)(https://uploader.shimo.im/f/A7ncGNwTh4zaTwu2.png!thumbnail?fileGuid=JKhRVxytwJDgGVrD)]
三、问题
问题1:Glide优点
1、和context的生命周期进行绑定。Glide调用with(Context)的时候,如果是activity或fragment,会创建RequestManagerFragment来监听activity的生命周期。调用lifecycle,使用观察者模式,通知观察者感知activity的生命周期。
2、使用缓存。
问题2:Glide缓存
内存缓存的主要作用是防止应用重复将图片数据读取到内存当中,而硬盘缓存的主要作用是防止应用重复从网络或其他地方重复下载和读取数据。
Glide主要有内存缓存和硬盘缓存。Glide会根据图片数据源、设置的宽高、options等生成key。
1、ActiveResources活动资源,ActiveResources维护了HashMap,HashMap 中保存的值是弱引用的。里面维护了acquired变量,当通过key获取数据的时候acquired++。当
release释放数据的时候acquired–。当 acquired 减到 0 的时候,又回调了 Engine#onResourceReleased()。在 onResourceReleased() 方法中将缓存资源从ActiveResources移除,然后将它缓存到内存缓存中MemoryCache(存储内存缓存)。
2、MemoryCache内存缓存,取出数据之后,会把数据存到ActiveResources中。
3、DiskCache,当网络请求获取到资源之后,存储到DiskCache中。
DiskLruCache、LruCache:底层是LinkedHashMap,使用了最近最少使用算法。当缓存满的时候,删除表头元素。当使用数据的时候,将数据从LinkedHashMap移除,然后添加到表尾。
4、HttpUrlFetcher的loadData方法,从网络获取InputStream。获取到数据之后,会通过Handler发送消息,通知主线程数据准备好了。接下来会做两件事,把数据存储到ActiveResources中,对数据转换为bitmap然后加载到ImageView上。
弱引用的缓存会在内存不够的时候被清理掉,而基于 LruCache 的内存缓存是强引用的,因此不会因为内存的原因被清理掉。LruCache 只有当缓存的数据达到了缓存空间的上限的时候才会将最近最少使用的缓存数据清理出去。
问题3:LruCache
https://shimo.im/docs/tQcvGrdcqpxPgWjC/read
问题4:AppGlideModule有什么作用
Answer:GlideModule是对glide全局配置相关的类。在Glide创建的时候,会获取AppGlideModule配置的参数和方法。会重写applyOptions和isManifestParsingEnabled方法。
isManifestParsingEnabled:会在glide创建的时候判断是否使用清单文件配置的GlideModule。
applyOptions:配置加载的options。
registerComponents:使用 OkHttp替换 HttpURLConnection 实现网络当中的图片的加载。
问题5:bitmap为什么会导致oom?
1、在recyclerview中,加载大量图片
2、bitmap所占用的内存:图片长宽一个像素点所占据的字节数
3、如果超过应用的最大内存,就会oom。
问题6:Bitmap的优化
1、对图片进行压缩
2、使用缓存
3、bitmap的recycle,记得设置bitmap为null
4、bitmap造成oom,如何捕获异常。生成bitmap的时候,要try catch bitmap的OutOfMemoryErro异常。
1、采样率压缩
解码图片时,设置BitmapFactory.Options类的inJustDecodeBounds属性为true,可以在Bitmap不被加载到内存的前提下,获取Bitmap的原始宽高。通过计算原始图片宽高和我们需要设置的宽高,来计算inSampleSize。通过设置inSampleSize,Bitmap的宽、高都会缩小inSampleSize倍。例如:一张宽高为2048x1536的图片,设置inSampleSize为4之后,实际加载到内存中的图片宽高是512x384。占有的内存就是0.75M而不是12M,足足节省了15倍。
2、补充
1、字节数
字节数:1KB=1024Byte(Byte表示字节意思),1Byte=8bit(bit表示二进制的位数)。
2、查看每个应用程序最高可用内存
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
Log.d(“TAG”, "Max memory is " + maxMemory + “KB”);
3、如何计算bitmap占用的内存大小
bitmap内存大小 = 图片长度 x 图片宽度 x 单位像素占用的字节数。默认使用ARGB_8888,每个像素4字节。那么常见的1080*1920的图片内存占用就是:1920 x 1080 x 4 = 7.9M。
问题7:如何实现加载圆形图片、圆角图片和ScaleType
在TransformationUtils中,计算宽高然后对canvas进行缩放、画圆或画圆角等,然后将bitmap画在canvas上。
自定义实现BitmapTransformation,可以自己设置加载图片的类型和圆角等。
https://blog.csdn.net/guolin_blog/article/details/71524668
问题8:磁盘缓存的策略
DiskCacheStrategy 封装的是磁盘缓存策略,一共有如下几种策略:
- ALL:既缓存原始图片,也缓存转换过后的图片。
- NONE:不缓存任何内容。
- DATA:只缓存原始图片。
- RESOURCE:只缓存转换过后的图片。
- AUTOMATIC:默认策略,它会尝试对本地和远程图片使用最佳的策略。如果是远程图片,则只缓存原始图片;如果是本地图片,那么只缓存转换过后的图片。
问题9:RecyclerView的优化
RecyclerView 滑动时不让 Glide 加载图片
//RecyclerView.SCROLL_STATE_IDLE //空闲状态
//RecyclerView.SCROLL_STATE_FLING //滚动状态
//RecyclerView.SCROLL_STATE_TOUCH_SCROLL //触摸后状态
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
LoggerUtils.e("initRecyclerView"+ "恢复Glide加载图片");
Glide.with(ImageBrowseActivity.this).resumeRequests();
}else {
LoggerUtils.e("initRecyclerView"+"禁止Glide加载图片");
Glide.with(ImageBrowseActivity.this).pauseRequests();
}
}
});
问题10:如何设计一个大图加载框架
就是把Glide加载图片的流程大体说一下。
- 封装参数:从指定来源,到输出结果,中间可能经历很多流程,所以第一件事就是封装参数,这些参数会贯穿整个过程;
- 解析路径:图片的来源有多种,格式也不尽相同,需要规范化;
- 读取缓存:为了减少计算,通常都会做缓存;同样的请求,从缓存中取图片(Bitmap)即可;
- 查找文件/下载文件:如果是本地的文件,直接解码即可;如果是网络图片,需要先下载;
- 解码:这一步是整个过程中最复杂的步骤之一,有不少细节,下个博客会说;
- 变换:解码出Bitmap之后,可能还需要做一些变换处理(圆角,滤镜等);
- 缓存:得到最终bitmap之后,可以缓存起来,以便下次请求时直接取结果;
- 显示:显示结果,可能需要做些动画(淡入动画,crossFade等)。
问题11:onTrimMemory和onLowMemory
对于一个 App 而言,在系统内存环境不足的情况下,会回调一些onTrimMemory()或者onLowMemory()等方法,这些都是在提醒开发者,当前设备的内存环境已经发生了变化,你最好调整你的内存使用策略,避免被系统清理掉或者出现 OOM 。