雖說從事android開發(fā)多年,但是一直處于不求甚解的過程,只是積累了工作年限,但實(shí)際的工作經(jīng)驗(yàn)并未見長,這不在日常開發(fā)中會(huì)經(jīng)常遇到的bitmap的相關(guān)問題,每次都得百度一番慚愧慚愧呀!
首先在實(shí)際應(yīng)用中,會(huì)遇到各種概念,有時(shí)總是傻傻的分不清楚衷掷,索性又百度了一番結(jié)合源碼來個(gè)小小的總結(jié)吧齿坷。
與大小相關(guān)的概念
- 圖片實(shí)際大小
- 圖片占用內(nèi)存空間大小
一張圖片占用內(nèi)存空間大小是多少?
我們可以看到2個(gè)圖片的分辯率都是1500*2560,一個(gè)是2.73 MB,一個(gè)是583 KB,在經(jīng)過BitmapFactory.decodeResource
(禁止縮放)之后得到bitmap,再通過Bitmap.getAllocationByteCount
獲取占用內(nèi)存空間大小竟然是一樣的,讓人疑惑些阅?
Bitmap
類中有2個(gè)方法getAllocationByteCount
與getByteCount
:
getAllocationByteCount
根據(jù)文檔的意思即占用內(nèi)存空間大小,在沒有其他操作的情況下默認(rèn)與getByteCount
取值一樣,那么也就是說默認(rèn)這個(gè)值一樣,大小為getRowBytes() * getHeight()
再深入一些,會(huì)發(fā)現(xiàn)getRowBytes
涉及到了C++代碼,暫且不說,可以參考其他文檔
getRowBytes
代碼沒找到,C++不懂,不過這個(gè)取值實(shí)際就是= 寬 * 4 * 高,這個(gè)是在沒有任何縮放的情況下計(jì)算的 剪决。
作一個(gè)說明如果圖片來源是File次员、URL 或者 Assets目錄歧匈,通過
BitmapFactory
得到的圖片則是沒有縮放的,其占用內(nèi)存空間大小就是等于 寬 * 4 * 高;-
如果圖片來源資源目錄
xxhdpi(480dpi) 垒酬、xxxhdpi(640dpi)
等其他目錄 ,則在經(jīng)過·BitmapFactory
解析得到bitmap的時(shí)候 會(huì)經(jīng)過縮放,縮放比為設(shè)備的DPI與資源目錄對(duì)應(yīng)的DPI進(jìn)行對(duì)比,也就是我們經(jīng)常遇過的現(xiàn)象 如手機(jī)設(shè)備的density為480,這個(gè)時(shí)候若把圖片放在xxhdpi里面,解析得到的bitmap然后取其寬高,這個(gè)取值跟實(shí)際的一樣,
這個(gè)時(shí)候若將圖片放到xhdpi里面,則得到的bitmap的寬高都會(huì)變大,圖片放大了
這個(gè)時(shí)候若將圖片放到xxhdpi里面,剛得到bitmap的寬高都會(huì)變小,圖片縮小了
在種場(chǎng)景之下,獲取的的bitmap所占用的內(nèi)存空間則為
寬 x scale x 4 x 高 x scale
與圖片壓縮相關(guān)
- 質(zhì)量壓縮
compress
- 鄰近采樣壓縮
options.inSampleSize=2
- 雙線性采樣
matrix.setScale(0.5f, 0.5f);
- 還有一些其他策略但涉及到底層算法之類的,暫時(shí)還搞不懂
具體可以參考上篇文章里面推薦的鏈接
魯班壓縮,這個(gè)工具類庫代碼不多,1.1.8版本壓縮核心方法其實(shí)也就是先采樣再質(zhì)量壓縮 ,這樣可以適用于一般的場(chǎng)景之下, 由于這個(gè)類庫長期未進(jìn)行更新,遺留許多問題需要改進(jìn),但我們可以借鑒其原理,根據(jù)實(shí)際情況加一改進(jìn)。
File compress() throws IOException {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = computeSize();//計(jì)算采樣率
Bitmap tagBitmap = BitmapFactory.decodeStream(srcImg.open(), null, options);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
if (Checker.SINGLE.isJPG(srcImg.open())) {
//旋轉(zhuǎn)圖片
tagBitmap = rotatingImage(tagBitmap,Checker.SINGLE.getOrientation(srcImg.open()));
}
//是否保留透明通道 保留PNG無損壓縮 JPEG質(zhì)量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 是為了方便計(jì)算取整吧
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);
//先計(jì)算一下寬高比 拍照寬高比 16:9=1:0.5625
float scale = ((float) shortSide / longSide);
//這些取值不知道作者是根據(jù)什么推斷出來的勘究,但現(xiàn)在從微信聊天記錄中保存的圖片大小似乎是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));
}
}
保存圖片大小的問題
在對(duì)bitmap壓縮之后矮湘,有時(shí)我們會(huì)將圖片保存到本地,這個(gè)時(shí)候有時(shí)會(huì)發(fā)現(xiàn)實(shí)際保存的圖片大小 與壓縮之后計(jì)算的預(yù)計(jì)保存的圖片大小不一致的問題?
InputStream inputStream1 = getResources().openRawResource(R.mipmap.china_map_xxhdpi);
//這個(gè)大小 基本與硬盤中的顯示大小一樣,如果略有差別應(yīng)該是kb按照1000或者1024計(jì)算的原因
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, "占用內(nèi)存大小 getAllocationByteCount===" + bitmap.getAllocationByteCount() / default_size + " kb");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
KLog.d(TAG, "==before=baos大小===最高質(zhì)量壓縮的大小" + 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以內(nèi)-----" + 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");
...省略
//圖片大小 與計(jì)算大小致
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");
//經(jīng)測(cè)試保存的圖片確實(shí) 變大 了
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();
所以說當(dāng)壓縮圖片到目標(biāo)大小后,不能再經(jīng)過其他操作,直接使用流保存到本地這個(gè)時(shí)候大小就與計(jì)算大小一樣,如果再經(jīng)過轉(zhuǎn)換大小就可能出現(xiàn)變化口糕。