共计 21123 个字符,预计需要花费 53 分钟才能阅读完成。
目录介绍
-
01. 图片根底概念介绍
- 1.1 图片占用内存介绍
- 1.2 加载网络图片流程
- 1.3 三方库加载图片逻辑
- 1.4 从网络间接拉取图片
- 1.5 加载图片的流程
- 1.6 Bitmap 能间接存储吗
- 1.7 Bitmap 创立流程
- 1.8 图片框架如何设计
-
02. 图片内存计算形式
- 2.1 如何计算占用内存
- 2.2 下面计算内存对吗
- 2.3 一个像素占用内存
- 2.4 应用 API 获取内存
- 2.5 影响 Bitmap 内存因素
- 2.6 加载 xhdpi 和 xxhdpi 图片
- 2.7 图片一些注意事项
-
03. 大图的内存优化
- 3.1 常见图片压缩
- 3.2 图片尺寸压缩
- 3.3 图片品质压缩
- 3.4 双线性采样压缩
- 3.5 高清图分片加载
- 3.6 图片综合压缩
-
04. 色调格局及内存优化
- 4.1 RGB 色彩品种
- 4.2 ARGB 色调模式
- 4.3 扭转色调格局优化
-
05. 缓存的应用实际优化
- 5.1 Lru 内存缓存
- 5.2 Lru 内存注意事项
- 5.3 应用 Lru 磁盘缓存
-
06. 不同版本对 Bitmap 治理
- 6.1 演变过程
- 6.2 治理 Bitmap 内存
- 6.3 进步 Bitmap 复用
-
07. 图片其余方面优化
- 7.1 缩小 PNG 图片的应用
- 7.2 控件切割圆角优化
- 7.3 如何给图片置灰色
- 7.4 如何解决图片旋转呢
- 7.5 保留图片且刷相册
- 7.6 对立图片域名优化
- 7.7 优化 H5 图片加载
- 7.8 优化图片暗影成果
- 7.9 图片资源的压缩
01. 图片根底概念介绍
1.1 图片占用内存介绍
-
挪动设施的系统资源无限,所以利用应该尽可能的升高内存的应用。
- 在利用运行过程中,Bitmap(图片)往往是内存占用最大的一个局部,Bitmap 图片的加载和解决,通常会占用大量的内存空间,所以在操作 Bitmap 时,应该尽可能的小心。
-
Bitmap 会耗费很多的内存,特地是诸如照片等内容丰盛的大图。
- 例如,一个手机拍摄的 2700 1900 像素的照片,须要 5.1M 的存储空间,然而在图像解码配置 ARGB_8888 时,它加载到内存须要 19.6M 内存空间(2592 1936 * 4 bytes),从而迅速消耗掉该利用的残余内存空间。
- OOM 的问题也是咱们常见的重大问题,OOM 的产生的一个次要场景就是在大图片分配内存的时候产生的,如果 APP 可用内存缓和,这时加载了一张大图,内存空间不足以调配该图片所须要的内存,就会产生 OOM,所以管制图片的高效应用是必备技能。
1.2 加载网络图片流程
- 这一部分压缩和缓存图片,在 glide 源码剖析的文章里曾经做出了比拟具体的阐明。在这里简略说一下图片申请加载过程……
-
在应用 App 的时候,会常常须要加载一些网络图片,个别的操作步骤大略是这样的:
- 第一步从网络加载图片:个别都是通过网络拉取的形式去服务器端获取到图片的文件流后,再通过 BitmapFactory.decodeStream(InputStream)来加载图片 Bitmap。
- 第二步这种压缩图片:网络加载图片形式加载一两张图片倒不会呈现问题,然而如果短时间内加载十几张或者几十张图片的时候,就很有可能会造成 OOM(内存溢出),因为当初的图片资源大小都是十分大的,所以咱们在加载图片之前还须要进行相应的图片压缩解决。
- 第三步变换图片:比方须要裁剪,切割圆角,旋转,增加高斯含糊等属性。
- 第四步缓存图片:但又有个问题来了,在应用挪动数据的状况下,如果用户每次进入 App 的时候都会去进行网络拉取图片,这样就会十分的节约数据流量,这时又须要对图片资源进行一些相应的内存缓存以及磁盘缓存解决,这样不仅节俭用户的数据流量,还能放慢图片的加载速度。
- 第五步异步加载:尽管利用缓存的形式能够放慢图片的加载速度,但当须要加载很多张图片的时候(例如图片墙瀑布流成果),就还需用到多线程来加载图片,应用多线程就会波及到线程同步加载与异步加载问题。
1.3 三方库加载图片逻辑
-
先说出论断,目前市面较为罕用的大略是 Glide,Picasso,Fresco 等。大略的解决图片波及次要逻辑有:
- 从网络或者本地等门路拉取图片;而后解码图片;而后进行压缩;接着会有图片罕用圆角,含糊或者裁剪等解决;而后三级缓存加载的图片;当然加载图片过程波及同步加载和异步加载;最初设置到具体 view 控件上。
1.4 从网络间接拉取图片
-
间接通过网络申请将网络图片转化成 bitmap
- 在这将采纳最原生的网络申请形式 HttpURLConnection 形式进行图片获取。
- 通过测试,申请 8 张图片,耗时毫秒值 174。个别是通过 get 申请拉取图片的。这种办法应该是最根底的网络申请,大家也能够回顾一下,个别开发中很少用这种形式加载图片。具体能够看:ImageToolLib
-
如何加载一个图片呢?
- 能够看看 BitmapFactory 类为咱们提供了四类办法来加载 Bitmap:decodeFile、decodeResource、decodeStream、decodeByteArray;也就是说 Bitmap,Drawable,InputStream,Byte[] 之间是能够进行转换。
1.5 加载图片的流程
-
搞清楚一个图片概念
- 在电脑上看到的 png 格局或者 jpg 格局的图片,png(jpg) 只是这张图片的容器。是通过绝对应的压缩算法将原图每个像素点信息转换用另一种数据格式示意。
-
加载图片显示到手机
- 通过代码,将这张图片加载进内存时,会先解析 (也就是解码操作) 图片文件自身的数据格式,而后还原为位图,也就是 Bitmap 对象。
-
图片大小 vs 图片内存大小
- 一张 png 或者 jpg 格局的图片大小,跟这张图片加载进内存所占用的大小齐全是两回事。
1.6 Bitmap 能间接存储吗
-
Bitmap 根底概念
- Bitmap 对象实质是一张图片的内容在手机内存中的表达形式。它将图片的内容看做是由存储数据的无限个像素点组成;每个像素点存储该像素点地位的 ARGB 值。每个像素点的 ARGB 值确定下来,这张图片的内容就相应地确定下来了。
-
Bitmap 实质上不能间接存储
- 为什么?bitmap 是一个对象,如果要存储老本地能够查看的图片文件,则必须对 bitmap 进行编码,而后通过 io 流写到本地 file 文件上。
1.7 Bitmap 创立流程
-
对于图片 OOM,能够发现一个景象。
- heapsize(虚拟机的内存配置)越大越不容易 OOM,Android8.0 及之后的版本更不容易 OOM,这个该如何了解呢?
-
Bitmap 对象内存的变动:
- 在 Android 8.0 之前,Bitmap 像素占用的内存是在 Java heap 中调配的;8.0 及之后,Bitmap 像素占用的内存调配到了 Native Heap。
- 因为 Native heap 的内存调配下限很大,32 位利用的可用内存在 3~4G,64 位上更大,虚拟内存简直很难耗尽,所以揣测 OOM 时 Java heap 中占用内存较多的对象是 Bitmap”成立的状况下,利用更不容易 OOM。
-
搞清楚 Bitmap 对象内存调配
- Bitmap 的构造方法是不公开的,在应用 Bitmap 的时候,个别都是通过 Bitmap、BitmapFactory 提供的静态方法来创立 Bitmap 实例。
- 以 Bitmap.createBitmap 阐明了 Bitmap 对象的次要创立过程剖析,能够看到 Java Bitmap 对象是在 Native 层通过 NewObject 创立的。
- allocateJavaPixelRef,是 8.0 之前版本为 Bitmap 像素从 Java heap 申请内存。其外围原理是 Bitmap 的像素是保留在 Java 堆上。
- allocateHeapBitmap,是 8.0 版本为 Bitmap 像素从 Native heap 申请内存。其外围原理次要是通过 calloc 为 Bitmap 的像素分配内存,这个调配就在 Native 堆上。
- 更多具体内容能够看:Bitmap 对象内存调配
1.8 图片框架如何设计
-
大多数图片框架加载流程
- 概括来说,图片加载蕴含封装,解析,下载,解码,变换,缓存,显示等操作。
-
图片框架是如何设计的
- 封装参数:从指定起源,到输入后果,两头可能经验很多流程,所以第一件事就是封装参数,这些参数会贯通整个过程;
- 解析门路:图片的起源有多种,格局也不尽相同,须要规范化;比方 glide 能够加载 file,io,id,网络等各种图片资源
- 读取缓存:为了缩小计算,通常都会做缓存;同样的申请,从缓存中取图片(Bitmap)即可;
- 查找文件 / 下载文件:如果是本地的文件,间接解码即可;如果是网络图片,须要先下载;比方 glide 这块是发动一个申请
- 解码:这一步是整个过程中最简单的步骤之一,有不少细节;比方 glide 中解析图片数据源,旋转方向,图片头等信息
- 变换和压缩:解码出 Bitmap 之后,可能还须要做一些变换处理(圆角,滤镜等),还要做图片压缩;
- 缓存:失去最终 bitmap 之后,能够缓存起来,以便下次申请时间接取后果;比方 glide 用到三级缓存
- 显示:显示后果,可能须要做些动画(淡入动画,crossFade 等);比方 glide 设置显示的时候能够增加动画成果
02. 图片内存计算形式
2.1 如何计算占用内存
- 如果图片要显示下 Android 设施上,ImageView 最终是要加载 Bitmap 对象的,就要思考单个 Bitmap 对象的内存占用了,如何计算一张图片的加载到内存的占用呢?其实就是所有像素的内存占用总和:
- bitmap 内存大小 = 图片长度 x 图片宽度 x 单位像素占用的字节数
- 起决定因素就是最初那个参数了,Bitmap 常见有 2 种编码方式:ARGB_8888 和 RGB_565,ARGB_8888 每个像素点 4 个 byte,RGB_565 是 2 个 byte,个别都采纳 ARGB_8888 这种。那么常见的 1080*1920 的图片内存占用就是:1920 x 1080 x 4 = 7.9M
2.2 下面计算内存对吗
-
我看到好多博客都是这样计算的,然而这样算对吗?有没有哥们试验过这种办法正确性?我感觉看博客要对博主示意狐疑,论证他人写的是否正确。
- 说出我的论断:下面 2.1 这种说法也对,然而不全对,没有阐明场景,同时也疏忽了一个影响项:Density。接下来看看源代码。
- inDensity 默认为图片所在文件夹对应的密度;inTargetDensity 为以后零碎密度。
-
加载一张本地资源图片,那么它占用的内存 = width height nTargetDensity/inDensity 一个像素所占的内存。
@Nullable public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value, @Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {validate(opts); if (opts == null) {opts = new Options(); } if (opts.inDensity == 0 && value != null) { final int density = value.density; if (density == TypedValue.DENSITY_DEFAULT) {opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;} else if (density != TypedValue.DENSITY_NONE) {opts.inDensity = density;} } if (opts.inTargetDensity == 0 && res != null) {opts.inTargetDensity = res.getDisplayMetrics().densityDpi; } return decodeStream(is, pad, opts); }
-
正确说法,这个留神呢?计算公式如下所示
- 对资源文件:width height nTargetDensity/inDensity nTargetDensity/inDensity 一个像素所占的内存;
- 别的:width height 一个像素所占的内存;
2.3 一个像素占用内存
-
一个像素占用多大内存?Bitmap.Config 用来形容图片的像素是怎么被存储的?
- ARGB_8888: 每个像素 4 字节. 共 32 位,默认设置。
- Alpha_8: 只保留透明度,共 8 位,1 字节。
- ARGB_4444: 共 16 位,2 字节。
- RGB_565: 共 16 位,2 字节,只存储 RGB 值。
2.4 应用 API 获取内存
-
Bitmap 应用 API 获取内存
-
getByteCount()
- getByteCount()办法是在 API12 退出的,代表存储 Bitmap 的色素须要的起码内存。API19 开始 getAllocationByteCount()办法代替了 getByteCount()。
-
getAllocationByteCount()
- API19 之后,Bitmap 加了一个 Api:getAllocationByteCount();代表在内存中为 Bitmap 调配的内存大小。
-
-
思考:getByteCount()与 getAllocationByteCount()的区别?
- 个别状况下两者是相等的;通过复用 Bitmap 来解码图片,如果被复用的 Bitmap 的内存比待分配内存的 Bitmap 大, 那么 getByteCount()示意新解码图片占用内存的大小(并非理论内存大小, 理论大小是复用的那个 Bitmap 的大小),getAllocationByteCount()示意被复用 Bitmap 实在占用的内存大小(即 mBuffer 的长度)。
- 在复用 Bitmap 的状况下,getAllocationByteCount()可能会比 getByteCount()大。
2.5 影响 Bitmap 内存因素
-
影响 Bitmap 占用内存的因素:
- 图片最终加载的分辨率;
- 图片的格局(PNG/JPEG/BMP/WebP);
- 图片所寄存的 drawable 目录;
- 图片属性设置的色调模式;
- 设施的屏幕密度;
2.6 加载 xhdpi 和 xxhdpi 图片
-
提个问题,加载 xhdpi 和 xxhdpi 中雷同的图片,显示在控件上会一样吗?内存大小一样吗?为什么?
- 必定是不一样的。xhdpi:240dpi–320dpi,xxhdpi:320dpi–480dpi,
-
app 中设置的图片是如何从 hdpi 中查找的?
- 首先计算 dpi,比方手机分辨率是 1920×1080,5.1 寸的手机。那么失去的 dpi 公式是(√~1920² + 1080²)/5.1 =2202/5.1= 431dpi。这样优先查找 xxhdpi
- 如果 xxhdpi 里没有查找图片,如果没有会往上找,遵循“先高再低”准则。如果 xhdpi 里有这个图片会应用 xhdpi 里的图片,这时候发现会比在 xhdpi 里的图片放大了。
-
为何要引入不同 hdpi 的文件治理?
- 比方:xxhdpi 放 94×94,xhdpi 放 74×74,hdpi 放 45×45,这样不论是什么样的手机图片都能在指定的比例显示。引入多种 hdpi 是为了让这个图片在任何手机上都是手机的这个比例。
2.7 图片一些注意事项
-
同样图片显示在大小不雷同的 ImageView 上,内存是一样吗?
- 图片占据内存空间大小与图片在界面上显示的大小没有关系。
-
图片放在 res 不同目录,加载的内存是一样的吗?
- 最终图片加载进内存所占据的大小会不一样,因为零碎在加载 res 目录下的资源图片时,会依据图片寄存的不同目录做一次分辨率的转换,而转换的规定是:新图的高度 = 原图高度 * (设施的 dpi / 目录对应的 dpi)
03. 大图的内存优化
3.1 常见图片压缩
-
常见压缩办法 Api
- Bitmap.compress(),品质压缩,不会对内存产生影响;
- BitmapFactory.Options.inSampleSize,内存压缩;
-
Bitmap.compress()品质压缩
- 品质压缩,不会对内存产生影响。它是在放弃像素的前提下扭转图片的位深及透明度等,来达到压缩图片的目标,不会缩小图片的像素。进过它压缩的图片文件大小会变小,然而解码成 bitmap 后占得内存是不变的。
-
BitmapFactory.Options.inSampleSize 内存压缩
- 解码图片时,设置 BitmapFactory.Options 类的 inJustDecodeBounds 属性为 true,能够在 Bitmap 不被加载到内存的前提下,获取 Bitmap 的原始宽高。而设置 BitmapFactory.Options 的 inSampleSize 属性能够实在的压缩 Bitmap 占用的内存,加载更小内存的 Bitmap。
- 设置 inSampleSize 之后,Bitmap 的宽、高都会放大 inSampleSize 倍。例如:一张宽高为 2048×1536 的图片,设置 inSampleSize 为 4 之后,理论加载到内存中的图片宽高是 512×384。占有的内存就是 0.75M 而不是 12M,足足节俭了 15 倍。
- 备注:inSampleSize 值的大小不是轻易设、或者越大越好,须要依据理论状况来设置。inSampleSize 比 1 小的话会被当做 1,任何 inSampleSize 的值会被取靠近 2 的幂值。
3.2 图片尺寸压缩
3.2.1 如何了解尺寸压缩
-
通常在大多数状况下,图片的理论大小都比须要出现的尺寸大很多。
- 例如,咱们的原图是一张 2700 1900 像素的照片,加载到内存就须要 19.6M 内存空间,然而,咱们须要把它展现在一个列表页中,组件可展现尺寸为 270 190,这时,咱们实际上只须要一张原图的低分辨率的缩略图即可(与图片显示所对应的 UI 控件匹配),那么实际上 270 * 190 像素的图片,只须要 0.2M 的内存即可。
- 能够看到,优化前后相差了 98 倍,原来显示 1 张,当初能够显示 98 张图片,成果十分显著。
-
既然在对原图缩放能够显著缩小内存大小,那么咱们应该如何操作呢?
- 先加载到内存,再进行操作吗,能够如果先加载到内存,如同也不太对,这样只接占用了 19.6M + 0.2M 2 份内存了,而咱们想要的是,在原图不加载到内存中,只接将缩放后的图片加载到内存中,能够实现吗?
-
BitmapFactory 提供了从不同资源创立 Bitmap 的解码办法:
- decodeByteArray()、decodeFile()、decodeResource() 等。然而,这些办法在结构位图的时候会尝试分配内存,也就是它们会导致原图间接加载到内存了,不满足咱们的需要。咱们能够通过 BitmapFactory.Options 设置一些附加的标记,指定解码选项,以此来解决该问题。
- 如何操作呢?答案来了:将 inJustDecodeBounds 属性设置为 true,能够在解码时防止内存的调配,它会返回一个 null 的 Bitmap,然而能够获取 outWidth、outHeight 和 outMimeType 值。利用该属性,咱们就能够在图片不占用内存的状况下,在图片压缩之前获取图片的尺寸。
-
怎样才能对图片进行压缩呢?
- 通过设置 BitmapFactory.Options 中 inSampleSize 的值就能够实现。其计算形式大略就是:计算出理论宽高和指标宽高的比率,而后抉择宽和高中最小的比率作为 inSampleSize 的值,这样能够保障最终图片的宽和高。
3.2.2 设置 BitmapFactory.Options 属性
-
大略步骤如下所示
- 要将 BitmapFactory.Options 的 inJustDecodeBounds 属性设置为 true,解析一次图片。留神这个中央是外围,这个解析图片并没有生成 bitmap 对象(也就是说没有为它分配内存控件),而仅仅是拿到它的宽低等属性。
- 而后将 BitmapFactory.Options 连同冀望的宽度和高度一起传递到到 calculateInSampleSize 办法中,就能够失去适合的 inSampleSize 值了。这一步会压缩图片。
- 之后再解析一次图片,应用新获取到的 inSampleSize 值,并把 inJustDecodeBounds 设置为 false,就能够失去压缩后的图片了。此时才正式创立了 bitmap 对象,因为后面曾经对它压缩了,所以你会发现此时所占内存大小曾经很少了。
-
具体的实现代码
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // 第一次解析将 inJustDecodeBounds 设置为 true,来获取图片大小 final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // 调用下面定义的办法计算 inSampleSize 值 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 应用获取到的 inSampleSize 值再次解析图片 options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); }
-
思考:inJustDecodeBounds 这个参数是干什么的?
- 如果设置为 true 则示意 decode 函数不会生成 bitmap 对象,仅是将图像相干的参数填充到 option 对象里,这样咱们就能够在不生成 bitmap 而获取到图像的相干参数了。
-
为何设置两次 inJustDecodeBounds 属性?
- 第一次:设置为 true 则示意 decode 函数不会生成 bitmap 对象,仅是将图像相干的参数填充到 option 对象里,这样咱们就能够在不生成 bitmap 而获取到图像的相干参数。
- 第二次:将 inJustDecodeBounds 设置为 false 再次调用 decode 函数时就能生成 bitmap 了。而此时的 bitmap 曾经压缩减小很多了,所以加载到内存中并不会导致 OOM。
3.3 图片品质压缩
-
在 Android 中,对图片进行品质压缩,通常咱们的实现形式如下所示:
//quality 为 0~100,0 示意最小体积,100 示意最高品质,对应体积也是最大 bitmap.compress(Bitmap.CompressFormat.JPEG, quality , outputStream);
-
在上述代码中,咱们抉择的压缩格局是 CompressFormat.JPEG,除此之外还有两个抉择:
- 其一,CompressFormat.PNG,PNG 格局是无损的,它无奈再进行品质压缩,quality 这个参数就没有作用了,会被疏忽,所以最初图片保留成的文件大小不会有变动;
- 其二,CompressFormat.WEBP,这个格局是 google 推出的图片格式,它会比 JPEG 更加省空间,通过实测大略能够优化 30% 左右。
-
Android 品质压缩逻辑,函数 compress 通过一连串的 java 层调用之后,最初来到了一个 native 函数:
- 具体看:Bitmap.cpp,最初调用了函数 encoder->encodeStream(…)编码保留本地。该函数是调用 skia 引擎来对图片进行编码压缩。
3.4 双线性采样压缩
-
双线性采样(Bilinear Resampling)在 Android 中的应用形式个别有两种:
bm = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/2, bitmap.getHeight()/2, true); // 或者间接应用 matrix 进行缩放 Matrix matrix = new Matrix(); matrix.setScale(0.5f, 0.5f); bm = Bitmap.createBitmap(bitmap, 0, 0, bit.getWidth(), bit.getHeight(), matrix, true);
-
看源码能够晓得 createScaledBitmap 函数最终也是应用第二种形式的 matrix 进行缩放
- 双线性采样应用的是双线性內插值算法,这个算法不像邻近点插值算法一样,间接粗犷的抉择一个像素,而是参考了源像素相应地位四周 2 ×2 个点的值,依据绝对地位取对应的权重,通过计算之后失去指标图像。
3.5 高清图分片加载
- 实用场景 : 当一张图片十分大 , 在手机中只须要显示其中一部分内容 , BitmapRegionDecoder 十分有用。
- 次要作用 : BitmapRegionDecoder 能够从图像中 解码一个矩形区域。相当于手在滑动的过程中,计算以后显示区域的图片绘制进去。
- 根本应用流程 : 先创立,后解码。调用 newInstance 办法 , 创立 BitmapRegionDecoder 对象;而后调用 decodeRegion 办法 , 获取指定 Rect 矩形区域的解码后的 Bitmap 对象
3.6 图片综合压缩
-
个别状况下图片综合压缩的整体思路如下:
- 第一步进行采样率压缩;
- 第二步进行宽高的等比例压缩(微信对原图和缩略图限度了最大长宽或者最小长宽);
- 第三步就是对图片的品质进行压缩(个别 75 或者 70);
- 第四步就是采纳 webP 的格局。
-
对于图片压缩的综合案例如下
- 具体能够参考:CompressServer
04. 色调格局及内存优化
4.1 RGB 色彩品种
-
RGB 色调模式是工业界的一种色彩规范
- 通过对红 (R)、绿(G)、蓝(B) 三个色彩通道的变动以及它们相互之间的叠加来失去各式各样的色彩的,RGB 即是代表红、绿、蓝三个通道的色彩,这个规范简直包含了人类视力所能感知的所有色彩,是使用最广的色彩零碎之一。Android 中,像素的存储形式应用的色调模式正是 RGB 色调模式。
4.2 ARGB 色调模式
-
在 Android 中,咱们常见的一些色彩设置,都是 RGB 色调模式来形容像素色彩的,并且他们都带有透明度通道,也就是所谓的 ARGB。例如,咱们常见的色彩定义如下:
// 在代码中定义色彩值:蓝色 public final int blue=0xff0000ff; // 或者在 xml 中定义:<drawable name="blue">#ff0000ff</drawable>
- 以上设置中,色彩值都是应用 16 进制的数字来示意的。以上色彩值都是带有透明度(通明通道)的色彩值,格局是 AARRGGBB,透明度、红色、绿色、蓝色四个色彩通道,各占有 2 位,也就是一个色彩通道,应用了 1 个字节来存储。
4.3 扭转色调格局优化
- Android 中有多种 RGB 模式,咱们能够设置不同的格局,来管制图片像素色彩的显示品质和存储空间。
-
Android.graphics.Bitmap 类里有一个外部类 Bitmap.Config 类,它定义了能够在 Android 中应用的几种色调格局:
public enum Config {ALPHA_8 (1), RGB_565 (3), @Deprecated ARGB_4444 (4), ARGB_8888 (5), RGBA_F16 (6), HARDWARE (7); }
-
解释一下这几个值别离代表了什么含意?咱们曾经晓得了:A 代表透明度、R 代表红色、G 代表绿色、B 代表蓝色。
- ALPHA_8:示意,只存在 Alpha 通道,没有存储色调值,只含有透明度,每个像素占用 1 个字节的空间。
- RGB_565:示意,R 占用 5 位二进制的地位,G 占用了 6 位,B 占用了 5 位。每个像素占用 2 个字节空间,并且不蕴含透明度。
- ARGB_4444:示意,A(透明度)、R(红色)、G(绿色)、B(蓝色)4 个通道各占用 4 个 bit 位。每个像素占用 2 个字节空间。
- ARGB_8888:示意,A(透明度)、R(红色)、G(绿色)、B(蓝色)4 个通道各占用 8 个 bit 位。每个像素占用 4 个字节空间。
- RGBA_F16:示意,每个像素存储在 8 个字节上。此配置特地适宜广色域和 HDR 内容。
- HARDWARE:非凡配置,当位图仅存储在图形内存中时。此配置中的位图始终是不可变的。
-
那么开发中个别抉择哪一种比拟适合呢
- Android 中的图片在加载时,默认的色调格局是 ARGB_8888,也就是每个像素占用 4 个字节空间,一张 2700 1900 像素的照片,加载到内存就须要 19.6M 内存空间(2592 1936 * 4 bytes)。
- 如果图片在 UI 组件中显示时,不须要太高的图片品质,例如显示一张缩略图(不通明图片)等场景,这时,咱们就没必要应用 ARGB_8888 的色调格局了,只须要应用 RGB_565 模式即可满足显示的须要。
- 那么,咱们的优化操作就能够是:
- 将 2700 1900 像素的原图,压缩到原图的低分辨率的缩略图 270 190 像素的图片,这时须要 0.2M 的内存。也就是从 19.6M 内存,压缩为 0.2 M 内存。
- 咱们还能够进一步优化色调格局,由 ARGB_8888 改为 RGB_565 模式,这时,指标图片须要的内存就变为 270 190 2 = 0.1M 了。图片内存空间又减小了一倍。
05. 缓存的应用实际优化
5.1 Lru 内存缓存
- LruCache 类特地适宜用来缓存 Bitmap,它应用一个强援用的 LinkedHashMap 保留最近援用的对象,并且在缓存超出设定大小时,删除最近起码应用的对象。
-
给 LruCache 确定一个适合的缓存大小十分重要,咱们须要思考几个因素:
- 利用残余多少可用内存?
- 须要有多少张图片同时显示到屏幕上?有多少图片须要筹备好以便马上显示到屏幕?
- 设施的屏幕大小和密度是多少?高密度的设施须要更大的缓存空间来缓存同样数量的图片。
- Bitmap 的尺寸配置是多少,破费多少内存?
- 图片被拜访的频率如何?如果其中一些比另外一些拜访更频繁,那么咱们可能心愿在内存中保留那些最常拜访的图片,或者依据拜访频率给 Bitmap 分组,为不同的 Bitmap 组设置多个 LruCache 对象。
- 是否能够在缓存图片的品质和数量之间寻找平衡点?有时,保留大量低质量的 Bitmap 会十分有用,加载更高质量的图片的工作能够交给另外一个后盾线程解决。
- 缓存太小会导致额定的花销却没有显著的益处,缓存太大同样会导致 java.lang.OutOfMemory 的异样,并且使得你的程序只留下小局部的内存用来工作(缓存占用太多内存,导致其余操作会因为内存不够而抛出异样)。所以,咱们须要剖析理论状况之后,提出一个适合的解决方案。
-
LruCache 是 Android 提供的一个缓存类,通常使用于内存缓存
- LruCache 是一个泛型类,它的底层是用一个 LinkedHashMap 以强援用的形式存储外界的缓存对象来实现的。
-
为什么应用 LinkedHashMap 来作为 LruCache 的存储,是因为 LinkedHashMap 有两种排序形式,一种是插入排序形式,一种是拜访排序形式,默认状况下是以拜访形式来存储缓存对象的;LruCache 提供了 get 和 put 办法来实现缓存的获取和增加,当缓存满时,会将最近起码应用的对象移除掉,而后再增加新的缓存对象。如下源码所示,底层是 LinkedHashMap。
public LruCache(int maxSize) {if (maxSize <= 0) {throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap<K, V>(0, 0.75f, true); }
-
在应用 LruCache 的时候,首先须要获取以后设施的内存容量,通常状况下会将总容量的八分之一作为 LruCache 的容量,而后重写 LruCache 的 sizeof 办法,sizeof 办法用于计算缓存对象的大小,单位须要与调配的容量的单位统一;
// 获取零碎最大缓存 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // set LruCache size; // 应用最大可用内存值的 1 / 8 作为缓存的大小 int cacheSize = maxMemory / 8; LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(@NonNull String uri, @NonNull Bitmap bitmap) { // 重写此办法来掂量每张图片的大小,默认返回图片数量 return bitmap.getRowBytes() * bitmap.getHeight() / 1024; } }; // 插入对象 memoryCache.put(key, bitmap); // 取出对象 memoryCache.get(key);
-
如何淘汰缓存
- 这个就要看 LinkedHashMap 汇合的特点呢!LinkedHashMap 构造函数的第三个参数:accessOrder,传入 true 时,元素会按拜访顺序排列,最初拜访的在遍历器最初端。在进行淘汰时,移除遍历器前端的元素,直至缓存总大小升高到指定大小以下。
5.2 Lru 内存注意事项
-
看一个实在的场景
- 假如咱们的 LruCache 能够缓存 80 张,每次刷新从网络获取 20 张图片且不反复,那么在刷新第五次的时候,依据 LruCache 缓存的规定,第一次刷新的 20 张图片就会从 LruCache 中移出,处于期待被零碎 GC 的状态。如果咱们持续刷新 n 次,期待被回收的张数就会累积到 20 * n 张。
-
会呈现什么问题
- 会呈现大量的 Bitmap 内存碎片,咱们不晓得零碎什么时候会触发 GC 回收掉这些无用的 Bitmap,对于内存是否会溢出,是否会频繁 GC 导致卡顿等未知问题。
-
解决方案该怎么做?
- 第一种:在 3.0 当前引入了 BitmapFactory.Options.inBitmap,如果设置此项,须要解码的图片就会尝试应用该 Bitmap 的内存,这样勾销了内存的动态分配,进步了性能,节俭了内存。
- 第二种:把处于无用的状态的 Bitmap 放入 SoftReference。SoftReference 援用的对象会在内存溢出之前被回收。
- 对于 Lru 缓存案例和代码能够参考:AppLruCache
5.3 应用 Lru 磁盘缓存
- 内存缓存可能进步拜访最近用过的 Bitmap 的速度,然而咱们无奈保障最近拜访过的 Bitmap 都可能保留在缓存中。像相似 GridView 等须要大量数据填充的控件很容易就会用尽整个内存缓存。另外,咱们的利用可能会被相似打电话等行为而暂停并退到后盾,因为后盾利用可能会被杀死,那么内存缓存就会被销毁,外面的 Bitmap 也就不存在了。一旦用户复原利用的状态,那么利用就须要重新处理那些图片。
- 磁盘缓存能够用来保留那些曾经解决过的 Bitmap,它还能够缩小那些不再内存缓存中的 Bitmap 的加载次数。当然从磁盘读取图片会比从内存要慢,而且因为磁盘读取操作工夫是不可预期的,读取操作须要在后盾线程中解决。
- 留神:如果图片会被更频繁的拜访,应用 ContentProvider 或者会更加适合,比方在图库利用中。
- 留神:因为初始化磁盘缓存波及到 I/O 操作,所以它不应该在主线程中进行。然而这也意味着在初始化实现之前缓存能够被拜访。为了解决这个问题,在下面的实现中,有一个锁对象(lock object)来确保在磁盘缓存实现初始化之前,利用无奈对它进行读取。
- 内存缓存的查看是能够在 UI 线程中进行的,磁盘缓存的查看须要在后盾线程中解决。磁盘操作永远都不应该在 UI 线程中产生。当图片解决实现后,Bitmap 须要增加到内存缓存与磁盘缓存中,不便之后的应用。
06. 不同版本对 Bitmap 治理
6.1 演变过程
-
Android 2.3.3 (API level 10)以及之前,
- 一个 Bitmap 的像素数据是寄存在 Native 内存空间中的。这些数据与 Bitmap 对象自身是隔离的,Bitmap 自身被寄存在 Dalvik 堆中。并且无奈预测在 Native 内存中的像素级数据何时会被开释,这意味着程序容易超过它的内存限度并且解体。
-
Android 3.0 (API Level 11)开始
- 像素数据则是与 Bitmap 自身一起寄存在 Dalvik 堆中。
-
Android 8.0(Android O)及之后的版本中
- Bitmap 的像素数据的内存调配又回到了 Native 层,它是在 Native 堆空间进行调配的。
6.2 治理 Bitmap 内存
-
治理 Android 2.3.3 及以下版本的内存应用
- 在 Android 2.3.3 (API level 10) 以及更低版本上,举荐应用 recycle() 办法。如果在利用中显示了大量的 Bitmap 数据,咱们很可能会遇到 OutOfMemoryError 的谬误。recycle() 办法能够使得程序更快的开释内存。
-
治理 Android 3.0 及其以上版本的内存
- 从 Android 3.0 (API Level 11)开始,引进了 BitmapFactory.Options.inBitmap 字段。如果应用了这个设置字段,decode 办法会在加载 Bitmap 数据的时候去重用曾经存在的 Bitmap。这意味着 Bitmap 的内存是被从新利用的,这样能够晋升性能,并且缩小了内存的调配与回收。然而,应用 inBitmap 有一些限度,特地是在 Android 4.4 (API level 19)之前,只有等同大小的位图才能够被重用。
-
治理 Android 8.0 及其以上版本的内存
- 在 Android 8.0 及其以上版本,解决内存,也遵循 Android 3.0 以上版本同样的形式。同时,图片像素数据存储在 native 层,并且不占用 Java 堆的空间,这也代表着咱们领有更大的图片存储空间,能够加载品质更高、数据更多的图片到内存中。然而,内存仍然不是有限的,利用还是要受到手机内存的限度,所以肯定要留神这一点。
6.3 进步 Bitmap 复用
-
Android3.0 之后,并没有强调 Bitmap.recycle();而是强调 Bitmap 的复用。
- 应用 LruCache 对 Bitmap 进行缓存,当再次应用到这个 Bitmap 的时候间接获取,而不必重走编码流程。
- Android3.0(API 11 之后)引入了 BitmapFactory.Options.inBitmap 字段,设置此字段之后解码办法会尝试复用一张存在的 Bitmap。这意味着 Bitmap 的内存被复用,防止了内存的回收及申请过程,显然性能体现更佳。
-
应用这个字段有几点限度:
- 申明可被复用的 Bitmap 必须设置 inMutable 为 true;
- Android4.4(API 19)之前只有格局为 jpg、png,等同宽高(要求刻薄),inSampleSize 为 1 的 Bitmap 才能够复用;
- Android4.4(API 19)之前被复用的 Bitmap 的 inPreferredConfig 会笼罩待分配内存的 Bitmap 设置的 inPreferredConfig;
- Android4.4(API 19)之后被复用的 Bitmap 的内存必须大于须要申请内存的 Bitmap 的内存;
- Android4.4(API 19)之前待加载 Bitmap 的 Options.inSampleSize 必须明确指定为 1。
-
Bitmap 复用的试验,代码如下所示,而后看打印的日志信息
- 从内存地址的打印能够看出,两个对象其实是一个对象,Bitmap 复用胜利;
- bitmapReuse 占用的内存(4346880)正好是 bitmap 占用内存(1228800)的四分之一;
-
getByteCount()获取到的是以后图片该当所占内存大小,getAllocationByteCount()获取到的是被复用 Bitmap 实在占用内存大小。尽管 bitmapReuse 的内存只有 4346880,然而因为是复用的 bitmap 的内存,因此其实在占用的内存大小是被复用的 bitmap 的内存大小(1228800)。这也是 getAllocationByteCount()可能比 getByteCount()大的起因。
@RequiresApi(api = Build.VERSION_CODES.KITKAT) private void initBitmap() {BitmapFactory.Options options = new BitmapFactory.Options(); // 图片复用,这个属性必须设置;options.inMutable = true; // 手动设置缩放比例,使其取整数,不便计算、察看数据;options.inDensity = 320; options.inTargetDensity = 320; Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg_autumn_tree_min, options); // 对象内存地址;Log.i("ycBitmap", "bitmap =" + bitmap); Log.i("ycBitmap", "ByteCount =" + bitmap.getByteCount() + ":::bitmap:AllocationByteCount =" + bitmap.getAllocationByteCount()); // 应用 inBitmap 属性,这个属性必须设置;options.inBitmap = bitmap; options.inDensity = 320; // 设置缩放宽高为原始宽高一半;options.inTargetDensity = 160; options.inMutable = true; Bitmap bitmapReuse = BitmapFactory.decodeResource(getResources(), R.drawable.bg_kites_min, options); // 复用对象的内存地址;Log.i("ycBitmap", "bitmapReuse =" + bitmapReuse); Log.i("ycBitmap", "bitmap:ByteCount =" + bitmap.getByteCount() + ":::bitmap:AllocationByteCount =" + bitmap.getAllocationByteCount()); Log.i("ycBitmap", "bitmapReuse:ByteCount =" + bitmapReuse.getByteCount() + ":::bitmapReuse:AllocationByteCount =" + bitmapReuse.getAllocationByteCount()); //11-26 18:24:07.971 15470-15470/com.yc.ycbanner I/ycBitmap: bitmap = [email protected] //11-26 18:24:07.972 15470-15470/com.yc.ycbanner I/ycBitmap: bitmap:ByteCount = 4346880:::bitmap:AllocationByteCount = 4346880 //11-26 18:24:07.994 15470-15470/com.yc.ycbanner I/ycBitmap: bitmapReuse = [email protected] //11-26 18:24:07.994 15470-15470/com.yc.ycbanner I/ycBitmap: bitmap:ByteCount = 1228800:::bitmap:AllocationByteCount = 4346880 //11-26 18:24:07.994 15470-15470/com.yc.ycbanner I/ycBitmap: bitmapReuse:ByteCount = 1228800:::bitmapReuse:AllocationByteCount = 4346880 }
07. 图片其余方面优化
7.1 缩小 PNG 图片的应用
- 这里要介绍一种新的图片格式:Webp,它是由 Google 推出的一种既保留 png 格局的长处,又可能缩小图片大小的一种新型图片格式。
- 在 Android 4.0(API level 14) 中反对有损的 WebP 图像,在 Android 4.3(API level 18) 和更高版本中反对无损和通明的 WebP 图像。
- 留神一下,Webp 格局图片仅仅只是缩小图片的品质大小,并不会缩小加载图片后的内存占用。
7.2 切割圆角优化
-
计划 1:间接采纳 Canvas.clipPath 相干 api,裁剪出一个圆角区域。
- 该计划简略暴力,通用性强。如果只是一个动态的单图视图,该办法问题不大,但如果是简单页面,滚动的时候,测试就会跟你说,页面卡顿了,要优化。起因就是 Canvas.clip 的相干 api 损耗绝对较大。
-
计划 2:零碎提供的 CardView 设置圆角
- 把原来全工程各个视频控件和图片控件的外层,都加上一层 CardView。革新老本大,布局层级更深一层,layout 工夫加长。
-
计划 3:应用 setXfermode 法
- 此种形式就是再 new 一个雷同尺寸的 bitmap,而后应用 paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));先画圆角矩形,再画原始 bitmap,而后就失去了一个圆角的 bitmap。晚期用得较多,占用 bitmap 双倍内存。
-
计划 4:图片加载库比方 Glide,Fresco 等
- 在底层,无非也是应用下面的这两种种形式。晚期的应用 setXfermode 来实现,起初应用 BitmapShader 实现。应用简略,稳固。
-
计划 5:遮罩
- 还是应用 setXfermode,不过与办法一不同的是:不对图片作任何更改,只在圆角之外再画一层与背景色彩雷同的四个角来遮挡,在视觉上造成圆角图片的成果。
-
那个切割圆角该怎么优化呢?
- 应用计划 3,能够采纳自定义 view,反对 LinearLayout、RelativeLayout、FrameLayout、ConstraintLayout、ImageView、TextView、View、Button 等设置圆角。
- 具体案例可见:RoundCorners
7.3 如何给图片置灰色
-
大略的操作步骤。具体能够参考:PicCalculateUtils
- 第一步:获取原始图片的宽高,而后创立一个 bitmap 可变位图对象。
- 第二步:创立画板 canvas 对象,而后创立画笔 paint。而后调用 canvas.drawBitmap 办法绘制图片
- 第三步:对画笔进行润饰,设置画笔色彩属性,这里应用到了 ColorMatrix,外围就是设置饱和度为 0,即可绘制灰色内容
7.4 如何解决图片旋转呢
-
在 Android 中应用 ImageView 显示图片的时候发现图片显示不正,方向偏了或者倒过去了。
- 解决这个问题很天然想到的两步走,首先是要自动识别图像方向,计算旋转角度,而后对图像进行旋转并显示。
-
辨认图像方向
- 首先在这里提一个概念 EXIF(Exchangeable Image File Format,可替换图像文件)。简而言之,Exif 是一个规范,用于电子照相机(也包含手机、扫描器等)上,用来标准图片、声音、视屏以及它们的一些辅助标记格局。
- Exif 反对的格局如下:图像;压缩图像文件:JPEG、DCT;非压缩图像文件:TIFF;音频;RIFF、WAV
- Android 提供了对 JPEG 格局图像 Exif 接口反对,能够读取 JPEG 文件 metadata 信息,参见 ExifInterface。这些 Metadata 信息总的来说大抵分为三类:日期工夫、空间信息(经纬度、高度)、Camera 信息(孔径、焦距、旋转角、曝光量等等)。
-
对于图像旋转
- 获取了图片的旋转方向后,而后再设置图像旋转。最初 Bitmap 提供的动态 createBitmap 办法,能够对图片设置旋转角度。具体看:PicCalculateUtils
7.5 保留图片且刷相册
-
大略的操作步骤如下所示。具体可看:ImageSaveUtils
- 第一步:创立图片文件,而后将 bitmap 对象写到图片文件中
- 第二步:通过 MediaStore 将图片插入到共享目录相册中
- 第三步:发送告诉,告诉相册中刷新插入图片的数据。留神,获取图片资源 uri 刷新即可,防止刷新所有数据造成等待时间过长。
7.6 对立图片域名优化
-
域名对立
- 缩小了 10%+ 的反复图片下载和内存耗费。同时缩小之前多域名图片加载时反复创立 HTTPS 申请的过程,缩小图片加载工夫。
7.7 优化 H5 图片加载
- 通过拦挡 WebView 图片加载的形式,让原生图片库来下载图片之后传递图片二进制数据给 WebView 显示。
-
采纳 OkHttp 拦挡资源缓存,上面是大略的思路。缓存的入口从 shouldInterceptRequest 登程
- 第一步,拿到 WebResourceRequest 对象中申请资源的 url 还有 header,如果开发者设置不缓存则返回 null
- 第二步,如果缓存,通过 url 判断拦挡资源的条件,过滤非 http,音视频等资源,这个是可自在配置缓存内容比方 css,png,jpg,xml,txt 等
- 第三步,判断本地是否有 OkHttp 缓存数据,如果有则间接读取本地资源,通过 url 找到对应的 path 门路,而后读取文件流,组装数据返回。
- 第四步,如果没有缓存数据,创立 OkHttp 的 Request 申请,将资源网络申请交给 okHttp 来解决,并且用它自带的缓存性能,当然如果是申请失败或者异样则返回 null,否则返回失常数据
- 对于 webView 图片缓存的计划,能够间接参考:YCWebView
7.8 优化图片暗影成果
-
暗影成果有哪些实现形式
- 第一种:应用 CardView,然而不能设置暗影色彩
- 第二种:采纳 shape 叠加,存在前期 UI 成果不便优化
- 第三种:UI 切图
- 第四种:自定义 View
- 第五种:自定义 Drawable
-
否定上背后两种计划起因剖析?
- 第一个计划的 CardView 渐变色和暗影成果很难管制,只能反对线性或者环装模式突变,这种不满足需要,因为暗影自身是一个周围一层很淡的色彩突围,在一个矩形框的层面上色彩大略统一,而且这个 CardView 有很多局限性,比方不能批改暗影的色彩,不能批改暗影的深浅。所以这个思路无奈实现这个需要。
- 第二个采纳 shape 叠加,能够实现暗影成果,然而影响 UI,且暗影局部是占像素的,而且不灵便。
- 第三个计划询问了一下 ui。他们给出的后果是如果应用切图的话那标注的话很难标,身为一个优良的设计师大多对像素点都和敏感,界面上的像素点有一点不协调那都是无奈容忍的。
-
网上一些介绍暗影成果计划
- 所有在深奥的技术,也都是为需要做筹备的。也就是须要实际并且能够用到理论开发中,这篇文章不再形象介绍暗影成果原理,了解三维空间中如何解决偏移光线达到暗影视差等,网上看了一些文章也没看明确或者了解。这篇博客间接通过调用 api 实现预期的成果。
- 多个 drawable 叠加,应用 layer-list 能够将多个 drawable 依照程序层叠在一起显示,默认状况下,所有的 item 中的 drawable 都会主动依据它附上 view 的大小而进行缩放,layer-list 中的 item 是依照程序从下往上叠加的,即先定义的 item 在上面,前面的顺次往上面叠放
-
暗影是否占位
- 应用 CardView 暗影不占位,不能设置暗影色彩和成果
- 应用 shape 暗影是能够设置暗影色彩,然而是占位的
-
几种计划优缺点比照剖析
- CardView 长处:自带性能实现简略 毛病:自带圆角不肯定可适配所有需要
- layer(shape 叠加) 长处:实现模式简略 毛病:成果个别
- 自定义实现 长处:实现成果好可配置能力高 毛病:须要开发者自行开发
-
对于解决暗影成果
- 具体各种计划的比照能够参考这个 demo:AppShadowLib
7.9 图片资源的压缩
- 咱们利用中应用的图片,设计师出的原图通常都十分大,他们通常会应用工具,通过肯定的压缩,缩减到比拟小一些的大小。
- 然而,这些图片通常都有肯定的可压缩空间,我在之前的我的项目中,对图片进行了二次压缩,整体压缩率达到了 40%~50%,成果还是十分不错的。
-
这里介绍下罕用的,图片压缩的办法:
- 应用压缩工具对图片进行二次压缩。
- 依据最终图片是否须要透明度展现,优先选择不通明的图片格式,例如,咱们应该防止应用 png 格局的图片。
- 对于色调简略,例如,一些背景之类的图片,能够抉择应用布局文件来定义(矢量图),这样就会十分节俭内存了。
- 如果蕴含透明度,优先应用 WebP 等格局图像。
-
图片在上线前进行压缩解决,岂但能够缩小内存的应用,如果图片是网络获取的,也能够缩小网络加载的流量和工夫。
- 举荐一个图片压缩网站:tinypng 网站