天天看點

設定圖檔inSampleSize但是記憶體沒變?或許你應該看看這個

對于安卓開發來說,記憶體溢出(oom)是安卓程式員不可繞過的坎,特别是對于大圖檔來說,加載時候的大記憶體更是常常讓人膽戰心驚。

很多安卓程式員都知道,避免圖檔加載大記憶體的最常用方法,那就是用BitmapFactory的options,設定這個options的inSampleSize來達到将圖檔按照實際顯示大小去縮小自己圖檔,來達到減少記憶體。

若是你去百度inSampleSize,那麼你會看到最多的用法是這樣的:

private Bitmap getimage(String srcPath) {
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        //開始讀入圖檔,此時把options.inJustDecodeBounds 設回true了
        newOpts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeFile(srcPath,newOpts);//此時傳回bm為空

        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        //現在主流手機比較多是800*480分辨率,是以高和寬我們設定為
        float hh = f;//這裡設定高度為800f
        float ww = f;//這裡設定寬度為480f
        //縮放比。由于是固定比例縮放,隻用高或者寬其中一個資料進行計算即可
        int be = ;//be=1表示不縮放
        if (w > h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= )
            be = ;
        newOpts.inSampleSize = be;//設定縮放比例
        //重新讀入圖檔,注意此時已經把options.inJustDecodeBounds 設回false了
        bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
        return compressImage(bitmap);//壓縮好比例大小後再進行品質壓縮
    }
           

看起來好像完全沒有問題,根據自己想要的的圖檔大小,然後去計算将原圖縮放的inSampleSize,然後指派給bitmap以達到縮放圖檔大小。

很多人看到這樣算法便喜不自勝急忙将它複制到自己工程,可是複制進去之後他們卻傻眼了,記憶體跟以前沒用時候差不多啊。

為什麼會這樣?

因為,inSampleSize隻能為整數啊。

我們舉個例子,假如有一張高度為700的圖檔,你想把它縮放為500,你要是用上面那個算法去算,你會發現,你算出來的inSampleSize其實為1!!!

而1,那就是圖檔不變,沒有壓縮

在這裡說一下,inSampleSize的值隻能為2的倍數,1,4,8,16……

1的話是不變,2的話是圖檔變為原來4分之一大小,8是變為原來64分之一大小,以此類推

是以到了最後,你會發現你想要壓縮的圖檔其實根本沒有被壓縮。

好吧,就算有變,但其實也很難,甚至幾乎不可能變到你想要的圖檔大小,就比如上面說的那個例子,無論你設定inSampleSize為什麼值,你都會發現,你最終得到的圖檔要麼太大要麼太小。

但是安卓怎麼可能那麼垃圾,

對的,安卓才不會這麼垃圾呢,

其實與inSampleSize相關的還有幾個參數,比如inScaled,inTargetDensity,inDensity。

這些參數其實非常有用,與inSampleSize一起用才會完美。

其中:

inScaled:将它設定為true,那麼代表這張圖檔可以縮放

inDensity:圖檔的原來密度,預設一般為160.

inTargetDensity:圖檔的目标密度,圖檔操作之後的密度

其實你設定一張圖檔的BitmapFactory的options,這張圖檔的輸出寬高是這樣決定的:

輸出圖檔的寬高= (原圖檔的寬高 * (inTargetDensity / inDensity)) / inSampleSize
           

是以你要是現将一張700的圖檔縮小為600,你可以計算一下圖檔的inTargetDensity,如下面方法

/**
     * 擷取特定大小縮略圖
     * @param imageid 圖檔資源id
     * @param size 你想要擷取的圖檔大小尺寸
     * @return
     */
    public Bitmap getSampleBitmap2(int imageid,int size){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        BitmapFactory.decodeResource(getResources(),imageid,options);
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        options.inSampleSize= calculateInSampleSize(options,size);
        //設定圖檔可以縮小
        options.inScaled = true;
        int calsize=options.outHeight>options.outWidth?options.outWidth:options.outHeight;
        /**
         * 計算圖檔縮小的目标密度,在這裡說一下,有一條公式:
         * 輸出圖檔的寬高= (原圖檔的寬高 * (inTargetDensity / inDensity)) / inSampleSize
         * 一般來說,圖檔的options.inDensity預設為160
         * 是以inTargetDensity計算公式為:(希望輸出的寬高*options.inDensity)/(原來圖檔的寬高/options.inSampleSize)
         */
        options.inTargetDensity =(size*options.inDensity)/(calsize/options.inSampleSize);
        options.inJustDecodeBounds = false;
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),imageid,options);
        return bitmap;
    }
           
//谷歌源碼裡面的計算simplesize方法,
    public int calculateInSampleSize(BitmapFactory.Options options,
                                     int size) {
        int reqWidth,reqHeight;
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        if(options.outHeight>options.outWidth){
            reqWidth=size;
            reqHeight=size*options.outHeight/options.outWidth;
        }else{
            reqWidth=size*options.outWidth/options.outHeight;
            reqHeight=size;
        }
        int inSampleSize = ;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / ;
            final int halfWidth = width / ;

            // Calculate the largest inSampleSize value that is a power of 2 and
            // keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= ;
            }
        }

        return inSampleSize;
    }
           

比如我将一張寬700圖檔縮小為寬450的圖檔,在沒設定inTargetDensity的時候

設定圖檔inSampleSize但是記憶體沒變?或許你應該看看這個
設定圖檔inSampleSize但是記憶體沒變?或許你應該看看這個

計算出來的inSampleSize為1,圖檔尺寸很大,沒有變

所占用記憶體:

設定圖檔inSampleSize但是記憶體沒變?或許你應該看看這個

差不多40M

而在設定了inTargetDensity 之後

設定圖檔inSampleSize但是記憶體沒變?或許你應該看看這個
設定圖檔inSampleSize但是記憶體沒變?或許你應該看看這個

計算出來的inSampleSize仍然為1,但是圖檔尺寸卻變為我想要擷取的大小450了!!!

而所占用的記憶體:

設定圖檔inSampleSize但是記憶體沒變?或許你應該看看這個

隻有14M左右!!!!!!!!!!

40?14!是不是感覺世界瞬間美好很多啦~

對了,後面順便說一下,若你将根據這個方法得到的圖檔指派給另一個bitmap的時候,可能會出現,這個被指派的bitmap圖檔雖然圖檔大小不變,但是顯示變了,那是因為這個inTargetDensity 屬性不會自動指派給新的bitmap,可以這樣做

//建立一張新的bitmap,跟傳入圖檔一樣寬的正方形bitmap,
        Bitmap b=Bitmap.createBitmap(bitmapSize,bitmapSize, Bitmap.Config.ARGB_8888);
        //将圖檔密度修改為上面那張的圖檔密度
        b.setDensity(bitmap.getDensity());
           

這樣新得到的b圖檔就會跟原來那張一樣了