使用BitmapFactory壓縮圖片遇到的問題總結

壓縮前先搞明白原理:Bitmap佔用的內存大小:java

bytes = 原始圖片寬*(options.inTargetDensity/options.inDensity)*原始圖片長*(options.inTargetDensity/options.inDensity)*每一個像素點位數
複製代碼

inTargetDensity指的是當前手機的密度,inDensity是圖片的所在drawable目錄生成的密度bash

使用sample採樣率來對Bitmap進行壓縮到指定的寬和高(原理不在贅述) 方法相信你們都熟悉,網上一搜都大致相似,下面是具體的方法框架

// 使用decodeRes方法對資源進行轉換,使用InJustDecodeBounds屬性置爲true,先獲圖
//片高,native層先不返回Bitmap
//進行計算sampleSize,而後設爲false,再次decode返回Bitmap
public Bitmap compressBitmapIntoThumbnailPic(Context context, int res) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        Bitmap bitmap1 = BitmapFactory.decodeResource(context.getResources(), res, options);
        Log.d("tag", "first bitmap == " + bitmap1);
        int sampleBitmap = calculateInSampleSize(options, 40, 40);
        options.inSampleSize = sampleBitmap;
        options.inJustDecodeBounds = false;
        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), res, options);
        Log.d("tag", "final == " + bitmap.getByteCount() +
                " target real density " + options.inTargetDensity + " folder density " + options.inDensity);
        return bitmap;
    }
複製代碼
// 對要求的寬高和當前圖片寬高對比,取一個最小的比例做爲sampleSize
 private int calculateInSampleSize(BitmapFactory.Options options, int requireW, int requereH) {
        int sampleSize = 1;
        int outHeight = options.outHeight;
        int outWidth = options.outWidth;
        int rationHeight = Math.round(outHeight / requereH);
        int rationWidth = Math.round(outWidth / requireW);
        if (rationHeight > 1 || rationWidth > 1) {
            sampleSize = rationHeight > rationWidth ? rationWidth : rationHeight;
        }
        Log.d("tag", "outHeight = " + outHeight + "outWidth = " + outWidth + " -------- " + sampleSize);
        return sampleSize;
    }
複製代碼

執行上述的代碼能正常的將一個大圖片進行壓縮展現,而後本身看了下源碼,發現BitmapFactory最終調用的都是decodeStream(...) 這個方法來處理的,因而想試一下直接處理流來對圖片壓縮也應該能夠,代碼見下:ide

/**
     *  用流的形式生成Bitmap
     **/
    public Bitmap compressBitmapIntoThumbnailPic(InputStream is) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        // 最終返回的Bitmap
        Bitmap finalBitmap = null;
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT){
            //設置inJustDecodeBounds在native decode時候只返回尺寸等,Bitmap爲空
            // 或者將inputStream -> BufferedInputStream 保證某些狀況reset不支持
            BitmapFactory.decodeStream(is, null, options);
            int sampleBitmap = calculateInSampleSize(options, 80, 80);
            options.inSampleSize = sampleBitmap;
            options.inJustDecodeBounds = false;
            try {
                is.reset();
            } catch (IOException e) {
                e.printStackTrace();
            }
            finalBitmap = BitmapFactory.decodeStream(is, null, options);
        }else {// 4.4包含之後就沒有這個is.mark(1024)的大小限制問題了。不會出現OOM
            if (is.markSupported()) {
                try {
                    BitmapFactory.decodeStream(is,null,options);
                    int sampleBitmap = calculateInSampleSize(options, 40, 40);
                    options.inSampleSize = sampleBitmap;
                    options.inJustDecodeBounds = false;
                    is.reset();
                    finalBitmap = BitmapFactory.decodeStream(is, null, options);
                    Log.d("tag", "final == " + finalBitmap.getByteCount() +
                            " target real density " + options.inTargetDensity + " folder density " + options.inDensity);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return finalBitmap;
    }
複製代碼

這裏對版本作了一個判斷,只是爲了調式方便。先說下緣由,和上面方法相比而言,多了一個 is.reset , 不加的話直接就返回null 了,即便咱們已近設置了inJustDecodeBounds爲false, 這就奇怪了,這時候仍是看下源碼來找答案吧,圈紅的部分意思是 : 咱們在options裏設置要求只返回大小或者流不能被decode的時候就返回null,那就說明是流不能被decode了,而後想到了流被使用事後的位置是會改變的,咱們第一次decode的時候流執行了mark方法,下次再執行這個流的時候就必需要使用reset,返回上次標記的內容,這樣才能正常使用流。有點恍然大悟~,被本身感動了。 ui

這裏寫圖片描述
而後咱們去看下InputStream的源碼是怎麼說的,重點是readLimit這個關鍵字,大致意思是在流支持mark的狀況下,調用reset方法會返回給記錄的字節數,可是若是超過了這個 閥值,調用reset不能所有的返回,

/**
     * Marks the current position in this input stream. A subsequent call to
     * the <code>reset</code> method repositions this stream at the last marked
     * position so that subsequent reads re-read the same bytes.
     *
     * <p> The <code>readlimit</code> arguments tells this input stream to
     * allow that many bytes to be read before the mark position gets
     * invalidated.
     *
     * <p> The general contract of <code>mark</code> is that, if the method
     * <code>markSupported</code> returns <code>true</code>, the stream somehow
     * remembers all the bytes read after the call to <code>mark</code> and
     * stands ready to supply those same bytes again if and whenever the method
     * <code>reset</code> is called.  However, the stream is not required to
     * remember any data at all if more than <code>readlimit</code> bytes are
     * read from the stream before <code>reset</code> is called.
     *
     * <p> Marking a closed stream should not have any effect on the stream.
     *
     * <p> The <code>mark</code> method of <code>InputStream</code> does
     * nothing.
     *
     * @param   readlimit   the maximum limit of bytes that can be read before
     *                      the mark position becomes invalid.
     * @see     java.io.InputStream#reset()
     */
複製代碼

在reset的時候容易發生OOM,緣由是在流在建立時沒有調用mark的話,mark記錄的大小確定是小於總體的,這時候你去調用reset請求的大於mark記錄的大小,就會OOM 。參考此條解決方式this

/**
     * Repositions this stream to the position at the time the
     * <code>mark</code> method was last called on this input stream.
     *
     * <p> The general contract of <code>reset</code> is:
     *
     * <ul>
     * <li> If the method <code>markSupported</code> returns
     * <code>true</code>, then:
     *
     *     <ul><li> If the method <code>mark</code> has not been called since
     *     the stream was created, or the number of bytes read from the stream
     *     since <code>mark</code> was last called is larger than the argument
     *     to <code>mark</code> at that last call, then an
     *     <code>IOException</code> might be thrown.
     *
     *     <li> If such an <code>IOException</code> is not thrown, then the
     *     stream is reset to a state such that all the bytes read since the
     *     most recent call to <code>mark</code> (or since the start of the
     *     file, if <code>mark</code> has not been called) will be resupplied
     *     to subsequent callers of the <code>read</code> method, followed by
     *     any bytes that otherwise would have been the next input data as of
     *     the time of the call to <code>reset</code>. </ul>
     *
     * <li> If the method <code>markSupported</code> returns
     * <code>false</code>, then:
     *
     *     <ul><li> The call to <code>reset</code> may throw an
     *     <code>IOException</code>.
     *
     *     <li> If an <code>IOException</code> is not thrown, then the stream
     *     is reset to a fixed state that depends on the particular type of the
     *     input stream and how it was created. The bytes that will be supplied
     *     to subsequent callers of the <code>read</code> method depend on the
     *     particular type of the input stream. </ul></ul>
     *
     * <p>The method <code>reset</code> for class <code>InputStream</code>
     * does nothing except throw an <code>IOException</code>.
     *
     * @exception  IOException  if this stream has not been marked or if the
     *               mark has been invalidated.
     * @see     java.io.InputStream#mark(int)
     * @see     java.io.IOException
     */
    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }
複製代碼

上面代碼中有區分4.4版本的判斷,這裏解釋下緣由:以下圖所示,在4.3源碼中默認mark的閥值大小爲1024K,這樣的話對大圖片很容易引發OOM ; 在4.4(包含)以後,去掉了這個case,沒有具體的1024限制了。這裏只是對原理作了簡單的研究,實際上咱們仍是使用Glide之類的框架去作加載圖片的工做。可是對原理有些瞭解仍是不錯的。 spa

這裏寫圖片描述

這裏寫圖片描述

擴展 這裏還有倆點疑問,3d

  1. 爲啥設置了inJustDecodeBounds = true 就返回空的Bitmap ; 2.第一種用decodeResource的方法爲啥能成功的返回Bitmap ??? 仍是要去源碼中找答案:

這裏寫圖片描述

這裏寫圖片描述
上面這倆張顯示了在native層對Bitmap的操做 ; 下面這張圖解釋了疑問 2 ,由於decodeRes不是直接操做流的,是根據資源Id去加載流,內部作了處理,每次返回的是可用的流,這樣就沒問題了。
這裏寫圖片描述

結尾

有疑問仍是要去看源碼啊,知其然也要知其因此然·,水平有限,錯誤之處請幫忙斧正code

相關文章
相關標籤/搜索