当前位置:首页 > SEO优化 > 正文

Android网络优化(Android系统)

    可以说是最常用的图片加载框架。链式调用使用方便,性能可以满足大部分场景的需求。源码和原理也是采访的常客。  

  

    但是源代码里面的内容很多,学习它的源代码也有很多问题,一时半会儿把握不了重点。  

  

    在这篇文章中,我们将介绍和学习的源代码和原则的基础上,我们所做的优化。如果对你有帮助,欢迎夸奖。  

  

    你做了什么,优化?  

  

    要回答这个问题,可以先思考一下。如果要自己实现一个图片加载框架,会想到什么问题?  

  

    1.图像下载是一个耗时的过程,首先需要考虑的是图像缓存的问题  

  

    2.图像加载也是一个消耗内存的操作,其中很多是由图像加载引起的,所以我们也要考虑内存优化问题  

  

    3.图片加载中途,页面关闭,图片加载也要暂停,这就涉及到生命周期管理的问题  

  

    4.还有,图片加载框架支持大图片加载吗?大局怎么了?  

  

    这些是我们向关于,提出的问题,以便我们能够容易地得到本文的主要内容  

  

    1.图片加载全过程介绍  

  

    2.优化的缓存机制是做什么的?  

  

    3.你对优化有什么记忆?  

  

    4.如何管理的生命周期?  

  

    5.如何加载大图?  

  

    下面带问题进入正文吧~  

  

    在我们开始了解优化之前,我们将简要介绍一下图像加载的整个过程,这样我们就可以先有一个大致的概念。  

  

    同时,也便于了解优化在后期发生了哪一步。  

  

    一般来说,图像加载包括封装、解析、下载、解码、转换、缓存和显示等操作,如下图所示:  

  

      

  

    1.包参数:从指定的源到输出的结果,可能有很多过程,所以第一件事就是包参数,会贯穿整个过程;2.解析路径:图片来源多,格式不同,需要规范;3.读缓存:为了减少计算,通常做缓存;对于同样的请求,只需从缓存中取图片()即可;4.查找文件/下载文件:如果是本地文件,可以直接解码;如果是网络图片,需要先下载;5.解码:这一步是整个过程中最复杂的一步,细节很多;6.变换:解码后,可能需要做一些变换处理(圆角,滤镜等)。);7.缓存:得到最终位图后,可以缓存,以便在下一次请求时直接得到结果;8.显示:要显示结果,可能需要做一些动画动画,渐变等)。).  

  

    以上是图像加载的整体流程。这里只简单介绍一下。细节可见一斑:谈谈关于格莱德采访中的那些事  

  

    众所周知,下载图片非常耗费资源,所以图片缓存机制是图片加载框架的重要组成部分。这里有一个表格来说明Glide缓存。  

  

    缓存类型缓存表示活动缓存活动资源的描述。如果当前对应的图片资源是从内存缓存中获取的,则图片将存储在活动资源中。内存缓存LruResourceCache映像最近被解析和加载,然后被放入内存磁盘缓存-资源类型diskrucachewrapper解码的映像被写入磁盘文件磁盘缓存-原始数据磁盘缓存器原始数据在网络请求成功后缓存在磁盘中  

  

    在介绍具体的缓存之前,让我们先来看看加载缓存的执行顺序,大致了解一下  

  

      

  

    缓存有两种,一种是内存缓存,一种是磁盘缓存。  

  

    使用内存缓存的原因是为了防止应用程序将图片反复读入内存,造成内存资源的浪费。  

  

    使用磁盘缓存的原因是为了防止应用程序从网络或其他地方重复下载和读取数据。  

  

    由于这两个缓存的结合正式,它构成了一个优秀的效果缓存  

  

    默认情况下,内存缓存是打开的,或者我们可以通过关闭它来关闭它  

  

    上面,我们可以看到内存缓存实际上分为两部分,缓存和缓存  

  

    它是一个弱引用,用于缓存正在使用的图片,也用于缓存正在使用的图片,可以通过算法保护这些图片不被回收  

  

    内存缓存加载顺序如下:  

  

    1.根据图像地址,宽度和高度,变换、签名等。  

  

    2.第一次加载没有获得活动缓存。  

  

    3.然后加载内存资源缓存,首先清理内存缓存,然后添加活动缓存。  

  

    4.第二个加载活动缓存已经存在。  

  

    5.当前图片引用为0时,清理活动资源,并将其添加到内存资源中。  

  

    6.回到第一步,然后像这样环环相扣。  

  

    总结如下流程图:  

1a7c.jpg" alt="" />

  

    这里没有贴出源码,如果想要看源码的同学可参考:从源码的角度分析 Glide 缓存策略

  

    我们上面总结了内存缓存加载的流程,看到这里我们很容易有个疑问,为什么要设计两种内存缓存?

  

    2.1.1 为什么设计两种内存缓存?

  

    算法的实现,你会发现它其实是用一个来缓存对象的,每次内存超出缓存设定触发操作的时候,其实是对这个进行遍历,然后移除缓存。但是我们都知道是无序的,因此遍历的时候有可能会把正在使用的缓存给误伤了,我还在用着它呢就给移出去了。因此这个弱引用可能是对正在使用中的图片的一种保护,使用的时候先从里面移出去,用完了再把它重新加到缓存里面。

  

    举个例子

  

    

  

    比如我们 内存缓存 设置装 99 张图片,在滑动 的时候,如果刚刚滑动到 100 张,那么就会回收掉我们已经加载出来的第一张,这个时候如果返回滑动到第一张,会重新判断是否有内存缓存,如果没有就会重新开一个 请求,很明显这里如果清理掉了第一张图片并不是我们要的效果。所以在从内存缓存中拿到资源数据的时候就主动添加到活动资源中,并且清理掉内存缓存中的资源。这么做很显然好处是 保护不想被回收掉的图片不被 算法回收掉,充分利用了资源。

  

    2.1.1 小结

  

    本节主要总结了内存缓存加载的流程

  

    1.首先去获取活动缓存,如果加载到则直接返回,没有则进入下一步

  

    2.接着去获取缓存,在获取时会将其从中删除并添加到活动缓存中

  

    3.下次加载就可以直接加载活动缓存了

  

    4.当图片引用为0时,会从活动缓存中清除并添加到缓存中

  

    5.之所以要设计两种内存缓存的原因是为了防止加载中的图片被回收

  

    首先了解一下磁盘缓存策略

  

    : 表示不缓存任何内容。: 在资源解码后将数据写入磁盘缓存,即经过缩放等转换后的图片资源。: 在资源解码前将原始数据写入磁盘缓存。 : 使用和缓存远程数据,仅使用来缓存本地数据。:它会尝试对本地和远程图片使用最佳的策略。当你加载远程数据时, 策略仅会存储未被你的加载过程修改过的原始数据,因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据, 策略则会仅存储变换过的缩略图,因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。默认使用这种缓存策略

  

    在了解磁盘缓存时我们主要需要明确一个概念,是当我们使用 去加载一张图片的时候, 默认并不会将原始图片展示出来,而是会对图片进行压缩和转换,总之就是经过种种一系列操作之后得到的图片,就叫转换过后的图片。

  

    我们既可以缓存变换之前的原始图片,也可以缓存变换后的图片

  

    2.2.1 为什么需要两种磁盘缓存

  

    上文已经说了,缓存的是变换后的资源,缓存的是变换前的资源

  

    举个例子,同一张图片,我们先在的是展示,再在的上展示

  

    如果不缓存变换后的类型相当于每次都要进行一次变换操作,如果不缓存原始数据则每次都要去重新下载数据

  

    如下可以看出,两种缓存的不一样

  

    2.2.2 小结

  

    本节主要介绍了磁盘缓存的几种策略并介绍了为什么需要两种磁盘缓存的原因

  

    这里也没有贴什么源码,如果想要看源码的同学可参考:从源码的角度分析 Glide 缓存策略

  

    的内存优化主要也是对的优化,在回答这个问题前,我们可以想想有哪些常见的优化手段

  

    1.当图片大小与大小不一致时,可以用进行尺寸优化

  

    2.图片所占内存即宽高每像素所占内存大小,不同的模式每个像素所占的内存大小不同,我们可以利用配置

  

    3.所占内存比较大,如果频繁创建回收内存可能造成内存抖动,我们可以利用利用内存

  

    4.内存缓存,上文我们已经介绍了的弱引用缓存与缓存

  

    其实常见的内存优化也就这么几种了,不过我们在工作中比较少直接使用他们。

  

    下面我们就介绍下中具体是怎么使用他们的.

  

    当装载图片的容器例如ImageView只有,而图片的分辨率为,这个时候将图片直接放置在容器上,很容易,同时也是对图片和内存资源的一种浪费。当容器的宽高都很小于图片的宽高,其实就需要对图片进行尺寸上的压缩,将图片的分辨率调整为宽高的大小,一方面不会对图片的质量有影响,同时也可以很大程度上减少内存的占用

  

    我们通常使用对进行尺寸缩放

  

    如果 设置的值大于1,则请求解码器对原始的进行子采样图像,然后返回较小的图片来减少内存的占用,例如 == 4,则采样后的图像宽高为原图像的1/4,而像素值为原图的1/16,也就是说采样后的图像所占内存也为原图所占内存的1/16;当 <=1时,就当作1来处理也就是和原图一样大小。另外最后一句还注明,的值一直为2的幂,如1,2,4,8。任何其他的值也都是四舍五入到最接近2的幂。

  

    如上就是图片进行尺寸缩放相关的代码

  

    1.首先计算出图片与的宽高比

  

    2.根据缩放策略是省内存还是高品质,决定取宽高比的最大值还是最小值

  

    3.当时,一些格式的图片不能缩放

  

    4.的功能是把我们计算的比例四舍五入到最接近2的幂

  

    5.如果缩放策略为省内存,并且我们计算的,将

  

    如上就是图片加载时做尺寸优化的大概逻辑

  

    我们知道,所占内存大小,由决定

  

    上面的尺寸优化决定宽高,图片格式优化决定每像素所占内存

  

    在中,将分为, , , , , 六个等级。

  

    :不存储颜色信息,每个像素占1个字节;:仅存储通道,每个像素占2个字节,对色彩没有高要求,可以使用该模式;:已弃用,用代替;:每个像素占用4个字节,保持高质量的色彩保真度,默认使用该模式;:每个像素占用8个字节,适合宽色域和;:一种特殊的配置,减少了内存占用同时也加快了的绘制。

  

    每个等级每个像素所占用的字节也都不一样,所存储的色彩信息也不同。同一张100像素的图片,就占了400字节,才占200字节,RGB_565在内存上取得了优势,但是的色彩值以及清晰度却不如模式下的

  

    值得注意的是在之前,默认使用格式,比较省内存

  

    但是之后,默认格式已经变成了格式了,这一优势也就不存在了。

  

    这本身也就是质量与内存之间的取舍,如果应用所需图片的质量要求不高,也可以修改默认格式

  

    所占内存比较大,如果我们频繁创建与回收,那么很容易造成内存抖动,所以我们应该尽量复用内存

  

    主要使用了与来实现内存的复用

  

    3.3.1 介绍

  

    在 开始,系统引入了 字段。如果设置了此选项,那么采用 对象的解码方法会在生成目标 时尝试复用 ,这意味着 的内存得到了重复使用,从而提高了性能,同时移除了内存分配和取消分配。不过 的使用方式存在某些限制,在 之前系统仅支持复用大小相同的位图,4.4 之后只要 的大小比目标 大即可

  

    3.3.2 介绍

  

    通过上文我们知道了可以通过复用内存,但是还需要一个地方存储可复用的,这就是

  

    中的 相信大多数开发者都很熟悉,我们一般将之称为“线程池”。池化是一个很常见的概念,其目的都是为了实现对象复用,例如 就实现了线程的复用机制

  

    即实现了的池化

  

    3.3.3 的应用

  

    如上即是设置的代码,向中传入宽高与格式,得到一个可复用的对象,这样就实现了的内存复用

  

    由于篇幅原因,详细的源码这里没有贴出来,想要了解更多的读者可参考:Coil 和 Glide 的 Bitmap 缓存复用机制

  

    当我们在做一个网络请示时,页面退出时应该中止请示,不然容易造成内存泄漏

  

    对于图片加载也是如此,我们在页面退出时应该中止请示,销毁资源。

  

    但是我们使用的时候却不需要在页面退出时做什么操作,说明可以做到在页面关闭时自动释放资源

  

    下面我们一起看下是如何实现的

  

    主要是两步:

  

    1.调用时通过传入,利用构建一个

  

    2.监听生命周期,销毁时释放资源

  

    如上所示:

  

    1.在当前添加一个透明用于管理请示生命周期

  

    2.构建并传入生命周期

  

    逻辑很简单:生命周期变化会回调生命周期,然后在进行相关的资源释放工作

  

    

  

    绑定了的生命周期。在内新建了一个无的,这个持有一个,通过在关键生命周期通知行相关从操作。在生命周期时继续加载,时暂停加载,时停止加载任务和清除操作。

  

    对于图片加载还有种情况,就是单个图片非常巨大,并且还不允许压缩。比如显示:世界地图、清明上河图、微博长图等

  

    首先不压缩,按照原图尺寸加载,那么屏幕肯定是不够大的,并且考虑到内存的情况,不可能一次性整图加载到内存中

  

    所以这种情况的优化思路一般是局部加载,通过来实现

  

    这种情况下通常只负责将图片下载下来,图片的加载由我们自定义的来实现

  

    主要用于显示图片的某一块矩形区域,如果你需要显示某个图片的指定区域,那么这个类非常合适。

  

    对于该类的用法,非常简单,既然是显示图片的某一块区域,那么至少只需要一个方法去设置图片;一个方法传入显示的区域即可

  

    举个例子:

  

    更详细的实现可见:Android 高清加载巨图方案 拒绝压缩图片

  

    不过这种方法虽然也能加载大图,但做的还不够,滑动时内存抖动,卡顿现象比较明显,不能用于线上

  

    

  

    下面介绍一种可以用于线上的大图加载方案

  

    介绍一个开源库:subsampling-scale-image-view

  

    将大图切片,再判断是否可见,如果可见则加入内存中,否则回收,减少了内存占用与抖动 同时根据不同的缩放比例选择合适的采样率,进一步减少内存占用 同时在子线程进行decodeRegion操作,解码成功后回调至主线程,减少UI卡顿.

  

    本文主要以做了哪些优化为切入点,回答了如下几个问题

  

    1.说一下图片加载的总体流程

  

    2.缓存机制做了哪些优化?

  

    3.做了哪些内存优化?

  

    4.如何管理生命周期?

  

    5.怎么做大图加载?

  

    笔者在面试前,从网上收集了一些 Android 开发相关的学习文档、面试题、Android 核心笔记等等文档,进行了复习,在此分享给大家,希望能帮助到大家学习提升,如有需要参考的可以直接去我 CodeChina地址:https://codechina.csdn.net/u012165769/Android-T3 访问查阅。

  

    

  

    

有话要说...