一、Bitmap 內(nèi)存回收
從3.0開(kāi)始秦效,Bitmap 像素?cái)?shù)據(jù)和 Bitmap 對(duì)象一起存放在 Dalvik 堆中,而在3.0之前,Bitmap 像素?cái)?shù)據(jù)存放在 Native 內(nèi)存中邓馒。
所以,在3.0之前蛾坯,Bitmap 像素?cái)?shù)據(jù)在 Nativie 內(nèi)存的釋放是不確定的光酣,容易內(nèi)存溢出而 Crash,官方強(qiáng)烈建議調(diào)用 recycle()(當(dāng)然是在確定不需要的時(shí)候)脉课;而在3.0之后救军,則是強(qiáng)調(diào)Bitmap的復(fù)用。
使用 LruCache 對(duì) Bitmap 對(duì)象進(jìn)行緩存倘零,當(dāng)再次使用到這個(gè) Bitmap 的時(shí)候直接獲取唱遭,而不用重走編碼流程。
Android3.0(API 11之后)引入了 BitmapFactory.Options.inBitmap 字段呈驶,設(shè)置此字段之后解碼方法會(huì)嘗試復(fù)用一張存在的 Bitmap 拷泽。這意味著 Bitmap 的內(nèi)存被復(fù)用,避免了內(nèi)存的回收及申請(qǐng)過(guò)程袖瞻,顯然性能表現(xiàn)更佳司致。不過(guò),使用這個(gè)字段有幾點(diǎn)限制:
- 聲明可被復(fù)用的 Bitmap 必須設(shè)置 inMutable 為 true聋迎;
- Android4.4(API 19)之前只有格式為 jpg脂矫、png,同等寬高(要求苛刻)霉晕,inSampleSize 為1的 Bitmap 才可以復(fù)用庭再;
- Android4.4(API 19)之前被復(fù)用的 Bitmap 的 inPreferredConfig 會(huì)覆蓋待分配內(nèi)存的 Bitmap 設(shè)置的 inPreferredConfig;
- Android4.4(API 19)之后被復(fù)用的 Bitmap 的內(nèi)存必須大于需要申請(qǐng)內(nèi)存的 Bitmap 的內(nèi)存缝彬;
二、Bitmap 內(nèi)存大小
Bitmap 類有兩個(gè)獲取存儲(chǔ) Bitmap 像素所占用內(nèi)存字節(jié)數(shù)的方法:
getByteCount
getByteCount() 方法是 API12 加入的哺眯,代表存儲(chǔ) Bitmap 的像素需要的最少內(nèi)存。
public final int getByteCount() {
return getRowBytes() * getHeight();
}
getAllocationByteCount()
從 API19 開(kāi)始奶卓,加入了 getAllocationByteCount() 方法一疯,代表在內(nèi)存中為 Bitmap 分配的內(nèi)存大小夺姑,代替了 getByteCount() 方法废膘。
public final int getAllocationByteCount() {
if (mBuffer == null) {
//mBuffer 代表存儲(chǔ) Bitmap 像素?cái)?shù)據(jù)的字節(jié)數(shù)組丐黄。
return getByteCount();
}
return mBuffer.length;
}
在不復(fù)用 Bitmap 時(shí)艰争,getByteCount() 和 getAllocationByteCount 返回的結(jié)果是一樣的甩卓。
在通過(guò)復(fù)用 Bitmap 來(lái)解碼圖片時(shí)鹿寻,如果被復(fù)用的 Bitmap 的內(nèi)存比待分配內(nèi)存的 Bitmap 大,那么 getByteCount() 表示新解碼圖片占用內(nèi)存的大小(并非實(shí)際內(nèi)存大小,實(shí)際大小是復(fù)用的那個(gè)Bitmap的大小),getAllocationByteCount() 表示被復(fù)用 Bitmap真實(shí)占用的內(nèi)存大屑獗肌(即 mBuffer 的長(zhǎng)度)茴扁。
Bitmap 大小的計(jì)算
上面是獲取內(nèi)存大小的方法,下面是 Bitmap 占用內(nèi)存的計(jì)算公式:
Bitamp 占用內(nèi)存大小 = 寬度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一個(gè)像素所占的內(nèi)存
上面公式中:
- 寬度像素和高度像素就是 Bitmap 原始的寬度和高度的像素規(guī)格坯苹;
- 一個(gè)像素所占的內(nèi)存與圖片的色彩模式有關(guān)粹湃,這個(gè)色彩模式在 Bitmap 類里面通過(guò)枚舉類 Config 標(biāo)識(shí):
public enum Config {
ALPHA_8 (1),
RGB_565 (3),
@Deprecated
ARGB_4444 (4),
ARGB_8888 (5);
}
- ARGB_8888:每個(gè)像素占四個(gè)字節(jié)为鳄,A偏形、R坠陈、G、B 分量各占8位乌昔,是 Android 的默認(rèn)設(shè)置溺蕉;
- RGB_565:每個(gè)像素占兩個(gè)字節(jié),R分量占5位葫男,G分量占6位鸣剪,B分量占5位西傀;
- ARGB_4444:每個(gè)像素占兩個(gè)字節(jié),A桶癣、R拥褂、G、B分量各占4位牙寞,成像效果比較差饺鹃;
- Alpha_8: 只保存透明度,共8位间雀,1字節(jié)悔详;
- 在 BitmapFactory 的內(nèi)部類 Options 有兩個(gè)成員變量 inDensity 和 inTargetDensity,其中 inDensity 就 Bitmap 的像素密度惹挟,也就是 Bitmap 的成員變量 mDensity茄螃,默認(rèn)是設(shè)備屏幕的像素密度,可以通過(guò) Bitmap#setDensity(int) 設(shè)置连锯,inTargetDensity 是圖片的目標(biāo)像素密度归苍,在加載圖片時(shí)就是 drawable 目錄的像素密度。
在從資源目錄加載圖片時(shí)运怖,這個(gè)時(shí)候調(diào)用的是 BitmapFactory#decodeResource 方法拼弃,內(nèi)部調(diào)用的是 decodeResourceStream 方法:
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options 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);
}
會(huì)根據(jù)設(shè)備屏幕像素密度到對(duì)應(yīng) drawable 目錄去尋找圖片,這個(gè)時(shí)候 inTargetDensity/inDensity = 1摇展,圖片不會(huì)做縮放吻氧,寬度和高度就是圖片原始的像素規(guī)格,如果沒(méi)有找到咏连,會(huì)到其他 drawable 目錄去找盯孙,這個(gè)時(shí)候 drawable 的屏幕像素密度就是 inTargetDensity,會(huì)根據(jù) inTargetDensity/inDensity 的比例對(duì)圖片的寬度和高度進(jìn)行縮放祟滴。
三镀梭、Bitmap 的創(chuàng)建
Bitmap 有一系列的 createXXX 方法用來(lái)創(chuàng)建 Bitmap 對(duì)象,但是我們一般都使用 BitmapFactory 的一系列 decodeXXX 方法來(lái)生成 Bitmap:
public static Bitmap decodeFile(String pathName, Options opts)
public static Bitmap decodeFile(String pathName)
public static Bitmap decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts)
public static Bitmap decodeResource(Resources res, int id, Options opts)
public static Bitmap decodeResource(Resources res, int id)
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)
public static Bitmap decodeByteArray(byte[] data, int offset, int length)
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
public static Bitmap decodeStream(InputStream is)
public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)
public static Bitmap decodeFileDescriptor(FileDescriptor fd)
上面的方法大致可以分為四類:
- 從本地文件中解碼圖片:decodeFile踱启。從本地文件中解壓的原始圖片并不會(huì)對(duì)圖片進(jìn)行縮放报账;
- 從資源文件中解碼圖片:decodeResource。會(huì)根據(jù) inTargetDensity/inDensity 對(duì)圖片進(jìn)行縮放埠偿;
- 從輸入流中解碼圖片:decodeStream透罢。獲取網(wǎng)絡(luò)圖片的時(shí)候使用此方法,其實(shí)從本地文件和資源文件中解壓圖片冠蒋,最終調(diào)用的也是該方法羽圃;
- 從字節(jié)數(shù)組中解碼圖片:decodeByteArray。這個(gè)字節(jié)數(shù)組是輸入流傳化為的字節(jié)數(shù)組抖剿;
- decodeFileDescriptor朽寞。也是從本地文件中解碼圖片识窿,但是并不是通過(guò)流的方式解碼,比 decodeFile 方法省內(nèi)存脑融;
四喻频、Bitmap 壓縮
1.質(zhì)量壓縮
調(diào)用 Bitmap#compress 方法:
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
checkRecycled("Can't compress a recycled bitmap");
// do explicit check before calling the native method
if (stream == null) {
throw new NullPointerException();
}
if (quality < 0 || quality > 100) {
throw new IllegalArgumentException("quality must be 0..100");
}
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
boolean result = nativeCompress(mNativePtr, format.nativeInt,
quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return result;
}
第一個(gè)參數(shù) format 標(biāo)識(shí)壓縮格式,CompressFormat 是一個(gè)枚舉類肘迎,有三個(gè)值甥温,是三種圖片格式 JPEG,PNG妓布,WEBP
public enum CompressFormat {
JPEG (0),
PNG (1),
WEBP (2);
}
- PNG是一種無(wú)損壓縮的圖像存儲(chǔ)格式姻蚓,相同像素寬高的圖像保存為PNG在文件大小上比JPEG往往要大的多,一般是JPEG大小的幾倍左右匣沼;
- JPEG是一種有損壓縮的圖像存儲(chǔ)格式狰挡,不支持alpha通道,由于它具有高壓縮比释涛,在壓縮過(guò)程中把重復(fù)的數(shù)據(jù)和無(wú)關(guān)緊要的數(shù)據(jù)會(huì)選擇性的丟失圆兵,所以如果不需要用到alpha通道,那么大都圖片格式都用該格式枢贿;
- Webp圖片格式是Google推出的一個(gè)支持alpha通道的有損壓縮格式殉农,據(jù)Google官方表明,同質(zhì)量情況下Webp圖像要比JPEG局荚、PNG圖像小25%~45%左右超凳;
第二個(gè)參數(shù) quality 表示壓縮質(zhì)量
這種方式是在保持像素的前提下改變圖片的位深及透明度等,來(lái)達(dá)到壓縮圖片的目的耀态,不會(huì)減少圖片的像素轮傍,它壓縮的是存儲(chǔ)大小,即你放到 disk 上的大小首装,但是解碼成 bitmap 后占的內(nèi)存是不變的创夜。
2.尺寸壓縮
前面的 decodeXXX 方法中有一個(gè) BitmapFactory.Options 類型的參數(shù),解碼圖片時(shí)仙逻,設(shè)置 BitmapFactory.Options 類的 inJustDecodeBounds 屬性為 true驰吓,可以在 Bitmap 不被加載到內(nèi)存的前提下,獲取 Bitmap 的原始寬高系奉。而設(shè)置 BitmapFactory.Options 的 inSampleSize 屬性可以真實(shí)的壓縮 Bitmap 占用的內(nèi)存檬贰,加載更小內(nèi)存的 Bitmap。
設(shè)置 inSampleSize 之后缺亮,Bitmap 的寬翁涤、高都會(huì)縮小 inSampleSize 倍。
inSampleSize 比1小的話會(huì)被當(dāng)做1,任何 inSampleSize 的值會(huì)被取接近2的冪值葵礼。
3.色彩模式壓縮
Bitmap 的色彩模式默認(rèn)為 Bitmap.Config.ARGB_8888号阿,可以通過(guò) BitmapFactory.Options.inPreferredConfig 屬性來(lái)修改解碼圖片的色彩模式,每個(gè)像素占用的字節(jié)減少了鸳粉,寬度和高度像素不變的情況下扔涧,占用的內(nèi)存大小也會(huì)減少;
4.Matrix
Matrix 矩陣變換赁严,可以對(duì) bitmap 進(jìn)行非常多的操作,其中一項(xiàng)是對(duì) bitmap 進(jìn)行等比縮放粉铐,這種方式可以精確的縮放到符合我們預(yù)期的 bitmap 大小疼约,奧代碼如下:
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
Matrix matrix = new Matrix();
float rate = computeScaleRate(bitmapWidth, bitmapHeight);
matrix.postScale(rate, rate);
Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, true);
5.Bitmap#createScaledBitmap
通過(guò) public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)
直接指定圖片壓縮后的寬度和高度
五、Bitmap 與 Drawable 的轉(zhuǎn)換
Bitmap-->Drawable
通過(guò) BitmapDrawable 的構(gòu)造方法:
@Deprecated
public BitmapDrawable(Bitmap bitmap)
public BitmapDrawable(Resources res, Bitmap bitmap)
Drawable-->Bitmap
根據(jù)已有的 Drawable 創(chuàng)建一個(gè)新的 Bitmap
public static Bitmap createBitmap(int width, int height, Config config)