在平常開發中,能夠說和Bitmap低頭不見擡頭見,基本上每一個應用都會直接或間接的用到,而這裏面又涉及到大量的相關知識。 因此這裏把Bitmap的經常使用知識作個梳理,限於經驗和能力,不作太深刻的分析。java
計算Bitmap內存佔用分爲兩種狀況:web
使用BitmapFactory.decodeResource()
加載本地資源文件的方式算法
不管是使用decodeResource(Resources res, int id)
仍是使用decodeResource(Resources res, int id, BitmapFactory.Options opts)
其內存佔用的計算方式都是: width * height * inTargetDensity / inDensity * inTargetDensity / inDensity * 一個像素所佔的內存。
canvas
使用BitmapFactory.decodeResource()
之外的方式,計算方式是: width * height *一個像素所佔的內存。
數組
所用參數解釋一下:bash
- width:圖片的原始像素寬度。
- height:圖片的原始像素高度。
- inTargetDensity:目標設備的屏幕密度,例如一臺手機的屏幕密度是640dp,那麼
inTargetDensity
的值就是640dp。- inDensity:這個值跟這張圖片的放置的目錄有關(好比 hdpi 是240,xxhdpi 是480)。
- 一個像素所佔的內存:使用
Bitmap.Config
來描述一個像素所佔用的內存,Bitmap.Config
有四個取值,分別是:
- ARGB_8888: 每一個像素4字節,每一個通道8位,四通道共32位,圖片質量是最高的,可是佔用的內存也是最大的,是
默認設置
。- RGB_565:共16位,2字節,只存儲RGB值,圖片失真小,沒有透明度,可用於不須要透明度是圖片。
- Alpha_8: 只有A通道,沒有顏色值,即只保存透明度,共8位,1字節,可用於設置遮蓋效果。
- ARGB_4444: ,每一個通道均佔用4位,共16位,2字節,嚴重失真,基本不使用。
getByteCount()方法是在API12加入的,表明存儲Bitmap的色素須要的最少內存。API19開始getAllocationByteCount()方法代替了getByteCount()。併發
API19以後,Bitmap加了一個Api:getAllocationByteCount();表明在內存中爲Bitmap分配的內存大小。函數
public final int getAllocationByteCount() {
if (mBuffer == null) {
//mBuffer表明存儲Bitmap像素數據的字節數組。
return getByteCount();
}
return mBuffer.length;
}
複製代碼
一般咱們能夠利用Bitmap的靜態方法createBitmap()
和BitmapFactory
的decode系列靜態方法建立Bitmap對象。post
主要用於圖片的操做,例如圖片的縮放,裁剪等。 性能
注意
:decodeFile
和decodeResource
其實最終都會調用decodeStream
方法來解析Bitmap
。有一個特別有意思的事情是,在decodeResource
調用decodeStream
以前還會調用decodeResourceStream
這個方法,這個方法主要對Options
進行處理,在獲得opts.inDensity
的屬性前提下,若是沒有對該屬性的設定值,那麼opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
這個值默認爲標準dpi的基值:160。若是沒有設定opts.inTargetDensity
的值時,opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
該值爲當前設備的 densityDpi,這個值是根據你放置在 drawable 下的文件不一樣而不一樣的。因此說 decodeResourceStream 這個方法主要對 opts.inDensity 和 opts.inTargetDensity進行賦值。
儘可能不要使用setImageBitmap
或setImageResource
或BitmapFactory.decodeResource
來設置一張大圖,由於這些函數在完成decode後,最終都是經過java層的createBitmap來完成的,須要消耗更多內存,能夠經過BitmapFactory.decodeStream
方法,建立出一個bitmap,再將其設爲ImageView的 source。
Resource資源加載的方式至關的耗費內存,建議採用經過InputStream ins = resources.openRawResource(resourcesId);
而後使用decodeStream
代替decodeResource
獲取Bitmap。這麼作的好處是:
這兩個接口各有用處,若是對性能要求較高,則應該使用 decodeStream;若是對性能要求不高,且須要 Android 自帶的圖片自適應縮放功能,則可使用 decodeResource。
Drawable newBitmapDrawable = new BitmapDrawable(bitmap);
還能夠從BitmapDrawable中獲取Bitmap對象
Bitmap bitmap = new BitmapDrawable.getBitmap();
複製代碼
BitmapFactory 中的 decodeResource 方法
Resources res = getResources();
Bitmap bmp = BitmapFactory.decodeResource(res, R.drawable.ic_drawable);
複製代碼
將 Drable 對象先轉化成 BitmapDrawable ,而後調用 getBitmap 方法 獲取
Resource res = gerResource();
Drawable drawable = res.getDrawable(R.drawable.ic_drawable);//獲取drawable
BitmapDrawable bd = (BitmapDrawable) drawable;
Bitmap bm = bd.getBitmap();
複製代碼
根據已有的Drawable建立一個新的Bitmap
public static Bitmap drawableToBitmap(Drawable drawable) {
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();
System.out.println("Drawable轉Bitmap");
Bitmap.Config config =
drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
: Bitmap.Config.RGB_565;
Bitmap bitmap = Bitmap.createBitmap(w, h, config);
//注意,下面三行代碼要用到,不然在View或者SurfaceView裏的canvas.drawBitmap會看不到圖
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, w, h);
drawable.draw(canvas);
return bitmap;
}
複製代碼
使用inBitmap可以大大提升內存的利用效率,可是它也有幾個限制條件:
Bitmap複用首選須要其 mIsMutable 屬性爲 true , mIsMutable 的表面意思爲:易變的
在Bitmap中的意思爲: 控制bitmap的setPixel方法可否使用,也就是外界可否修改bitmap的像素。mIsMutable 屬性爲 true 那麼就能夠修改Bitmap的像素數據,這樣也就能夠實現Bitmap對象的複用了。
在SDK 11 -> 18之間,重用的bitmap大小必須是一致的,例如給inBitmap賦值的圖片大小爲100-100,那麼新申請的bitmap必須也爲100-100纔可以被重用。
被複用的Bitmap必須是Mutable,即inMutable的值爲true。違反此限制,不會拋出異常,且會返回新申請內存的Bitmap。
從SDK 19開始,新申請的bitmap大小必須小於或者等於已經賦值過的bitmap大小。違反此限制,將會致使複用失敗,拋出異常IllegalArgumentException(Problem decoding into existing bitmap)
新申請的bitmap與舊的bitmap必須有相同的解碼格式,例如你們都是8888的,若是前面的bitmap是8888,那麼就不能支持4444與565格式的bitmap了,不過能夠經過建立一個包含多種典型可重用bitmap的對象池,這樣後續的bitmap建立都可以找到合適的「模板」去進行重用。
質量壓縮不會改變圖片的像素點,即咱們使用完質量壓縮後,在轉換Bitmap
時佔用內存依舊不會減少。可是能夠減小咱們存儲在本地文件的大小,即放到 disk上的大小。
/**
* 質量壓縮方法,並不能減少加載到內存時所佔用內存的空間,應該是減少的所佔用磁盤的空間
* @param image
* @param compressFormat
* @return
*/
public static Bitmap compressbyQuality(Bitmap image, Bitmap.CompressFormat compressFormat) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//質量壓縮方法,這裏100表示不壓縮,把壓縮後的數據存放到baos中
image.compress(compressFormat, 100, baos);
int quality = 100;
//循環判斷若是壓縮後圖片是否大於100kb,大於繼續壓縮
while ( baos.toByteArray().length / 1024 > 100) {
baos.reset();//重置baos即清空baos
if(quality > 10){
quality -= 20;//每次都減小20
}else {
break;
}
//這裏壓縮options%,把壓縮後的數據存放到baos中
image.compress(Bitmap.CompressFormat.JPEG,quality,baos);
}
//把壓縮後的數據baos存放到ByteArrayInputStream中
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
//把ByteArrayInputStream數據生成圖片
Bitmap bmp = BitmapFactory.decodeStream(isBm, null, options);
return bmp;
}
複製代碼
這個方法主要用在圖片資源自己較大,或者適當地採樣並不會影響視覺效果的條件下,這時候咱們輸出的目標可能相對的較小,對圖片的大小和分辨率都減少。
**
* 採樣率壓縮,這個和矩陣來實現縮放有點相似,可是有一個原則是「大圖小用用採樣,小圖大用用矩陣」。
* 也能夠先用採樣來壓縮圖片,這樣內存小了,但是圖的尺寸也小。若是要是用 Canvas 來繪製這張圖時,再用矩陣放大
* @param image
* @param compressFormat
* @param requestWidth 要求的寬度
* @param requestHeight 要求的長度
* @return
*/
public static Bitmap compressbySample(Bitmap image, Bitmap.CompressFormat compressFormat, int requestWidth, int requestHeight){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//質量壓縮方法,這裏100表示不壓縮,把壓縮後的數據存放到baos中
image.compress(compressFormat,100,baos);
//把壓縮後的數據baos存放到ByteArrayInputStream中
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inPurgeable = true;
//只讀取圖片的頭信息,不去解析真是的位圖
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(isBm,null,options);
options.inSampleSize = calculateInSampleSize(options,requestWidth,requestHeight);
//-------------inBitmap------------------
options.inMutable = true;
try{
Bitmap inBitmap = Bitmap.createBitmap(options.outWidth, options.outHeight, Bitmap.Config.RGB_565);
if (inBitmap != null && canUseForInBitmap(inBitmap, options)) {
options.inBitmap = inBitmap;
}
}catch (OutOfMemoryError e){
options.inBitmap = null;
System.gc();
}
//---------------------------------------
options.inJustDecodeBounds = false;//真正的解析位圖
isBm.reset();
Bitmap compressBitmap;
try{
compressBitmap = BitmapFactory.decodeStream(isBm, null, options);//把ByteArrayInputStream數據生成圖片
}catch (OutOfMemoryError e){
compressBitmap = null;
System.gc();
}
return compressBitmap;
}
/**
* 採樣壓縮比例
* @param options
* @param reqWidth 要求的寬度
* @param reqHeight 要求的長度
* @return
*/
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int originalWidth = options.outWidth;
int originalHeight = options.outHeight;
int inSampleSize = 1;
if (originalHeight > reqHeight || originalWidth > reqHeight){
// 計算出實際寬高和目標寬高的比率
final int heightRatio = Math.round((float) originalHeight / (float) reqHeight);
final int widthRatio = Math.round((float) originalWidth / (float) reqWidth);
// 選擇寬和高中最小的比率做爲inSampleSize的值,這樣能夠保證最終圖片的寬和高
// 必定都會大於等於目標的寬和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
複製代碼
前面咱們採用了採樣壓縮,Bitmap 所佔用的內存是小了,但是圖的尺寸也小了。當咱們須要尺寸較大時該怎麼辦?咱們要用用 Canvas 繪製怎麼辦?固然能夠用矩陣(Matrix)
/**
* 矩陣縮放圖片
* @param sourceBitmap
* @param width 要縮放到的寬度
* @param height 要縮放到的長度
* @return
*/
private Bitmap getScaleBitmap(Bitmap sourceBitmap,float width,float height){
Bitmap scaleBitmap;
//定義矩陣對象
Matrix matrix = new Matrix();
float scale_x = width/sourceBitmap.getWidth();
float scale_y = height/sourceBitmap.getHeight();
matrix.postScale(scale_x,scale_y);
try {
scaleBitmap = Bitmap.createBitmap(sourceBitmap,0,0,sourceBitmap.getWidth(),sourceBitmap.getHeight(),matrix,true);
}catch (OutOfMemoryError e){
scaleBitmap = null;
System.gc();
}
return scaleBitmap;
}
複製代碼