虽说从事android开发多年,但是一直处于不求甚解的过程,只是积累了工作年限,但实际的工作经验并未见长,这不在日常开发中会经常遇到的bitmap的相关问题,每次都得百度一番惭愧惭愧呀!
首先在实际应用中,会遇到各种概念,有时总是傻傻的分不清楚,索性又百度了一番结合源码来个小小的总结吧。
与大小相关的概念
- 图片实际大小
- 图片占用内存空间大小
一张图片占用内存空间大小是多少?
我们可以看到2个图片的分辩率都是1500*2560,一个是2.73 MB,一个是583 KB,在经过BitmapFactory.decodeResource
(禁止缩放)之后得到bitmap,再通过Bitmap.getAllocationByteCount
获取占用内存空间大小竟然是一样的,让人疑惑?
Bitmap
类中有2个方法getAllocationByteCount
与getByteCount
:
getAllocationByteCount
根据文档的意思即占用内存空间大小,在没有其他操作的情况下默认与getByteCount
取值一样,那么也就是说默认这个值一样,大小为getRowBytes() * getHeight()
再深入一些,会发现getRowBytes
涉及到了C++代码,暂且不说,可以参考其他文档
getRowBytes
代码没找到,C++不懂,不过这个取值实际就是= 宽 * 4 * 高,这个是在没有任何缩放的情况下计算的 。
作一个说明如果图片来源是File、URL 或者 Assets目录,通过
BitmapFactory
得到的图片则是没有缩放的,其占用内存空间大小就是等于 宽 * 4 * 高;-
如果图片来源资源目录
xxhdpi(480dpi) 、xxxhdpi(640dpi)
等其他目录 ,则在经过·BitmapFactory
解析得到bitmap的时候 会经过缩放,缩放比为设备的DPI与资源目录对应的DPI进行对比,也就是我们经常遇过的现象 如手机设备的density为480,这个时候若把图片放在xxhdpi里面,解析得到的bitmap然后取其宽高,这个取值跟实际的一样,
这个时候若将图片放到xhdpi里面,则得到的bitmap的宽高都会变大,图片放大了
这个时候若将图片放到xxhdpi里面,刚得到bitmap的宽高都会变小,图片缩小了
在种场景之下,获取的的bitmap所占用的内存空间则为
宽 x scale x 4 x 高 x scale
与图片压缩相关
- 质量压缩
compress
- 邻近采样压缩
options.inSampleSize=2
- 双线性采样
matrix.setScale(0.5f, 0.5f);
- 还有一些其他策略但涉及到底层算法之类的,暂时还搞不懂
具体可以参考上篇文章里面推荐的链接
Android中图片压缩分析(下)
LuBan 仿微信朋友圈压缩策略
鲁班压缩,这个工具类库代码不多,1.1.8版本压缩核心方法其实也就是先采样再质量压缩 ,这样可以适用于一般的场景之下, 由于这个类库长期未进行更新,遗留许多问题需要改进,但我们可以借鉴其原理,根据实际情况加一改进。
File compress() throws IOException {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = computeSize();//计算采样率
Bitmap tagBitmap = BitmapFactory.decodeStream(srcImg.open(), null, options);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
if (Checker.SINGLE.isJPG(srcImg.open())) {
//旋转图片
tagBitmap = rotatingImage(tagBitmap,Checker.SINGLE.getOrientation(srcImg.open()));
}
//是否保留透明通道 保留PNG无损压缩 JPEG质量60%
tagBitmap.compress(focusAlpha ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG, 60, stream);
tagBitmap.recycle();
FileOutputStream fos = new FileOutputStream(tagImg);
fos.write(stream.toByteArray());
fos.flush();
fos.close();
stream.close();
return tagImg;
}
private int computeSize() {
//%2==1的话 加1 是为了方便计算取整吧
srcWidth = srcWidth % 2 == 1 ? srcWidth + 1 : srcWidth;
srcHeight = srcHeight % 2 == 1 ? srcHeight + 1 : srcHeight;
int longSide = Math.max(srcWidth, srcHeight);
int shortSide = Math.min(srcWidth, srcHeight);
//先计算一下宽高比 拍照宽高比 16:9=1:0.5625
float scale = ((float) shortSide / longSide);
//这些取值不知道作者是根据什么推断出来的,但现在从微信聊天记录中保存的图片大小似乎是1080*1440
if (scale <= 1 && scale > 0.5625) {
if (longSide < 1664) {
return 1;
} else if (longSide < 4990) { //1024*4 = 4096
return 2;
} else if (longSide > 4990 && longSide < 10240) {
return 4;
} else {
return longSide / 1280 == 0 ? 1 : longSide / 1280;
}
} else if (scale <= 0.5625 && scale > 0.5) {
return longSide / 1280 == 0 ? 1 : longSide / 1280;
} else {
return (int) Math.ceil(longSide / (1280.0 / scale));
}
}
保存图片大小的问题
在对bitmap压缩之后,有时我们会将图片保存到本地,这个时候有时会发现实际保存的图片大小 与压缩之后计算的预计保存的图片大小不一致的问题?
InputStream inputStream1 = getResources().openRawResource(R.mipmap.china_map_xxhdpi);
//这个大小 基本与硬盘中的显示大小一样,如果略有差别应该是kb按照1000或者1024计算的原因
KLog.d(TAG, "==china map 原始大小" + inputStream1.available() / default_size + "kb");
Drawable drawable = getResources().getDrawable(R.mipmap.china_map_xxhdpi);
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
KLog.d(TAG, "占用内存大小 getAllocationByteCount===" + bitmap.getAllocationByteCount() / default_size + " kb");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
KLog.d(TAG, "==before=baos大小===最高质量压缩的大小" + baos.toByteArray().length / default_size + " kb");
int quality = 95;
boolean flag = true;
while (baos.toByteArray().length / default_size > 100 && flag) {
baos.reset();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
quality -= 5;
if (quality <= 0) {
flag = false;
}
}
KLog.d(TAG, "==after=baos大小===控制100以内-----" + baos.toByteArray().length / default_size + " kb");
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(baos.toByteArray());
KLog.d(TAG, "byteArrayInputStream==1024===" + byteArrayInputStream.available() / default_size + " kb");
KLog.d(TAG, "byteArrayInputStream==1000===" + byteArrayInputStream.available() / 1000 + " kb");
...省略
//图片大小 与计算大小致
String filepath = dirpath + File.separator + name + ".jpg";
KLog.d(filepath);
FileOutputStream fos = new FileOutputStream(new File(filepath));
fos.write(baos.toByteArray());
fos.flush();
fos.close();
ByteArrayOutputStream baosResult = new ByteArrayOutputStream();
resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baosResult);
KLog.d(TAG, "===baosResult==resultBitmap==" + baosResult.toByteArray().length / default_size + " kb");
//经测试保存的图片确实 变大 了
String filepath2 = dirpath + File.separator + name + "_result_" + baosResult.toByteArray().length / default_size + ".jpg";
KLog.d(filepath2);
FileOutputStream fos2 = new FileOutputStream(new File(filepath2));
fos2.write(baosResult.toByteArray());
fos2.flush();
fos2.close();
所以说当压缩图片到目标大小后,不能再经过其他操作,直接使用流保存到本地这个时候大小就与计算大小一样,如果再经过转换大小就可能出现变化。