Bitmap做爲重要Android應用之一,在不少時候若是應用不當,很容易形成內存溢出,那麼這篇文章的目的就在於探討Bitmap的有效運用及其優化html
當屢次發送請求的時候,請求同一內容,爲了使資源獲得合理利用,那麼就須要設置緩存,避免同一內容被屢次請求
在這裏使用一個Http的緩存策略,對http自帶的緩存策略作一個簡單的使用介紹,從而引出今天的主角java
http自帶緩存的使用前提:服務器設置了緩存時間android
response.addHeader("Cache-control", "max-age=10"); //HttpServletResponse response
以上表明瞭在10秒重內不會再請求服務器,此時客戶端開啓了緩存的話,在10內就不會重複請求了
http自帶緩存的策略的使用:web
try { File cacheDir = new File(getCacheDir(), "http");//緩存目錄,在應用目錄的cache文件夾下生成http文件夾 long maxSize = 10 * 1024 * 1024;//緩存大小:byte HttpResponseCache.install(cacheDir, maxSize ); Log.d(TAG, "打開緩存"); } catch (IOException e) { e.printStackTrace(); }
new Thread(new Runnable() { @Override public void run() { try { BitmapFactory.decodeStream((InputStream) new URL("http://192.168.1.7:8080/test.png").getContent()); Log.d(TAG, "下載圖片"); } catch (Exception e) { e.printStackTrace(); } } }).start();
HttpResponseCache cache = HttpResponseCache.getInstalled(); if(cache!=null){ try { cache.delete(); Log.d(TAG, "清空緩存"); } catch (IOException e) { e.printStackTrace(); } }
通過以上步驟,就走完了http緩存的一個流程,在實際應用中,通常會採用本身設計緩存的方法,這裏只是引出緩存這個概念算法
在使用Bitmap的時候,容易使得Bitmap超過系統分配的內存閾值,發麼此時就產生了常見的OOM報錯,所以,優化Bitmap的思路也就在於如何避免OOM的發生canvas
那麼避免OOM發生就要從圖片自己下手了,常見的處理方式即是將圖片進行壓縮,經常使用的壓縮算法有三種:數組
質量壓縮
質量壓縮是經過同化像素點周圍的相近的像素點,從而達到下降文件大小的做用,也就是說其自己的像素多少並無改變,壓縮出來的圖片雖然大小發生了改變,但分辨率沒有發生改變,而在Bitmap中,其是按照像素大小,即圖片的像素多少來計算內存空間的,所以這種方法並不能有效避免OOM,這也就是爲什麼只改變圖片大小對於Bitmap的內存使用沒有做用的緣由
那麼質量壓縮有什麼做用呢?其實它的做用就是減小存儲體積,方便傳輸或者保存緩存
尺寸壓縮
尺寸壓縮的思路就是使用Canvas讀取如今的bitmap,而後對其尺寸進行修改,這裏是真實的改變了圖片的像素大小,因此在Bitmap使用的時候,就會獲得改變尺寸後的大小,那麼就能夠對Bitmap進行有效優化,這種操做通常用於緩存縮略圖服務器
採樣率壓縮
設置圖片的採樣率,下降圖片像素,其原理和尺寸壓縮相似,不過實現的方式不一樣網絡
因爲Bitmap的底層CPP代碼涉及到的東西略多,這裏就簡單介紹下Java層的源碼就好
在Bitmap使用的使用,一般會使用以下三個方法加載來自不一樣地方的圖片
BitmapFactory.decodeFile(path) BitmapFactory.decodeResource(res, id) BitmapFactory.decodeStream(is)
那麼在BitmapFactory
源代碼中,這三個方法是怎麼處理的呢?
打開源代碼,咱們能夠看到:
public static Bitmap decodeFile(String pathName) { return decodeFile(pathName, null); } ··· public static Bitmap decodeResource(Resources res, int id) { return decodeResource(res, id, null); } ··· public static Bitmap decodeStream(InputStream is) { return decodeStream(is, null, null); }
在這裏又傳遞了一次參數,那麼找到這三個傳遞參數的方法
public static Bitmap decodeFile(String pathName, Options opts) { Bitmap bm = null; InputStream stream = null; try { stream = new FileInputStream(pathName); bm = decodeStream(stream, null, opts); } catch (Exception e) { /* do nothing. If the exception happened on open, bm will be null. */ Log.e("BitmapFactory", "Unable to decode stream: " + e); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { // do nothing here } } } return bm; }
在decodeFile()
方法中,將傳入文件轉化成了流,送到了decodeStream()
進行處理,其餘就沒作什麼了,那麼咱們再看看decodeResource()
作了啥
public static Bitmap decodeResource(Resources res, int id, Options opts) { Bitmap bm = null; InputStream is = null; try { final TypedValue value = new TypedValue(); is = res.openRawResource(id, value); bm = decodeResourceStream(res, value, is, null, opts); } catch (Exception e) { /* do nothing. If the exception happened on open, bm will be null. If it happened on close, bm is still valid. */ } finally { try { if (is != null) is.close(); } catch (IOException e) { // Ignore } } if (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); } return bm; }
由以上代碼看出,decodeResource()
也只是中轉,把參數設置好,傳到decodeResourceStream()
中去了,那麼順藤摸瓜,看一看這個方法裏幹了啥,因而咱們找到了這個方法
public static Bitmap decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts) { if (opts == null) { opts = new Options(); } if (opts.inDensity == 0 && value != null) { final int density = value.density; if (density == TypedValue.DENSITY_DEFAULT) { opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; } else if (density != TypedValue.DENSITY_NONE) { opts.inDensity = density; } } if (opts.inTargetDensity == 0 && res != null) { opts.inTargetDensity = res.getDisplayMetrics().densityDpi; } return decodeStream(is, pad, opts); }
查看代碼發現,這裏其實也沒有幹嗎,設置了Options參數,這裏涉及到一個像素密度和分辨率的轉化問題,其中,DisplayMetrics.DENSITY_DEFAULT = 160
,這個問題網上有不少討論,這裏簡明說明一下
px = dp * Density
詳細介紹能夠參閱此文:dpi、dip、分辨率、屏幕尺寸、px、density關係以及換算
綜合上面的分析,全部的線索都指向了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; } Bitmap bm = null; Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap"); try { if (is instanceof AssetManager.AssetInputStream) { final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset(); bm = nativeDecodeAsset(asset, outPadding, opts); } else { bm = decodeStreamInternal(is, outPadding, opts); } if (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); } setDensityFromOptions(bm, opts); } finally { Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); } return bm; }
看一看上面的代碼,先是判斷了輸出流,而後新建Bitmap對象,以後開始寫入跟蹤信息,這裏能夠忽略這個寫入跟蹤信息的玩意兒,下一步就是校驗輸入流的來源,是assets裏面的,仍是其餘的,由於位置不同,其處理方法不同,這裏的nativeDecodeAsset()
方法就是JNI的方法了,而decodeStreamInternal
又是啥呢?再往下看看
private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) { // ASSERT(is != null); byte [] tempStorage = null; if (opts != null) tempStorage = opts.inTempStorage; if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE]; return nativeDecodeStream(is, tempStorage, outPadding, opts); }
這裏就是設置了一個數組,以供JNI使用,而後又傳遞到了JNI裏面去了,也就是說這裏調用了兩個JNI的方法,一個是assets目錄,一個是非assets目錄
private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage, Rect padding, Options opts); private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);
那麼JNI中作了什麼事情呢?
static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, jobject padding, jobject options) { jobject bitmap = NULL; std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage)); if (stream.get()) { std::unique_ptr<SkStreamRewindable> bufferedStream( SkFrontBufferedStream::Create(stream.release(), SkCodec::MinBufferedBytesNeeded())); SkASSERT(bufferedStream.get() != NULL); bitmap = doDecode(env, bufferedStream.release(), padding, options); } return bitmap; }
nativeDecodeAsset()
處理與其相似,這裏就再也不展開了,這篇文章進行了詳細分析:android 圖片佔用內存大小及加載解析
那麼,Java層代碼到此分析結束,在Java層,其最終調用的就是decodeStream()
方法了
decodeStream()
方法參數分析由上面的代碼分析,咱們已經知道,BitmapFactory的調用參數能夠設置的由三個InputStream
,Rect
和Options
那麼Options又有什麼參數能夠設置呢?
inDensity
:bitmap的像素密度
inTargetDensity
:bitmap輸出的像素密度
BitmapFactory.Options options = new BitmapFactory.Options(); Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); public void qualitCompress(Bitmap bmp, File file){ int quality = 50; //0~100 ByteArrayOutputStream baos = new ByteArrayOutputStream(); bmp.compress(Bitmap.CompressFormat.JPEG, quality, baos); //圖片格式能夠選擇JPEG,PNG,WEBP try { FileOutputStream fos = new FileOutputStream(file); fos.write(baos.toByteArray()); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } }
BitmapFactory.Options options = new BitmapFactory.Options(); Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); public static void sizeCompress(Bitmap bmp,File file){ int ratio = 4; //縮小的倍數 Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Bitmap.Config.ARGB_8888);//這裏設置的是Bitmap的像素格式 Canvas canvas = new Canvas(result); RectF rect = new RectF(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio); canvas.drawBitmap(bmp, null, rect, null); //經過Canvas從新畫入 ByteArrayOutputStream baos = new ByteArrayOutputStream(); result.compress(Bitmap.CompressFormat.JPEG, 100, baos); try { FileOutputStream fos = new FileOutputStream(file); fos.write(baos.toByteArray()); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } }
public static void rateCompress(String filePath, File file){ int inSampleSize = 8; //設置採樣率,數值越大,像素越低 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = false; //爲true的時候不會真正加載圖片,而是獲得圖片的寬高信息 options.inSampleSize = inSampleSize; //採樣率 Bitmap bitmap = BitmapFactory.decodeFile(filePath, options); ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100 ,baos); // 把壓縮後的數據存放到baos中 try { if(file.exists()){ file.delete(); } else { file.createNewFile(); } FileOutputStream fos = new FileOutputStream(file); fos.write(baos.toByteArray()); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } }
以上就是系統自帶的壓縮算法實現Bitmap的處理 質量壓縮:適合網絡傳輸 尺寸壓縮和採樣率壓縮:適合生成縮略圖