Android學習筆記之BitmapFactory.Options實現圖片資源的加載...


PS:小項目總算是做完了...歷經20多天...素材,設計,以及實現全由自己完成...心力憔悴啊...該寫寫博客記錄一下學習到的東西了...

 

學習內容:

1.使用BitmapFactory.Options實現圖片資源的加載...防止OOM的產生...

  我們在設計和制作app的時候,有時我們需要去動態的加載圖片資源數據,圖片的大小將決定讀取圖片資源所耗費物理內存...Android對內存的要求是非常苛刻的...如果圖片的資源過大,那么就會出現OOM,也就是所說的內存溢出...這樣程序就不得不被終止掉...因此我們需要避免這種情況的發生...這里我們需要使用到BitmapFactory.Options...

  BitmapFactory.Options其實是一個輔助類,這個類提供了創建Bitmap類的接口,真正完成圖像處理的類其實是Bitmap,但是由於這個類的構造函數是私有的,因此是無法在其他的類外進行實例化對象的,因此BitmapFactory.Options充當了這個輔助類,對外暴露接口,這樣,我們就可以真正的調用Bitmap中的方法了...

  BitmapFactory.Options通過decodeFile()的方法去獲取圖片資源的位置.那么獲取到了資源位置后,我們就可以對圖片進行解碼操作,因為我們都知道,想要獲取到一個圖片,首先要從圖片的碼流開始,對碼流進行解析才會得到相應的圖片...那么這個解碼的過程就是使用decodeStream()方法進行解碼,解碼也是可以限制的...BitmapFactory.Options這個類的Options其實就是對解碼進行一個限制...

  那么再說一下Options,Options有幾個屬性,第一個inJustDecodeBounds(這是一個布爾值,true和false兩個屬性),inJustDecodeBounds=true的時候,那么代表對現在的這張圖片進行非完全解碼,其實說白了就是不給這個圖片資源分配任何的內存,只是獲取這個圖片的基本信息(比如說:長度和寬度),不分配內存的原因想必大家都知道了,就是防止圖片過大的問題,如果圖片過大,那么我們獲取到圖片長度和寬度后,需要對圖片進行一個壓縮的操作,那么這個壓縮就涉及第二個參數,inSampleSize(int 類型,可以是任意值)...這個值的獲取取決於我們壓縮操作的方式,這個壓縮方式是需要我們自己去實現的,最后通過壓縮的方式去獲取inSampleSize的值,通過這個值指定圖片縮放的大小...最后將inJustDecodeBounds=false這樣就可以得到縮放后的圖片了...如果圖片滿足正常的范圍之內,那么就直接進行顯示就可以了...沒必要再進行壓縮操作...

  還是來一段代碼,來方便大家的理解...

 private Bitmap decodeFile(File f){   
        try {   
            //解碼圖像大小,對圖片進行縮放...防止圖片過大導致內存溢出...  
            BitmapFactory.Options o = new BitmapFactory.Options();//實例化一個對象... 
            
            o.inJustDecodeBounds = true;//這個就是Options的第一個屬性,設置為true的時候,不會完全的對圖片進行解碼操作,不會為其分配內存,只是獲取圖片的基本信息...               
            
            BitmapFactory.decodeStream(new FileInputStream(f),null,o); //以碼流的形式進行解碼....
    
            /*
             * 下面也就是對圖片進行的一個壓縮的操作...如果圖片過大,最后會根據指定的數值進行縮放...
             * 找到正確的刻度值,它應該是2的冪.
             * 這里我指定了圖片的長度和寬度為70個像素...
             * 
             * */
            
            final int REQUIRED_SIZE=70;   
            int width_tmp=o.outWidth, height_tmp=o.outHeight;   
            int scale=1;   
            while(true){   
                if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)   
                    break;   
                width_tmp/=2;   
                height_tmp/=2;   
                scale*=2;   
            }   
    
            BitmapFactory.Options o2 = new BitmapFactory.Options(); //這里定義了一個新的對象...獲取的還是同一張圖片...
            o2.inSampleSize=scale;   //對這張圖片設置一個縮放值...inJustDecodeBounds不需要進行設置...
            return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); //這樣通過這個方法就可以產生一個小的圖片資源了...
        } catch (Exception e) {}   
        return null;   
    }   

  這里只是粘貼了縮放過程的一個代碼,通過這段代碼,我們就可以實現大圖片縮放為小型圖片,防止OOM的發生...這只是一個小的函數,最后我會給出一個源碼提供給大家去下載,方便大家去學習...這里還需要說明一些問題...就是這個inSampleSize值的設置.這個值如果設置為2,那么圖片就縮放4倍...如果為3,那么縮放的程度就為9倍...以此類推...

  還有這個decodeStream()方法只是一個把數據封裝成流的形式對碼流進行一個數據傳遞...真正完成圖片繪制的是其內部過程...我們還是來看一下源代碼,方便大家的理解...

/*
 *這是源碼的實現過程,說實話,我也看不懂所有的東西...因為自己也是個小菜鳥...
 *不過我們可以分析一下...
 */ 
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
        // we don't throw in this case, thus allowing the caller to only check
        // the cache, and not force the image to be decoded.
        if (is == null) {
            return null;
        }

        // we need mark/reset to work properly

        if (!is.markSupported()) {
            is = new BufferedInputStream(is, DECODE_BUFFER_SIZE);
        }

        // so we can call reset() if a given codec gives up after reading up to
        // this many bytes. FIXME: need to find out from the codecs what this
        // value should be.
        is.mark(1024);

        Bitmap bm;
        boolean finish = true;
        /*
         *我們來看下面這個函數...
         */
        if (is instanceof AssetManager.AssetInputStream) {//這塊是一個判斷的過程,判斷的東西就是,我們這個圖片資源到底來自於什么地方,如果滿足這個if條件,那么這個圖片資源屬於drawable文件下的資源...
            final int asset = ((AssetManager.AssetInputStream) is).getAssetInt();
            /*
             *這里就是設置縮放的一個過程,其中包含一些參數的設置...
             *比如說縮放的參數,以及目標面積的大小設定...
             */
            if (opts == null || (opts.inScaled && opts.inBitmap == null)) {
                float scale = 1.0f;
                int targetDensity = 0;
                if (opts != null) {
                    final int density = opts.inDensity;
                    targetDensity = opts.inTargetDensity;
                    if (density != 0 && targetDensity != 0) {
                        scale = targetDensity / (float) density;
                    }
                }
                /*
                 *下面這個函數的源碼,我也看不到了...應該就是一個解碼的操作過程...
                 */
                bm = nativeDecodeAsset(asset, outPadding, opts, true, scale);
if (bm != null && targetDensity != 0) bm.setDensity(targetDensity); finish = false; } else { /* *這塊就是直接解碼操作,這個else滿足的條件是圖片資源滿足指定的大小,因此我們不需要傳遞scale參數了...也就是代表不用進行縮放... */ bm = nativeDecodeAsset(asset, outPadding, opts); } } else { //這里滿足的條件想必大家知道,就是圖片資源可能來自於其他地方... // pass some temp storage down to the native code. 1024 is made up, // but should be large enough to avoid too many small calls back // into is.read(...) This number is not related to the value passed // to mark(...) above. /* *通過byte流的形式對圖片進行獲取,這個也很好理解,如果想獲取圖片資源,那么必須要經過流的形式,對資源數據進行封裝...然后獲取... *上面之所以不用流的形式,是因為drawable中的資源有特定的獲取方式... */ byte [] tempStorage = null; if (opts != null) tempStorage = opts.inTempStorage; if (tempStorage == null) tempStorage = new byte[16 * 1024]; /* *還是同理,設置參數.... */ if (opts == null || (opts.inScaled && opts.inBitmap == null)) { float scale = 1.0f; int targetDensity = 0; if (opts != null) { final int density = opts.inDensity; targetDensity = opts.inTargetDensity; if (density != 0 && targetDensity != 0) { scale = targetDensity / (float) density; } } bm = nativeDecodeStream(is, tempStorage, outPadding, opts, true, scale); if (bm != null && targetDensity != 0) bm.setDensity(targetDensity); finish = false; } else { bm = nativeDecodeStream(is, tempStorage, outPadding, opts); } } if (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); } //這個方法,將會被最終調用..也就是下面的方法... return finish ? finishDecode(bm, outPadding, opts) : bm; } /* *這個方法是當完成解碼操作后需要調用的方法... */ private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) { if (bm == null || opts == null) { return bm; } final int density = opts.inDensity; if (density == 0) { return bm; } bm.setDensity(density); final int targetDensity = opts.inTargetDensity; if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) { return bm; } byte[] np = bm.getNinePatchChunk(); int[] lb = bm.getLayoutBounds(); final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np); /* *這個函數重要部分就是下面,我們可以看到,通過對參數的一些設定..最后會調用 Bitmap.createScaledBitmap方法... */ if (opts.inScaled || isNinePatch) { float scale = targetDensity / (float) density; if (scale != 1.0f) { final Bitmap oldBitmap = bm; bm = Bitmap.createScaledBitmap(oldBitmap, Math.max(1, (int) (bm.getWidth() * scale + 0.5f)), Math.max(1, (int) (bm.getHeight() * scale + 0.5f)), true); if (bm != oldBitmap) oldBitmap.recycle(); if (isNinePatch) { np = nativeScaleNinePatch(np, scale, outPadding); bm.setNinePatchChunk(np); } if (lb != null) { int[] newLb = new int[lb.length]; for (int i=0; i<lb.length; i++) { newLb[i] = (int)((lb[i]*scale)+.5f); } bm.setLayoutBounds(newLb); } } bm.setDensity(targetDensity); } return bm; }

 我們可以不完全弄懂這個源碼到底是怎么回事,但是我們通過源碼可以知道其中到底是以怎樣的過程進行實現的,這才是看源碼的目的...如果還是有更深入研究的讀者...那么您可以完全弄懂...這里我們可以看到最后的調用時Bitmap.createScaledBitmap方法,接着Bitmap.createScaledBitmap方法將調用Bitmap createBitmap...這個方法才是最終實現把圖片顯示在畫布上的一個最終函數,這個函數通過使用Canvas最后在畫布上將圖片描畫出來...這是這個函數的源碼,我就不進行解釋了,其實這個源碼比上面那個更簡單,容易理解,其實就是根據這些參數把圖片展示出來,比如說獲取圖片的高度和寬度,縮放的大小,圖片的編碼格式,最后通過調用Paint畫筆,結合Canvas,這里我們能夠發現,最后畫出來的畫布是一個矩形,那么畫出來的圖片必然也是一個矩形..最后把畫出來的圖片進行返回..最后就顯示在畫布之上了...

 public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,
            Matrix m, boolean filter) {

        checkXYSign(x, y);
        checkWidthHeight(width, height);
        if (x + width > source.getWidth()) {
            throw new IllegalArgumentException("x + width must be <= bitmap.width()");
        }
        if (y + height > source.getHeight()) {
            throw new IllegalArgumentException("y + height must be <= bitmap.height()");
        }

        // check if we can just return our argument unchanged
        if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() &&
                height == source.getHeight() && (m == null || m.isIdentity())) {
            return source;
        }

        int neww = width;
        int newh = height;
        Canvas canvas = new Canvas();
        Bitmap bitmap;
        Paint paint;

        Rect srcR = new Rect(x, y, x + width, y + height);
        RectF dstR = new RectF(0, 0, width, height);

        Config newConfig = Config.ARGB_8888;
        final Config config = source.getConfig();
        // GIF files generate null configs, assume ARGB_8888
        if (config != null) {
            switch (config) {
                case RGB_565:
                    newConfig = Config.RGB_565;
                    break;
                case ALPHA_8:
                    newConfig = Config.ALPHA_8;
                    break;
                //noinspection deprecation
                case ARGB_4444:
                case ARGB_8888:
                default:
                    newConfig = Config.ARGB_8888;
                    break;
            }
        }

        if (m == null || m.isIdentity()) {
            bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
            paint = null;   // not needed
        } else {
            final boolean transformed = !m.rectStaysRect();

            RectF deviceR = new RectF();
            m.mapRect(deviceR, dstR);

            neww = Math.round(deviceR.width());
            newh = Math.round(deviceR.height());

            bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig,
                    transformed || source.hasAlpha());

            canvas.translate(-deviceR.left, -deviceR.top);
            canvas.concat(m);

            paint = new Paint();
            paint.setFilterBitmap(filter);
            if (transformed) {
                paint.setAntiAlias(true);
            }
        }
        
        // The new bitmap was created from a known bitmap source so assume that
        // they use the same density
        bitmap.mDensity = source.mDensity;
        
        canvas.setBitmap(bitmap);
        canvas.drawBitmap(source, srcR, dstR, paint);
        canvas.setBitmap(null);

        return bitmap;
    }

  這樣就實現了通過對圖片大小的縮放,避免OOM的發生...最后放上面一個源碼....提供大家下載....只不過這個源碼是通過從服務器上下載圖片,通過解析XML文件,然后對數據進行獲取..然后對圖片進行縮放,最后以ListView的形式進行顯示...圖片的縮放只隸屬於一個小的模塊....

源碼位置:http://files.cnblogs.com/files/RGogoing/daimarufeng.zip

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM