內存優(yōu)化(二):內存大戶偏塞,Bitmap內存優(yōu)化

一油宜、Bitmap:

Bitmap是Android系統(tǒng)中的圖像處理的最重要類之一。用它可以獲取圖像文件信息,進行圖像剪切、旋轉剥槐、縮放等操作几于,并可以指定格式保存圖像文件。
常用方法:

  • 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為原圖,創(chuàng)建新的圖像,指定新圖像的高寬以及是否可變。
  • public static Bitmap createBitmap(int width, int height, Config config) //創(chuàng)建指定格式、大小的位圖
  • public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) //以source為原圖户敬,創(chuàng)建新的圖片呢堰,指定起始坐標以及新圖像的高寬问麸。

二席舍、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  //創(chuàng)建臨時文件怎棱,將圖片存儲
  • 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的內存空間在系統(tǒng)內存不足時是否可以被回收
  • public boolean inInputShareable  //inPurgeable為true情況下才生效,是否可以共享一個InputStream
  • public boolean inPreferQualityOverSpeed  //為true則優(yōu)先保證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)占用的內存主要和以下幾個因數有關:圖片長度咐扭,圖片寬度芭挽,單位像素占用的字節(jié)數。一張圖片(BitMap)占用的內存=圖片長度圖片寬度單位像素占用的字節(jié)數蝗肪。


三袜爪、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));
    // 發(fā)送位置和尺寸到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的內存優(yōu)化詳解

在Android應用里颁股,最耗費內存的就是圖片資源。而且在Android系統(tǒng)中傻丝,讀取位圖Bitmap時甘有,分給虛擬機中的圖片的堆棧大小只有8M,如果超出了葡缰,就會出現OutOfMemory異常亏掀。所以,對于圖片的內存優(yōu)化泛释,是Android應用開發(fā)中比較重要的內容滤愕。

1. 要及時回收Bitmap的內存

Bitmap類有一個方法recycle(),從方法名可以看出意思是回收怜校。這里就有疑問了间影,Android系統(tǒng)有自己的垃圾回收機制,可以不定期的回收掉不使用的內存空間茄茁,當然也包括Bitmap的空間魂贬。那為什么還需要這個方法呢?
Bitmap類的構造方法都是私有的裙顽,所以開發(fā)者不能直接new出一個Bitmap對象付燥,只能通過BitmapFactory類的各種靜態(tài)方法來實例化一個Bitmap。仔細查看BitmapFactory的源代碼可以看到愈犹,生成Bitmap對象最終都是通過JNI調用方式實現的键科。所以,加載Bitmap到內存里以后,是包含兩部分內存區(qū)域的萝嘁。簡單的說梆掸,一部分是Java部分的,一部分是C部分的牙言。這個Bitmap對象是由Java部分分配的酸钦,不用的時候系統(tǒng)就會自動回收了,但是那個對應的C可用的內存區(qū)域咱枉,虛擬機是不能直接回收的卑硫,這個只能調用底層的功能釋放宰睡。所以需要調用recycle()方法來釋放C部分的內存舆吮。從Bitmap類的源代碼也可以看到验夯,recycle()方法里也的確是調用了JNI方法了的养距。
那如果不調用recycle(),是否就一定存在內存泄露呢访惜?也不是的椿疗。Android的每個應用都運行在獨立的進程里该肴,有著獨立的內存葛假,如果整個進程被應用本身或者系統(tǒng)殺死了障陶,內存也就都被釋放掉了,當然也包括C部分的內存聊训。
Android對于進程的管理是非常復雜的抱究。簡單的說,Android系統(tǒng)的進程分為幾個級別带斑,系統(tǒng)會在內存不足的情況下殺死一些低優(yōu)先級的進程鼓寺,以提供給其它進程充足的內存空間。在實際項目開發(fā)過程中勋磕,有的開發(fā)者會在退出程序的時候使用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()調用一下系統(tǒng)的垃圾回收器進行回收杂彭,可以通知垃圾回收器盡快進行回收墓毒。這里需要注意的是吓揪,調用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對象過程中可能發(fā)生的OutOfMemory異常進行了捕獲。如果發(fā)生了OutOfMemory異常饲常,應用不會崩潰蹲堂,而是得到了一個默認的Bitmap圖。
注意:很多開發(fā)者會習慣性的在代碼中直接捕獲Exception不皆。但是對于OutOfMemoryError來說贯城,這樣做是捕獲不到的。因為OutOfMemoryError是一種Error霹娄,而不是Exception能犯。在此僅僅做一下提醒,避免寫錯代碼而捕獲不到OutOfMemoryError犬耻。

3.緩存通用的Bitmap對象

有時候踩晶,可能需要在一個Activity里多次用到同一張圖片。比如一個Activity會展示一些用戶的頭像列表枕磁,而如果用戶沒有設置頭像的話渡蜻,則會顯示一個默認頭像,而這個頭像是位于應用程序本身的資源文件中的计济。
如果有類似上面的場景茸苇,就可以對同一Bitmap進行緩存。如果不進行緩存沦寂,盡管看到的是同一張圖片文件学密,但是使用BitmapFactory類的方法來實例化出來的Bitmap,是不同的Bitmap對象传藏。緩存可以避免新建多個Bitmap對象腻暮,避免內存的浪費彤守。
在Android應用開發(fā)過程中,也會經常使用緩存的技術哭靖。這里所說的緩存有兩個級別具垫,一個是硬盤緩存,一個是內存緩存试幽。比如說筝蚕,在開發(fā)網絡應用過程中,可以將一些從網絡上獲取的數據保存到SD卡中铺坞,下次直接從SD卡讀取饰及,而不從網絡中讀取,從而節(jié)省網絡流量康震。這種方式就是硬盤緩存燎含。再比如,應用程序經常會使用同一對象腿短,也可以放到內存中緩存起來屏箍,需要的時候直接從內存中讀取。這種方式就是內存緩存橘忱。

4.壓縮圖片

如果圖片像素過大赴魁,使用BitmapFactory類的方法實例化Bitmap的過程中,需要大于8M的內存空間钝诚,就必定會發(fā)生OutOfMemory異常颖御。這個時候該如何處理呢?如果有這種情況凝颇,則可以將圖片縮小潘拱,以減少載入圖片過程中的內存的使用,避免異常發(fā)生拧略。
使用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幻锁。如果需要縮小,則動態(tài)計算并設置inSampleSize的值边臼,對圖片進行縮小哄尔。需要注意的是,在下次使用BitmapFactory的decodeFile()等方法實例化Bitmap對象前柠并,別忘記將opts.inJustDecodeBound設置回false岭接。否則獲取的bitmap對象還是null。
注意:如果程序的圖片的來源都是程序包中的資源臼予,或者是自己服務器上的圖片鸣戴,圖片的大小是開發(fā)者可以調整的,那么一般來說粘拾,就只需要注意使用的圖片不要過大窄锅,并且注意代碼的質量,及時回收Bitmap對象缰雇,就能避免OutOfMemory異常的發(fā)生入偷。
如果程序的圖片來自外界,這個時候就特別需要注意OutOfMemory的發(fā)生械哟。一個是如果載入的圖片比較大盯串,就需要先縮小戒良;另一個是一定要捕獲異常体捏,避免程序Crash。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末糯崎,一起剝皮案震驚了整個濱河市几缭,隨后出現的幾起案子,更是在濱河造成了極大的恐慌沃呢,老刑警劉巖年栓,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異薄霜,居然都是意外死亡某抓,警方通過查閱死者的電腦和手機纸兔,發(fā)現死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來否副,“玉大人汉矿,你說我怎么就攤上這事”纲鳎” “怎么了洲拇?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長曲尸。 經常有香客問我赋续,道長,這世上最難降的妖魔是什么另患? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任纽乱,我火速辦了婚禮,結果婚禮上昆箕,老公的妹妹穿的比我還像新娘迫淹。我一直安慰自己,他們只是感情好为严,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布敛熬。 她就那樣靜靜地躺著,像睡著了一般第股。 火紅的嫁衣襯著肌膚如雪应民。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天夕吻,我揣著相機與錄音诲锹,去河邊找鬼。 笑死涉馅,一個胖子當著我的面吹牛归园,可吹牛的內容都是我干的。 我是一名探鬼主播稚矿,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼庸诱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了晤揣?” 一聲冷哼從身側響起桥爽,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昧识,沒想到半個月后钠四,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡跪楞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年缀去,在試婚紗的時候發(fā)現自己被綠了侣灶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡缕碎,死狀恐怖褥影,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情阎曹,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布煞檩,位于F島的核電站处嫌,受9級特大地震影響,放射性物質發(fā)生泄漏斟湃。R本人自食惡果不足惜熏迹,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凝赛。 院中可真熱鬧注暗,春花似錦、人聲如沸墓猎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽毙沾。三九已至骗卜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間左胞,已是汗流浹背寇仓。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留烤宙,地道東北人遍烦。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像躺枕,于是被迫代替她去往敵國和親服猪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內容

  • 2021期待與你一起共事拐云,點擊查看崗位[http://www.reibang.com/p/6f4d67fa406...
    閑庭閱讀 16,609評論 0 75
  • Android 優(yōu)化目錄 優(yōu)化的意義 減少 OOM蔓姚,提高應用穩(wěn)定性。 減少卡頓慨丐,提高應用流暢度坡脐。 減少內存占用,提...
    三流之路閱讀 1,324評論 0 1
  • 為什么要進行內存優(yōu)化:APP運行內存限制房揭,OOM導致APP崩潰备闲。APP性能:流暢性晌端、響應速度和用戶體驗,因為GC回...
    ArcherZang閱讀 877評論 0 0
  • 目錄介紹 01.如何計算Bitmap占用內存1.1 如何計算占用內存1.2 上面方法計算內存對嗎1.3 一個像素占...
    楊充211閱讀 4,039評論 1 9
  • 推薦指數: 6.0 書籍主旨關鍵詞:特權恬砂、焦點咧纠、注意力、語言聯(lián)想泻骤、情景聯(lián)想 觀點: 1.統(tǒng)計學現在叫數據分析漆羔,社會...
    Jenaral閱讀 5,701評論 0 5