1. 程式人生 > >bitmap--Bitmap詳解與Bitmap的記憶體優化

bitmap--Bitmap詳解與Bitmap的記憶體優化

一、Bitmap:

Bitmap是Android系統中的影象處理的最重要類之一。用它可以獲取影象檔案資訊,進行影象剪下、旋轉、縮放等操作,並可以指定格式儲存影象檔案。
常用方法:

  • public void recycle()  // 回收點陣圖佔用的記憶體空間,把點陣圖標記為Dead
  • public final boolean isRecycled()  //判斷點陣圖記憶體是否已釋放
  • public final int getWidth() //獲取點陣圖的寬度
  • public final int getHeight() //獲取點陣圖的高度
  • public final boolean isMutable() //圖片是否可修改
  • public int getScaledWidth(Canvas canvas) //獲取指定密度轉換後的影象的寬度
  • public int getScaledHeight(Canvas canvas) //獲取指定密度轉換後的影象的高度
  • public boolean compress(CompressFormat format, int quality, OutputStream stream) //按指定的圖片格式以及畫質,將圖片轉換為輸出流。
    format:壓縮影象的格式,如Bitmap.CompressFormat.PNG或Bitmap.CompressFormat.JPEG
    quality:畫質,0-100.0表示最低畫質壓縮,100以最高畫質壓縮。對於PNG等無損格式的圖片,會忽略此項設定。
    stream: OutputStream中寫入壓縮資料。
    return: 是否成功壓縮到指定的流。
  • public static Bitmap createBitmap(Bitmap src)  //以src為原圖生成不可變得新影象
  • public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) //以src為原圖,建立新的影象,指定新影象的高寬以及是否可變。
  • public static Bitmap createBitmap(int width, int height, Config config) //建立指定格式、大小的點陣圖
  • public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) //以source為原圖,建立新的圖片,指定起始座標以及新影象的高寬。

二、BitmapFactory工廠類:

Option 引數類:

  • public boolean inJustDecodeBounds //如果設定為true,不獲取圖片,不分配記憶體,但會返回圖片的高度寬度資訊。
    如果將這個值置為true,那麼在解碼的時候將不會返回bitmap,只會返回這個bitmap的尺寸。這個屬性的目的是,如果你只想知道一個bitmap的尺寸,但又不想將其載入到記憶體時。這是一個非常有用的屬性。
  • public int inSampleSize //圖片縮放的倍數
    這個值是一個int,當它小於1的時候,將會被當做1處理,如果大於1,那麼就會按照比例(1 / inSampleSize)縮小bitmap的寬和高、降低解析度,大於1時這個值將會被處置為2的倍數。例如,width=100,height=100,inSampleSize=2,那麼就會將bitmap處理為,width=50,height=50,寬高降為1 / 2,畫素數降為1 / 4。
  • public int outWidth //獲取圖片的寬度值
  • public int outHeight //獲取圖片的高度值
    表示這個Bitmap的寬和高,一般和inJustDecodeBounds一起使用來獲得Bitmap的寬高,但是不載入到記憶體。
  • public int inDensity //用於點陣圖的畫素壓縮比
  • public int inTargetDensity //用於目標點陣圖的畫素壓縮比(要生成的點陣圖)
  • public byte[] inTempStorage  //建立臨時檔案,將圖片儲存
  • public boolean inScaled //設定為true時進行圖片壓縮,從inDensity到inTargetDensity
  • public boolean inDither  //如果為true,解碼器嘗試抖動解碼
  • public Bitmap.Config inPreferredConfig  //設定解碼器
    這個值是設定色彩模式,預設值是ARGB_8888,在這個模式下,一個畫素點佔用4bytes空間,一般對透明度不做要求的話,一般採用RGB_565模式,這個模式下一個畫素點佔用2bytes。
  • public String outMimeType  //設定解碼影象
  • public boolean inPurgeable //當儲存Pixel的記憶體空間在系統記憶體不足時是否可以被回收
  • public boolean inInputShareable  //inPurgeable為true情況下才生效,是否可以共享一個InputStream
  • public boolean inPreferQualityOverSpeed  //為true則優先保證Bitmap質量其次是解碼速度
  • public boolean inMutable  //配置Bitmap是否可以更改,比如:在Bitmap上隔幾個畫素加一條線段
  • public int inScreenDensity  //當前螢幕的畫素密度

工廠方法:

  • public static Bitmap decodeFile(String pathName, Options opts)  //從檔案讀取圖片
  • public static Bitmap decodeFile(String pathName)
  • public static Bitmap decodeStream(InputStream is)  //從輸入流讀取圖片
  • public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
  • public static Bitmap decodeResource(Resources res, int id)  //從資原始檔讀取圖片
  • public static Bitmap decodeResource(Resources res, int id, Options opts)
  • public static Bitmap decodeByteArray(byte[] data, int offset, int length)  //從陣列讀取圖片
  • public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)
  • public static Bitmap decodeFileDescriptor(FileDescriptor fd) //從檔案讀取檔案 與decodeFile不同的是這個直接呼叫JNI函式進行讀取 效率比較高
  • public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)

** Bitmap.Config inPreferredConfig :**
列舉變數 (點陣圖位數越高代表其可以儲存的顏色資訊越多,影象越逼真,佔用記憶體越大)

  • public static final Bitmap.Config ALPHA_8  //代表8位Alpha點陣圖 每個畫素佔用1byte記憶體
  • public static final Bitmap.Config ARGB_4444  //代表16位ARGB點陣圖 每個畫素佔用2byte記憶體
  • public static final Bitmap.Config ARGB_8888  //代表32位ARGB點陣圖 每個畫素佔用4byte記憶體
  • public static final Bitmap.Config RGB_565  //代表8位RGB點陣圖 每個畫素佔用2byte記憶體

Android中一張圖片(BitMap)佔用的記憶體主要和以下幾個因數有關:圖片長度,圖片寬度,單位畫素佔用的位元組數。一張圖片(BitMap)佔用的記憶體=圖片長度*圖片寬度*單位畫素佔用的位元組數。


三、Bitmap載入方式

Bitmap的載入方式有Resource資源載入、本地(SDcard)載入、網路載入等載入方式。

1. 從本地(SDcard)檔案讀取

  • 方式一
/**
     * 獲取縮放後的本地圖片
     *
     * @param filePath 檔案路徑
     * @param width    寬
     * @param height   高
     * @return */ public static Bitmap readBitmapFromFile(String filePath, int width, int height) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(filePath, options); float srcWidth = options.outWidth; float srcHeight = options.outHeight; int inSampleSize = 1; if (srcHeight > height || srcWidth > width) { if (srcWidth > srcHeight) { inSampleSize = Math.round(srcHeight / height); } else { inSampleSize = Math.round(srcWidth / width); } } options.inJustDecodeBounds = false; options.inSampleSize = inSampleSize; return BitmapFactory.decodeFile(filePath, options); } 
  • 方式二 (效率高於方式一)
/**
     * 獲取縮放後的本地圖片
     *
     * @param filePath 檔案路徑
     * @param width    寬
     * @param height   高
     * @return */ public static Bitmap readBitmapFromFileDescriptor(String filePath, int width, int height) { try { FileInputStream fis = new FileInputStream(filePath); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options); float srcWidth = options.outWidth; float srcHeight = options.outHeight; int inSampleSize = 1; if (srcHeight > height || srcWidth > width) { if (srcWidth > srcHeight) { inSampleSize = Math.round(srcHeight / height); } else { inSampleSize = Math.round(srcWidth / width); } } options.inJustDecodeBounds = false; options.inSampleSize = inSampleSize; return BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options); } catch (Exception ex) { } return null; } 

2. 從輸入流中讀取檔案(網路載入)

/**
     * 獲取縮放後的本地圖片
     *
     * @param ins    輸入流
     * @param width  寬
     * @param height 高
     * @return */ public static Bitmap readBitmapFromInputStream(InputStream ins, int width, int height) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(ins, null, options); float srcWidth = options.outWidth; float srcHeight = options.outHeight; int inSampleSize = 1; if (srcHeight > height || srcWidth > width) { if (srcWidth > srcHeight) { inSampleSize = Math.round(srcHeight / height); } else { inSampleSize = Math.round(srcWidth / width); } } options.inJustDecodeBounds = false; options.inSampleSize = inSampleSize; return BitmapFactory.decodeStream(ins, null, options); } 

3.Resource資源載入

  1. Res資源載入方式:
public static Bitmap readBitmapFromResource(Resources resources, int resourcesId, int width, int height) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(resources, resourcesId, options); float srcWidth = options.outWidth; float srcHeight = options.outHeight; int inSampleSize = 1; if (srcHeight > height || srcWidth > width) { if (srcWidth > srcHeight) { inSampleSize = Math.round(srcHeight / height); } else { inSampleSize = Math.round(srcWidth / width); } } options.inJustDecodeBounds = false; options.inSampleSize = inSampleSize; return BitmapFactory.decodeResource(resources, resourcesId, options); } 

此種方式相當的耗費記憶體 建議採用decodeStream代替decodeResource 可以如下形式:

public static Bitmap readBitmapFromResource(Resources resources, int resourcesId, int width, int height) { InputStream ins = resources.openRawResource(resourcesId); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(ins, null, options); float srcWidth = options.outWidth; float srcHeight = options.outHeight; int inSampleSize = 1; if (srcHeight > height || srcWidth > width) { if (srcWidth > srcHeight) { inSampleSize = Math.round(srcHeight / height); } else { inSampleSize = Math.round(srcWidth / width); } } options.inJustDecodeBounds = false; options.inSampleSize = inSampleSize; return BitmapFactory.decodeStream(ins, null, options); } 

BitmapFactory.decodeResource 載入的圖片可能會經過縮放,該縮放目前是放在 java 層做的,效率比較低,而且需要消耗 java 層的記憶體。因此,如果大量使用該介面載入圖片,容易導致OOM錯誤
BitmapFactory.decodeStream 不會對所載入的圖片進行縮放,相比之下佔用記憶體少,效率更高。
這兩個介面各有用處,如果對效能要求較高,則應該使用 decodeStream;如果對效能要求不高,且需要 Android 自帶的圖片自適應縮放功能,則可以使用 decodeResource。

  1. Assets資源載入方式:
/**
     * 獲取縮放後的本地圖片
     *
     * @param filePath 檔案路徑,即檔名稱
     * @return
     */
    public static Bitmap readBitmapFromAssetsFile(Context context, String filePath) { Bitmap image = null; AssetManager am = context.getResources().getAssets(); try { InputStream is = am.open(filePath); image = BitmapFactory.decodeStream(is); is.close(); } catch (IOException e) { e.printStackTrace(); } return image; } 

4.從二進位制資料讀取圖片

public static Bitmap readBitmapFromByteArray(byte[] data, int width, int height) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(data, 0, data.length, options); float srcWidth = options.outWidth; float srcHeight = options.outHeight; int inSampleSize = 1; if (srcHeight > height || srcWidth > width) { if (srcWidth > srcHeight) { inSampleSize = Math.round(srcHeight / height); } else { inSampleSize = Math.round(srcWidth / width); } } options.inJustDecodeBounds = false; options.inSampleSize = inSampleSize; return BitmapFactory.decodeByteArray(data, 0, data.length, options); } 

四、Bitmap | Drawable | InputStream | Byte[ ] 之間進行轉換

  1. Drawable轉化成Bitmap
public static Bitmap drawableToBitmap(Drawable drawable) { Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); drawable.draw(canvas); return bitmap; } 

drawable的獲取方式:Drawable drawable = getResources().getDrawable(R.drawable.ic_launcher);

  1. Bitmap轉換成Drawable
public static Drawable bitmapToDrawable(Resources resources, Bitmap bm) { Drawable drawable = new BitmapDrawable(resources, bm); return drawable; } 
  1. Bitmap轉換成byte[]
public byte[] bitmap2Bytes(Bitmap bm) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
        return baos.toByteArray(); } 
  1. byte[]轉換成Bitmap
    Bitmap bitmap = BitmapFactory.decodeByteArray(byte, 0, b.length);
  2. InputStream轉換成Bitmap
InputStream is  = getResources().openRawResource(id);  
Bitmap bitmap = BitmaoFactory.decodeStream(is);  
  1. InputStream轉換成byte[]
InputStream is = getResources().openRawResource(id);//也可以通過其他方式接收一個InputStream物件  
ByteArrayOutputStream baos = new ByteArrayOutputStream();    
byte[] b = new byte[1024*2]; int len = 0; while ((len = is.read(b, 0, b.length)) != -1) { baos.write(b, 0, len); baos.flush(); } byte[] bytes = baos.toByteArray(); 

五、Bitmap簡單操作

  1. ** 將Bitmap儲存為本地檔案:**
public static void writeBitmapToFile(String filePath, Bitmap b, int quality) { try { File desFile = new File(filePath); FileOutputStream fos = new FileOutputStream(desFile); BufferedOutputStream bos = new BufferedOutputStream(fos); b.compress(Bitmap.CompressFormat.JPEG, quality, bos); bos.flush(); bos.close(); } catch (IOException e) { e.printStackTrace(); } } 
  1. 圖片壓縮:
private static Bitmap compressImage(Bitmap image) { if (image == null) { return null; } ByteArrayOutputStream baos = null; try { baos = new ByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.JPEG, 100, baos); byte[] bytes = baos.toByteArray(); ByteArrayInputStream isBm = new ByteArrayInputStream(bytes); Bitmap bitmap = BitmapFactory.decodeStream(isBm); return bitmap; } catch (OutOfMemoryError e) { } finally { try { if (baos != null) { baos.close(); } } catch (IOException e) { } } return null; } 
  1. 圖片縮放:
/**
     * 根據scale生成一張圖片
     *
     * @param bitmap
     * @param scale  等比縮放值
     * @return
     */
    public static Bitmap bitmapScale(Bitmap bitmap, float scale) { Matrix matrix = new Matrix(); matrix.postScale(scale, scale); // 長和寬放大縮小的比例 Bitmap resizeBmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); return resizeBmp; } 
  1. 獲取圖片旋轉角度:
/**
     * 讀取照片exif資訊中的旋轉角度
     *
     * @param path 照片路徑
     * @return角度
     */
    private static int readPictureDegree(String path) { if (TextUtils.isEmpty(path)) { return 0; } int degree = 0; try { ExifInterface exifInterface = new ExifInterface(path); int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_90: degree = 90; break; case ExifInterface.ORIENTATION_ROTATE_180: degree = 180; break; case ExifInterface.ORIENTATION_ROTATE_270: degree = 270; break; } } catch (Exception e) { } return degree; } 
  1. 設定圖片旋轉角度
private static Bitmap rotateBitmap(Bitmap b, float rotateDegree) { if (b == null) { return null; } Matrix matrix = new Matrix(); matrix.postRotate(rotateDegree); Bitmap rotaBitmap = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, true); return rotaBitmap; } 
  1. 通過圖片id獲得Bitmap:
    Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
  2. 通過 assest 獲取 獲得Drawable bitmap:
InputStream in = this.getAssets().open("ic_launcher");
Drawable da = Drawable.createFromStream(in, null); Bitmap mm = BitmapFactory.decodeStream(in); 
  1. 通過 sdcard 獲得 bitmap
    Bitmap bit = BitmapFactory.decodeFile("/sdcard/android.jpg");
  2. ** view轉Bitmap**
public static Bitmap convertViewToBitmap(View view, int bitmapWidth, int bitmapHeight){ Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); view.draw(new Canvas(bitmap)); return bitmap; } 
  1. 將控制元件轉換為bitmap
public static Bitmap convertViewToBitMap(View view){ // 開啟影象快取 view.setDrawingCacheEnabled(true); // 必須呼叫measure和layout方法才能成功儲存可視元件的截圖到png影象檔案 // 測量View大小 view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); // 傳送位置和尺寸到View及其所有的子View view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); // 獲得可視元件的截圖 Bitmap bitmap = view.getDrawingCache(); return bitmap; } 
public static Bitmap getBitmapFromView(View view){ Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(returnedBitmap); Drawable bgDrawable = view.getBackground(); if (bgDrawable != null) bgDrawable.draw(canvas); else canvas.drawColor(Color.WHITE); view.draw(canvas); return returnedBitmap; } 
  1. ** 放大縮小圖片**
public static Bitmap zoomBitmap(Bitmap bitmap,int w,int h){ int width = bitmap.getWidth(); int height = bitmap.getHeight(); Matrix matrix = new Matrix(); float scaleWidht = ((float)w / width); float scaleHeight = ((float)h / height); matrix.postScale(scaleWidht, scaleHeight); Bitmap newbmp = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); return newbmp; } 
  1. 獲得圓角圖片的方法
public static Bitmap getRoundedCornerBitmap(Bitmap bitmap,float roundPx){ Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap .getHeight(), Config.ARGB_8888); Canvas canvas = new Canvas(output); final int color = 0xff424242; final Paint paint = new Paint(); final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); final RectF rectF = new RectF(rect); paint.setAntiAlias(true); canvas.drawARGB(0, 0, 0, 0); paint.setColor(color); canvas.drawRoundRect(rectF, roundPx, roundPx, paint); paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); canvas.drawBitmap(bitmap, rect, rect, paint); return output; } 
  1. **對 bitmap 進行裁剪 **
public Bitmap  bitmapClip(Context context , int id , int x , int y){ Bitmap map = BitmapFactory.decodeResource(context.getResources(), id); map = Bitmap.createBitmap(map, x, y, 120, 120); return map; } 

六、Bitmap的記憶體優化詳解

在Android應用裡,最耗費記憶體的就是圖片資源。而且在Android系統中,讀取點陣圖Bitmap時,分給虛擬機器中的圖片的堆疊大小隻有8M,如果超出了,就會出現OutOfMemory異常。所以,對於圖片的記憶體優化,是Android應用開發中比較重要的內容。

1. 要及時回收Bitmap的記憶體

Bitmap類有一個方法recycle(),從方法名可以看出意思是回收。這裡就有疑問了,Android系統有自己的垃圾回收機制,可以不定期的回收掉不使用的記憶體空間,當然也包括Bitmap的空間。那為什麼還需要這個方法呢?
Bitmap類的構造方法都是私有的,所以開發者不能直接new出一個Bitmap物件,只能通過BitmapFactory類的各種靜態方法來例項化一個Bitmap。仔細檢視BitmapFactory的原始碼可以看到,生成Bitmap物件最終都是通過JNI呼叫方式實現的。所以,載入Bitmap到記憶體裡以後,是包含兩部分記憶體區域的。簡單的說,一部分是Java部分的,一部分是C部分的。這個Bitmap物件是由Java部分分配的,不用的時候系統就會自動回收了,但是那個對應的C可用的記憶體區域,虛擬機器是不能直接回收的,這個只能呼叫底層的功能釋放。所以需要呼叫recycle()方法來釋放C部分的記憶體。從Bitmap類的原始碼也可以看到,recycle()方法裡也的確是呼叫了JNI方法了的。
那如果不呼叫recycle(),是否就一定存在記憶體洩露呢?也不是的。Android的每個應用都執行在獨立的程序裡,有著獨立的記憶體,如果整個程序被應用本身或者系統殺死了,記憶體也就都被釋放掉了,當然也包括C部分的記憶體。
Android對於程序的管理是非常複雜的。簡單的說,Android系統的程序分為幾個級別,系統會在記憶體不足的情況下殺死一些低優先順序的程序,以提供給其它程序充足的記憶體空間。在實際專案開發過程中,有的開發者會在退出程式的時候使用Process.killProcess(Process.myPid())的方式將自己的程序殺死,但是有的應用僅僅會使用呼叫Activity.finish()方法的方式關閉掉所有的Activity。
釋放Bitmap的示例程式碼片段:

// 先判斷是否已經回收
if(bitmap != null && !bitmap.isRecycled()){ 
        // 回收並且置為null
        bitmap.recycle(); 
        bitmap = null; } System.gc(); 

從上面的程式碼可以看到,bitmap.recycle()方法用於回收該Bitmap所佔用的記憶體,接著將bitmap置空,最後使用System.gc()呼叫一下系統的垃圾回收器進行回收,可以通知垃圾回收器儘快進行回收。這裡需要注意的是,呼叫System.gc()並不能保證立即開始進行回收過程,而只是為了加快回收的到來。
如何呼叫recycle()方法進行回收已經瞭解了,那什麼時候釋放Bitmap的記憶體比較合適呢?一般來說,如果程式碼已經不再需要使用Bitmap物件了,就可以釋放了。釋放記憶體以後,就不能再使用該Bitmap物件了,如果再次使用,就會丟擲異常。所以一定要保證不再使用的時候釋放。比如,如果是在某個Activity中使用Bitmap,就可以在Activity的onStop()或者onDestroy()方法中進行回收。

2.捕獲異常

為了避免應用在分配Bitmap記憶體的時候出現OutOfMemory異常以後Crash掉,需要特別注意例項化Bitmap部分的程式碼。通常,在例項化Bitmap的程式碼中,一定要對OutOfMemory異常進行捕獲。

Bitmap bitmap = null;
try {
    // 例項化Bitmap
    bitmap = BitmapFactory.decodeFile(path);
} catch (OutOfMemoryError e) {
    // } if (bitmap == null) { // 如果例項化失敗 返回預設的Bitmap物件 return defaultBitmapMap; } 

這裡對初始化Bitmap物件過程中可能發生的OutOfMemory異常進行了捕獲。如果發生了OutOfMemory異常,應用不會崩潰,而是得到了一個預設的Bitmap圖。
注意:很多開發者會習慣性的在程式碼中直接捕獲Exception。但是對於OutOfMemoryError來說,這樣做是捕獲不到的。因為OutOfMemoryError是一種Error,而不是Exception。在此僅僅做一下提醒,避免寫錯程式碼而捕獲不到OutOfMemoryError。

3.快取通用的Bitmap物件

有時候,可能需要在一個Activity裡多次用到同一張圖片。比如一個Activity會展示一些使用者的頭像列表,而如果使用者沒有設定頭像的話,則會顯示一個預設頭像,而這個頭像是位於應用程式本身的資原始檔中的。
如果有類似上面的場景,就可以對同一Bitmap進行快取。如果不進行快取,儘管看到的是同一張圖片檔案,但是使用BitmapFactory類的方法來例項化出來的Bitmap,是不同的Bitmap物件。快取可以避免新建多個Bitmap物件,避免記憶體的浪費。
在Android應用開發過程中,也會經常使用快取的技術。這裡所說的快取有兩個級別,一個是硬碟快取,一個是記憶體快取。比如說,在開發網路應用過程中,可以將一些從網路上獲取的資料儲存到SD卡中,下次直接從SD卡讀取,而不從網路中讀取,從而節省網路流量。這種方式就是硬碟快取。再比如,應用程式經常會使用同一物件,也可以放到記憶體中快取起來,需要的時候直接從記憶體中讀取。這種方式就是記憶體快取。

4.壓縮圖片

如果圖片畫素過大,使用BitmapFactory類的方法例項化Bitmap的過程中,需要大於8M的記憶體空間,就必定會發生OutOfMemory異常。這個時候該如何處理呢?如果有這種情況,則可以將圖片縮小,以減少載入圖片過程中的記憶體的使用,避免異常發生。
使用BitmapFactory.Options設定inSampleSize就可以縮小圖片。屬性值inSampleSize表示縮圖大小為原始圖片大小的幾分之一。即如果這個值為2,則取出的縮圖的寬和高都是原始圖片的1/2,圖片的大小就為原始大小的1/4。
如果知道圖片的畫素過大,就可以對其進行縮小。那麼如何才知道圖片過大呢?
使用BitmapFactory.Options設定inJustDecodeBounds為true後,再使用decodeFile()等方法,並不會真正的分配空間,即解碼出來的Bitmap為null,但是可計算出原始圖片的寬度和高度,即options.outWidth和options.outHeight。通過這兩個值,就可以知道圖片是否過大了。

BitmapFactory.Options opts = new BitmapFactory.Options();
    // 設定inJustDecodeBounds為true
    opts.inJustDecodeBounds = true;
    // 使用decodeFile方法得到圖片的寬和高
    BitmapFactory.decodeFile(path, opts);
    // 打印出圖片的寬和高 Log.d("example", opts.outWidth + "," + opts.outHeight); 

在實際專案中,可以利用上面的程式碼,先獲取圖片真實的寬度和高度,然後判斷是否需要跑縮小。如果不需要縮小,設定inSampleSize的值為1。如果需要縮小,則動態計算並設定inSampleSize的值,對圖片進行縮小。需要注意的是,在下次使用BitmapFactory的decodeFile()等方法例項化Bitmap物件前,別忘記將opts.inJustDecodeBound設定回false。否則獲取的bitmap物件還是null。
注意:如果程式的圖片的來源都是程式包中的資源,或者是自己伺服器上的圖片,圖片的大小是開發者可以調整的,那麼一般來說,就只需要注意使用的圖片不要過大,並且注意程式碼的質量,及時回收Bitmap物件,就能避免OutOfMemory異常的發生。
如果程式的圖片來自外界,這個時候就特別需要注意OutOfMemory的發生。一個是如果載入的圖片比較大,就需要先縮小;另一個是一定要捕獲異常,避免程式Crash。



作者:閒庭CC
連結:https://www.jianshu.com/p/8206dd8b6d8b
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。