1. 程式人生 > >Android Bitmap優化

Android Bitmap優化

開發中經常需要使用Bitmap進行點陣圖顯示,由於現在手機畫素的提升,直接頻繁顯示原圖會消耗大量的記憶體,很容易造成OOM,因此我們需要對Bitmap進行壓縮處理。

首先在通過BitmapFactory建立Bitmap時可以發現,官方為我們提供了一個option引數,大多數開發者都知道這個引數可以幫助我們調節Bitmap的質量,從而實現圖片的壓縮。

public enum Config {
        ALPHA_8     (1),
        RGB_565     (3),
        ARGB_4444   (4),
        ARGB_8888   (5);
    }
官方為我們提供瞭如上幾種配置資訊。

其實這都是色彩的儲存方法:我們知道ARGB指的是一種色彩模式,裡面A代表Alpha,R表示red,G表示green,B表示blue,其實所有的可見色都是右紅綠藍組成的,所以紅綠藍又稱為三原色,每個原色都儲存著所表示顏色的資訊值。

說白了其實就是:

ALPHA_8就是Alpha由8位組成,代表8位Alpha點陣圖

ARGB_4444就是由4個4位組成即16位,代表16位ARGB點陣圖

ARGB_8888就是由4個8位組成即32位,代表32位ARGB點陣圖

RGB_565就是R為5位,G為6位,B為5位共16位,代表16位RGB點陣圖

根據上面的引數可以看出,當我們需要對一個圖片進行壓縮時,如果需要其擁有透明度則選擇ARGB_4444,如果不需要透明度則選擇RGB_565。雖然這會損失一些細節但是普通顯示上是可以接受的。

除了顏色配置,還有一個引數影響著圖片大小,那就是解析度。這裡就不多說原因,相信都知道,那就直接說怎麼解決。

可以直接通過修改Bitmap大小實現:

/**
     * @param bitmap 原始檔
     * @param convertWidth 輸出寬度
     * @param convertHeight 輸出高度
     * @param option 如果option定義為 OPTIONS_RECYCLE_INPUT,則會回收bitmap原始檔,不用手動釋放
     */
    public static Bitmap convertBitmap(Bitmap bitmap, int convertWidth, int convertHeight,int option) {
        Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, convertWidth, convertHeight, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
        return thumbnail;
    }
通過Matrix對圖片按倍數放大和縮小:
/**
     * 講bitmap放大或縮小制定的倍數
     * @param bytes bitmap的位元組陣列
     * @param scale 倍數
     */
    public static byte[] scaleBitmap(byte[] bytes, float scale) {
        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
        Matrix matrix = new Matrix();
        matrix.postScale(scale, scale);
        Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        newBitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
        byte[] byteArray = bos.toByteArray();
        try {
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        bitmap.recycle();
        newBitmap.recycle();
        return byteArray;
    }
上面兩種都有一定的侷限性,第一個需要指定精確的長寬值,如果指定比例和原始比例不符,會導致圖片變形。第二個雖然不會變形,但是隻能按倍數增大和縮小。並且這兩種都需要先生成一個Bitmap,再通過這個Bitmap進行轉換。

最後一種,在Bitmap建立時便直接建立成想要的尺寸:

public interface CompressBitmapCallback {
        void onComplete(Bitmap bitmap);
    }

    private static Executor executor = Executors.newFixedThreadPool(3);

    /**
     * 生成壓縮過的bitmap
     *
     * @param height   定製最大高度
     * @param width    定製最大寬度
     * @param path     圖片本地路徑
     * @param callback 操作完成回撥
     */
    public static void getCompressBitmap(final int height, final int width, final String path, final Handler handler, final CompressBitmapCallback callback) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                BitmapFactory.Options options = new BitmapFactory.Options();
                //inJustDecodeBounds為true時建立的Bitmap只有尺寸資訊,而沒有真正的建立,可以節省資源
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeFile(path, options);
                int bitHeight = options.outHeight;
                int bitWidth = options.outWidth;
                int sample = 1;
                //通過原始尺寸和需要輸出尺寸判斷sample值
                if (bitHeight > height || bitWidth > width) {
                    while (bitHeight / (2 * sample) >= height || bitWidth / (2 * sample) >= width) {
                        sample *= 2;
                    }
                }
                Bitmap compressBitmap = null;
                options.inJustDecodeBounds = false;
                options.inSampleSize = sample;
                options.inPreferredConfig = Bitmap.Config.RGB_565;
                compressBitmap = BitmapFactory.decodeFile(path, options);
                final Bitmap finalCompressBitmap = compressBitmap;
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        callback.onComplete(finalCompressBitmap);
                    }
                });
            }
        });
    }
這種轉換是比較推薦的一種方式,先將options的inJustDecodeBounds屬性設為true,得到將要生成Bitmap的尺寸(注意這裡其實系統並沒有真正生成bitmap,只是拿到了對應的尺寸資訊,因此不會消耗太多資源)。然後再通過需要輸出的大小進行計算inSampleSize的值,這裡解釋一下,這個值的大小代表了生成圖片會是原圖片大小的N分之一,如果是2則為原圖的一半,這也是官方比較推薦的一種方式,並且這樣生成的圖片因為是按比例縮放,所以不會出現圖片變形的情況。還有需要注意的是,這個值只有當為2的指數時才會生效,即1、2、4、8。具體原因不詳。

採用這種方法可以使用大多數情況,但是同樣也有缺點,那就是不能保證輸出圖片尺寸的精確性,因為縮放比例只能是2的指數,所以和想要的尺寸肯定會存在出入。但是這種方法相對來說最節省資源,並且可以儘量保證圖片的顯示效果。

通過BitmapFactory.Options來縮放圖片,主要用到inSampleSize屬性,獲得的Bitmap解析度大小將會為原圖的1/inSampleSize,其中該值必須是2的指數,即:1,2,4,8,16等,不滿足時繪選擇一個最最接近的指數來代替。

為了保證不同解析度圖片處理之後得到同樣的效果,現將Options的inJustDecodeBounds設為true,這樣生成的bitmap只有尺寸屬性而沒有真正的圖片,通過得到的尺寸來計算inSampleSize的大小,再次生成bitmap即為我們想要得到的最終Bitmap。