基本概念(是什么,应用场景)以及BitMap的编码原理(做引导)
BitMap类在Android类中的基本实现(基本结构)
recycle
Bitmap
位图的像素都分配有特定的位置和颜色值。每个像素的颜色信息由RGB组合或者灰度值表示。
根据位深度,可将位图分为1、4、8、16、24及32位图像等。每个像素使用的信息位数越多,可用的颜色就越多,颜色表现就越逼真,相应的数据量越大。例如,位深度为1的像素位图只有两个可能的值(黑色和白色),所以又称为二值位图。位深度为8的图像有28(即256)个可能的值。位深度为8的灰度模式图像有256个可能的灰色值。
Config解析:
Bitmap.Config.ALPHA_8:颜色信息只由透明度组成,占8位。
Bitmap.Config.ARGB_4444:颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占4位,总共占16位。已经被废弃,因为显示质量不好。
Bitmap.Config.ARGB_8888:颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占8位,总共占32位。是Bitmap默认的颜色配置信息,也是最占空间的一种配置。
Bitmap.Config.RGB_565:颜色信息由R(Red),G(Green),B(Blue)三部分组成,R占5位,G占6位,B占5位,总共占16位。如果不需要 alpha 通道,特别是资源本身为 jpg 格式的情况下,用这个格式比较理想
这个在skia库中可以看到
当需要做性能优化或者防止OOM(Out Of Memory),我们通常会使用Bitmap.Config.RGB_565这个配置。
大多数情况下其实我们并不需要argb中的alpha通道,在背景已知的情况下,rgb和argb是可以互相转换的。而多数情况下我们都是白色背景。(直接使用target的 rgb就可以了)
Source => Target = (BGColor + Source) =
Target.R = ((1 - Source.A) * BGColor.R) + (Source.A * Source.R)
Target.G = ((1 - Source.A) * BGColor.G) + (Source.A * Source.G)
Target.B = ((1 - Source.A) * BGColor.B) + (Source.A * Source.B)
小问题:RGB_565这个数字是怎么定的?为什么不取555?
文件读取是按byte来的。
和矢量图的比较
1.文件小,图像中保存的是线条和图块的信息,所以矢量图形文件与分辨率和图像大小无关,只与图像的复杂程度有关,图像文件所占的存储空间较小。
2矢量图无限放大不模糊,大部分位图都是由矢量导出来的
3.矢量图最大的缺点是难以表现色彩层次丰富的逼真图像效果。
移动端开发中位图的应用很少,因为很少遇到这种需要无限缩放的场景。对于少数有缩放需要的场景,Bitmap类提供了一种特殊而且有趣的方式。这就是(九点图)
Bitmap的相关类很多,但是只要按照一个基本思路梳理 ,就会很清晰
- 文件和Bitmap的相互转换
1.1 文件转换为Bitmap
Bitmap是一个final类,因此不能被继承。Bitmap只有一个构造方法,且该构造方法是没有任何访问权限修饰符修饰,也就是说该构造方法是friendly,但是谷歌称Bitmap的构造方法是private(私有的),感觉有点不严谨。不管怎样,一般情况下,我们不能通过构造方法直接新建一个Bitmap对象。
从文件创建Bitmap类就离不开BitmapFactory
BitmapFactory类提供了四类方法:decodeFile、decodeRe-source、decodeStream和decodeByteArray,分别用于支持从文件系统、资源、输入流以及字节数组中加载出一个Bitmap对象,其中decodeFile和decodeResource又间接调用了decode-Stream方法,这四类方法最终是在Android的底层实现的,对应着BitmapFactory类的几个native方法。
其实核心思想也很简单,那就是采用BitmapFactory.Options来加载所需尺寸的图片。
通过BitmapFactory.Options来缩放图片,主要是用到了它的inSampleSize参数,即采样率。当inSampleSize为1时,采样后的图片大小为图片的原始大小;当inSampleSize大于1时,比如为2,那么采样后的图片其宽/高均为原图大小的1/2,而像素数为原图的1/4,其占有的内存大小也为原图的1/4。
(1)将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片。
这一步并不会读取文件的像素区块。只会去从
(2)从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数。
(3)根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize。
(4)将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。
我们现在有个需求,要求将一张图片进行模糊,然后作为 ImageView 的 src 呈现给用户,而我们的原始图片大小为 1080*1920,如果我们直接拿来模糊的话,一方面模糊的过程费时费力,另一方面生成的图片又占用内存,实际上在模糊运算过程中可能会存在输入和输出并存的情况,此时内存将会有一个短暂的峰值。
1.2 从Bitmap转换为文件
Bitmap支持的文件格式
Bitmap在内存中的大小是可以简单计算出来的了。但是文件不同,文件可以进行压缩
一种比较典型的压缩方式比如
位图的格式有很多种, 每种的压缩算法都不同。
但是目前Android中的Bitmap只支持三种
CompressFormat解析:
和刚才的Bitmap.Config相比,这个内部类只会在压缩文件等时被用到
Bitmap.CompressFormat.JPEG:表示以JPEG压缩算法进行图像压缩,压缩后的格式可以是".jpg"或者".jpeg",是一种有损压缩。
Bitmap.CompressFormat.PNG:表示以PNG压缩算法进行图像压缩,压缩后的格式可以是".png",是一种无损压缩。这意味着在解析时,可能会忽略掉 质量。
Bitmap.CompressFormat.WEBP:是一种同时提供了有损压缩与无损压缩(可逆压缩)的图片文件格式
而在日常应用中发现,同样的图片质量(70%)下,这三种格式的大小是有差异的:
如果对存储性能要求更严格的话(存储器空间不足)或者有大量图片存储,可以考虑使用webp格式。
题外话,如果本地有大量的图片资源文件,可以考虑批量将png图转换成webp格式。
2.Bitmap的调整
Bitmap自身的调整也是一件非常有意思的事情。 说道bitmap就不得不提Matrix。
可以说,这俩如影相随。
???
这里写什么# 矩阵变换?
3. 手动recycler()是否有必要?
Google对这个问题其实已经解释,但是比较含混:
首先我们看下这个方法到底做了些什么:
我们刚才已经看过Bitmap的java类中所包含的只是一些方便我们取用的信息。
主要方法包括recycler都在能去JNI层查看。
这段话其实看得人也比较迷糊。前一句还比较简单。大致是说其主要数据都是存在native的内存中,无轮是malloc了native memory哪些,轮不到dalvik来管。所以在“交互接口”上得自己管理好资源的分配和释放。 如果处理得不好,有可能java虚拟机自己跑得还挺欢,进程首先内存就不够用了。
怎么办 ,就需要我们显式去调用recycler()
所以2.3.3 之前的代码应该怎么写呢,得靠你自己来实现一个引用计数器。
对于我们java程序员来说,这个真的有点难。我就想做个图片,你还得让我实现一个引用计数器?
即便是对于今天的C++ 程序员来说,也已经有智能指针来帮助他做这些事情了。
那么 现在的版本里,recycler是否有效果?我们写一个小demo先试试吧:
我选用的targetApi为25 ,源码非常简单
结果如图:
我们发现,其实手动调用recycler并没有将内存释放掉。
那是以前,现在又提到到了3.0之后 Dalvik 又把这些东西都收到自己的堆里, 并且和Bitmap联系起来。
怎么联系起来的?
我们先看看Bitmap的构造方法,这是个私有的构造方法。是在native层构建了之后,再回调过来的。
在这个方法中 ,有这样一句话(API25):
厉害了,native层分配的内存大小居然是业务中自己计算出来的,连同析构函数一同给了这个注册器。
这个注册器最终会调用到VMRunTime的registerNativeAllocation
会将native对象的大小通知给dalvik,如果当前的native内存分配过大,可能会引发一次GC,这也是为什么我们看到了上面的效果、
最终实际进行gc的地方:
(https://android.googlesource.com/platform/dalvik/+/kitkat-release/vm/alloc/HeapSource.cpp)
当然这只是其中一种的gc触发路径。在别的很多情况下都有可能,但是recycler并不会触发gc,或者说recycler 方法并不能在性能上带来提升。gc的事情还是去交给gc去做吧。