图片是我们app开发中最常见的一种展示形态,因此对于图片的压缩和内存空间占用是非常有必要理解和掌握的,这篇文章借鉴和归纳了一下图片加载到内存空间的占用及常见压缩算法等,通过这样的总结和归纳希望我们再次面对图片问题的时候有一个很明朗的思维和处理方案。
图片的物理内存的计算
对于一张图片,无论是jpg、png、bmp或者其他格式,我们光知道他们像素总数是1280*720,是无法计算出图片大小的~~因为压缩方式、编码等都不一样.
但是我们要有一个这样的概念
bmp
无压缩情况下的原图
png
无损压缩算法格式
jpg
有损压缩算法格式
这里我们暂时不过多讨论物理内存占用情况,android也提供了webp的有损压缩格式很大程度上减小了物理内存的空间占用。这里我们特别的研究一下运行时内存占用及优化方案。
图片运行内存计算
我们常规的认知
里计算图片占用运行时虚拟机内存的计算方法
图片分辨率 * 每个像素点大小
而每个像素点的大小的计算方式是和图片质量有关系如下表所示
Possible bitmap configurations. A bitmap configuration describes how pixels are stored. This affects the quality (color depth) as well as the ability to display transparent/translucent colors. 参考GoogleDeveloper
BitMap.Config | 释义 |
---|---|
ALPHA_8 | 此时图片只有alpha值,没有RGB值,一个像素占用一个字节 |
ARGB_4444 | 一个像素占用2个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占4个bites,共16bites,即2个字节 |
ARGB_8888 | 一个像素占用4个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占8个bites,共32bites,即4个字节这是一种高质量的图片格式,电脑上普通采用的格式。它也是Android手机上一个BitMap的默认格式。 |
RGB_565 | 从Android4.0开始,该选项无效。即使设置为该值,系统任然会采用 ARGB_8888来构造图片 |
?那么一张图片无论jpg或者png加载到内存中的时候占用的内存空间是分辨率*Bitmap.Config占用的字节数吗
是的,这个答案是不准确的。
如果我们要获取这个答案的话我们可以自己写demo去测试,分别加载不同资源目录下如assests;res下不同屏幕密度下的资源文件夹如drawable-hdpi,drawable-xdpi;磁盘目录下等 具体的测试过程可以参考这篇文章
private void loadResImage(ImageView imageView) {
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.weixin, options);
//Bitmap bitmap = BitmapFactory.decodeFile("mnt/sdcard/weixin.png", options);
imageView.setImageBitmap(bitmap);
Log.i("!!!!!!", "bitmap:ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
Log.i("!!!!!!", "width:" + bitmap.getWidth() + ":::height:" + bitmap.getHeight());
Log.i("!!!!!!", "inDensity:" + options.inDensity + ":::inTargetDensity:" + options.inTargetDensity);
Log.i("!!!!!!", "imageview.width:" + imageView.getWidth() + ":::imageview.height:" + imageView.getHeight());
}
这里我们只备注一下测试结果:
当我们把图片放在不同位置去分别加载的时候,得到的在内存中占用大小是不一样的如
资源文件夹 | 对应像素密度值 |
---|---|
mdpi | 160 |
hdpi | 240 |
xhdpi | 320 |
xxhdpi | 480 |
结论如下(当我们测试一张1024*768分辨率的图片并且图片质量为ARGB_8888也就是像素占用字节数为4)
- 图片放在assests和磁盘里加载的话占用内存大小计算方式为10247684
- 图片放在不同密度对应的文件夹时占用内存大小为1024密度值/260768密度值/2604
我们看到在不同密度值代表的文件夹的图片会经过一次分辨率转换 然后在进行图片加载;这些理论是基于Android原生的图片处理算法得出的,如果使用了第三方的图片加载库,这些理论就有可能不成立了,因为他们对不同的图片进行了相应的图片压缩算法的处理。如文章最后我们要说的一个图片压缩算法一样。
优化方案
基于以上的知识储备,因此理解图片加载的优化方案就很好理解我们可以通过以下几个方案来处理图片优化相关
- 存放在apk中的资源文件如果是大文件的话可以考虑使用jpg格式替换png格式;
- 加载资源文件res不是只放一份文件就可以,如果需要兼容不同的设备就需要放置不同的文件防止造成图片加载到内存空间占用过大
- 处理图片的分辨率或者图片质量达到兼容视觉和内存的效果
- google推荐使用Glide处理图片缓存问题,但是这些基础的图片处理策略我们还是要清楚
jpg图片压缩算法
首先明确一下这里的压缩算法,针对的都是对于图片存储的物理空间大小的算法。对于图片的压缩不一定能改变其在内存空间占用的大小(具体参考上面篇幅)
android 原生的图片压缩算法是基于一个叫skia开源的引擎
(基于JPEG引擎修改),但是去掉了比较消耗cpu的哈夫曼算法;这样处理的结果是减少了cpu的消耗但是图片处理的效果并不一定是最好的,改为定长算法
导致了拍一张高清图片会占用很大的存储空间。这也还是计算机领域中存储空间和性能的博弈
;备注:android原生的图片处理引擎解码目前依然保留的是哈夫曼算法
使用Android系统压缩算法api
质量压缩
质量压缩是保持像素的前提下改变图片的位深及透明度,(即:通过算法抠掉(同化)了图片中的一些某个些点附近相近的像素),达到降低质量压缩文件大小的目的。常用在不需要直接操作原图的一些场景中,如
上传图片
,图片另存
使用原生api的进行质量压缩一些示例:
/**
* 质量压缩方法
*
* @param image
* @return
*/
public static Bitmap compressImage(Bitmap image) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
int options = 100;
while (baos.toByteArray().length / 1024 > 100) {
//循环判断如果压缩后图片是否大于100kb,大于继续压缩
baos.reset();//重置baos即清空baos
//第一个参数 :图片格式 ,第二个参数: 图片质量,100为最高,0为最差 ,第三个参数:保存压缩后的数据的流
image.compress(Bitmap.CompressFormat.JPEG