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