通用的Bitmap壓縮算法,進一步節約內存(推薦)

  前幾天我寫了一篇經過壓縮Bitmap,減小OOM的文章,那篇文章的目的是按照imageview的大小來壓縮bitmap,讓bitmap的大小正好是imageview。可是那種算法的通用性比較差,僅僅能適合fit_xy的狀況。對此我進一步分析了下這個問題,而且參考了Volley的源碼,最終得出告終論:若是你要讓這個壓縮後的bitmap徹底適合多種imageview拉伸模式,你就必須重寫拉伸模式的算法,但這過於小題大作了。討巧一點的辦法就是讓這個imageview不徹底按照imageview的長寬進行壓縮,而僅僅按照imageview的長或寬按比例縮小,獲得的是一張和原圖比率同樣的小圖,讓imageview加載這個小圖就好了。世上沒有十全十美的事情,你這個雖然討巧了,但問題也就來了,在某些模式下可能會有一部分圖片沒有顯示在屏幕上,浪費了一點點內存,在cent模式下,原圖的顯示效果和小圖的顯示效果徹底不同。html

總結:考慮到多種因素,我仍是決定使用比較討巧的作法,由於它通用性比較高,浪費內存的狀況有,但浪費的內存不多(幾kb),通常狀況下咱們不用center模式進行圖片的顯示,因此咱們徹底能夠考慮這個方式。android

 

工具類:算法

我參考了volley的代碼,從新構建了工具類,下面直接貼出工具類的代碼:數組

package com.kale.bitmaptest;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

public class BitUtils {

    private static int mDesiredWidth;
    private static int mDesiredHeight;

    /**
     * @description 從Resources中加載圖片
     *
     * @param res
     * @param resId
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        // 設置成了true,不佔用內存,只獲取bitmap寬高
        options.inJustDecodeBounds = true; 
        // 初始化options對象
        BitmapFactory.decodeResource(res, resId, options); 
        // 獲得計算好的options,目標寬、目標高
        options = getBestOptions(options, reqWidth, reqHeight);
        Bitmap src = BitmapFactory.decodeResource(res, resId, options); // 載入一個稍大的縮略圖
        return createScaleBitmap(src, mDesiredWidth, mDesiredHeight); // 進一步獲得目標大小的縮略圖
    }

    /**
     * @description 從SD卡上加載圖片
     *
     * @param pathName
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static Bitmap decodeSampledBitmapFromFile(String pathName, int reqWidth, int reqHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(pathName, options);
        options = getBestOptions(options, reqWidth, reqHeight);
        Bitmap src = BitmapFactory.decodeFile(pathName, options);
        return createScaleBitmap(src, mDesiredWidth, mDesiredHeight);
    }

    /**
     * @description 計算目標寬度,目標高度,inSampleSize
     *
     * @param options
     * @param reqWidth
     * @param reqHeight
     * @return BitmapFactory.Options對象
     */
    private static BitmapFactory.Options getBestOptions(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // 讀取圖片長寬
        int actualWidth = options.outWidth;
        int actualHeight = options.outHeight;
        // Then compute the dimensions we would ideally like to decode to.
        mDesiredWidth = getResizedDimension(reqWidth, reqHeight, actualWidth, actualHeight);
        mDesiredHeight = getResizedDimension(reqHeight, reqWidth, actualHeight, actualWidth);
        // 根據如今獲得計算inSampleSize
        options.inSampleSize = calculateBestInSampleSize(actualWidth, actualHeight, mDesiredWidth, mDesiredHeight);
        // 使用獲取到的inSampleSize值再次解析圖片
        options.inJustDecodeBounds = false;
        return options;
    }

    /**
     * Scales one side of a rectangle to fit aspect ratio. 最終獲得從新測量的尺寸
     *
     * @param maxPrimary
     *            Maximum size of the primary dimension (i.e. width for max
     *            width), or zero to maintain aspect ratio with secondary
     *            dimension
     * @param maxSecondary
     *            Maximum size of the secondary dimension, or zero to maintain
     *            aspect ratio with primary dimension
     * @param actualPrimary
     *            Actual size of the primary dimension
     * @param actualSecondary
     *            Actual size of the secondary dimension
     */
    private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary, int actualSecondary) {
        double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;
        if (resized * ratio > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }
        return resized;
    }

    /**
     * Returns the largest power-of-two divisor for use in downscaling a bitmap
     * that will not result in the scaling past the desired dimensions.
     *
     * @param actualWidth
     *            Actual width of the bitmap
     * @param actualHeight
     *            Actual height of the bitmap
     * @param desiredWidth
     *            Desired width of the bitmap
     * @param desiredHeight
     *            Desired height of the bitmap
     */
    // Visible for testing.
    private static int calculateBestInSampleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
        double wr = (double) actualWidth / desiredWidth;
        double hr = (double) actualHeight / desiredHeight;
        double ratio = Math.min(wr, hr);
        float inSampleSize = 1.0f;
        while ((inSampleSize * 2) <= ratio) {
            inSampleSize *= 2;
        }

        return (int) inSampleSize;
    }

    /**
     * @description 經過傳入的bitmap,進行壓縮,獲得符合標準的bitmap
     *
     * @param src
     * @param dstWidth
     * @param dstHeight
     * @return
     */
    private static Bitmap createScaleBitmap(Bitmap tempBitmap, int desiredWidth, int desiredHeight) {
        // If necessary, scale down to the maximal acceptable size.
        if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth || tempBitmap.getHeight() > desiredHeight)) {
            // 若是是放大圖片,filter決定是否平滑,若是是縮小圖片,filter無影響
            Bitmap bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true);
            tempBitmap.recycle(); // 釋放Bitmap的native像素數組
            return bitmap;
        } else {
            return tempBitmap; // 若是沒有縮放,那麼不回收
        }
    }

}

這個工具類構造的思想和本來的構造思想徹底一致,差異之處在於這裏的圖片是等比縮放的。app

 

測試代碼:ide

    public void loadBitmap(boolean exactable) {
        int bmSize = 0;
        Bitmap bm = null;
        if (exactable) {
            // 經過工具類來產生一個符合ImageView的縮略圖
            bm = BitUtils.decodeSampledBitmapFromResource(getResources(), R.drawable.saber, iv.getWidth(), iv.getHeight());
        } else {
            // 直接加載原圖
            bm = BitmapFactory.decodeResource(getResources(), R.drawable.saber);
        }
        iv.setImageBitmap(bm);
bmSize
+= bm.getByteCount(); // 獲得bitmap的大小 int kb = bmSize / 1024; int mb = kb / 1024; kb = kb % 1024; Log.d("Bitmap", "bitmap w = " + bm.getWidth() + " h = " + bm.getHeight()); Log.d("Bitmap", "bitmap size = " + mb + "MB " + kb + "KB"); Toast.makeText(this, "bitmap size = " + mb + "MB " + kb + "KB", Toast.LENGTH_LONG).show(); }

經過加載原圖和加載縮略圖進行比較,最終在log打印出圖片的寬高和圖片內存佔用。工具

 

測試結果:佈局

佈局文件:測試

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context="${relativePackage}.${activityClass}" >

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:src="@drawable/ic_launcher" />

    <Button
        android:id="@+id/original_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:onClick="butonListener"
        android:text="加載原圖" />

    <Button
        android:id="@+id/clip_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/original_button"
        android:layout_alignBottom="@+id/original_button"
        android:layout_alignParentRight="true"
        android:onClick="butonListener"
        android:text="加載縮略圖" />

</RelativeLayout>
View Code

前題:個人手機定義的imageview是100dp,實際是200pix。加載圖片的實際大小:850 x 1200this

① 加載原圖

bitmap寬 = 567,高 = 800;

內存佔用:1M 747KB

解釋:最終獲得的圖片大小和原始圖片不一樣,這裏應該是BitmapFactory在解碼時就已經作了壓縮,算是自帶的一個智能壓縮方案吧。

 

② 用工具類加載縮略圖

bitmap寬 = 141,高 = 200;

內存佔用:110KB

解釋:目標的imageview寬、高均爲100dp,在我手機上換算爲200pix,這裏作了等比縮放處理,因此高爲200.最後咱們也明顯的看出,用這種方式獲得的圖片比較小,不會輕易出現OOM

 

完整的activity代碼:

package com.kale.bitmaptest;

import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.Toast;

public class MainActivity extends Activity {
    ImageView iv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iv = (ImageView) findViewById(R.id.imageView);
        iv.setScaleType(ScaleType.CENTER_CROP); 
        // center 變了
        getMemoryCacheSize();
    }

    public void butonListener(View v) {
        switch (v.getId()) {
        case R.id.original_button:
            loadBitmap(false); // 加載原圖
            break;

        case R.id.clip_button:
            loadBitmap(true); // 加載縮略圖
            break;
        }
    }

    public void loadBitmap(boolean exactable) {
        int bmSize = 0;
        Bitmap bm = null;
        if (exactable) {
            // 經過工具類來產生一個符合ImageView的縮略圖
            bm = BitUtils.decodeSampledBitmapFromResource(getResources(), R.drawable.saber, iv.getWidth(), iv.getHeight());
        } else {
            // 直接加載原圖
            bm = BitmapFactory.decodeResource(getResources(), R.drawable.saber);
        }
        iv.setImageBitmap(bm);
        bmSize += bm.getByteCount(); // 獲得bitmap的大小
        int kb = bmSize / 1024;
        int mb = kb / 1024;
        kb = kb % 1024;
        Log.d("Bitmap", "bitmap w = " + bm.getWidth() + " h = " + bm.getHeight());
        Log.d("Bitmap", "bitmap size = " + mb + "MB " + kb + "KB");
        Toast.makeText(this, "bitmap size = " + mb + "MB " + kb + "KB", Toast.LENGTH_LONG).show();
    }

    public int getMemoryCacheSize() {
        // Get memory class of this device, exceeding this amount will throw an
        // OutOfMemory exception.
        final int memClass = ((ActivityManager) getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
        System.out.println("memory = " + memClass + "M");
        return memClass;
    }

    public int dip2px(float dpValue) {
        final float scale = getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}
View Code

 

利弊:

利:

節約內存,下降出現OOM的概率

弊:

下降了圖片的清晰度,不適用於center模式的imageview。

  

左邊的是加載的縮略圖,右邊的是加載的原圖。右邊的圖片明顯比座標的清晰,但銳化過於嚴重了,左邊的雖然小,可是較爲模糊。

 

若是你用了android studio,你能夠很明顯的看出內存的變化:

Android Studio下的測試代碼:

package com.example.jack.loadbitmap;

import com.kale.lib.activity.KaleBaseActivity;
import com.kale.lib.utils.BitmapUtil;
import com.kale.lib.utils.EasyToast;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;


public class MainActivity extends KaleBaseActivity {

    ImageView mImageView;

    Button loadOriginPicBtn;

    Button loadThumbPicBtn;

    @Override
    protected int getContentViewId() {
        return R.layout.activity_main;
    }

    @Override
    protected void findViews() {
        mImageView =  getView(R.id.imageView);
        loadOriginPicBtn = getView(R.id.loadOriginal_button);
        loadThumbPicBtn = getView(R.id.loadThumb_button);
    }

    @Override
    protected void setViews() {
        // 加載原始的圖片
        loadOriginPicBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.saber);
                setBitmapToImageView(bitmap);
            }
        });

        // 加載壓縮後的圖片
        loadThumbPicBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Bitmap bitmap = BitmapUtil.decodeSampledBitmapFromResource(getResources(), R.drawable.saber,
                        mImageView.getWidth(), mImageView.getHeight());
                setBitmapToImageView(bitmap);

            }
        });
    }

    private void setBitmapToImageView(Bitmap bitmap) {
        mImageView.setImageBitmap(bitmap);

        int bmSize = BitmapUtil.getBitmapSize(bitmap);
        int mb = bmSize / 1024 / 1024;
        int kb = bmSize /1014 % 1024;
        String bitmapSizeStr = mb + "MB " + kb + "KB";
        Log.d("Bitmap", "bitmap w = " + bitmap.getWidth() + " h = " + bitmap.getHeight());
        Log.d("Bitmap", bitmapSizeStr);
        EasyToast.makeText(mContext, "bitmap size = " + bitmapSizeStr);
    }

}

 

源碼下載:

http://download.csdn.net/detail/shark0017/8412329

利用AS構建的工程(推薦):http://download.csdn.net/detail/shark0017/8671957

 

參考自:

http://www.open-open.com/lib/view/open1329994992015.html

相關文章
相關標籤/搜索