在咱們編寫
Android
程序的時候,幾乎永遠逃避不了圖片壓縮的難題。除了應用圖標以外,咱們所要顯示的圖片基本上只有兩個來源:java
- 來自網絡下載
- 本地相冊中加載
不論是網上下載下來的也好,仍是從系統圖片庫中讀取的圖片,都有一個相同的特色:像素一幫較高。同時咱們都知道,Android
系統分配給咱們每一個應用的內存是有限的,因爲解析、加載一張圖片,須要佔用的內存大小,是遠大於圖片自身大小的。因此,這時程序就可能由於佔用了過多的內存,從而出現OOM
現象。那麼什麼是 OOM
呢?android
Exception java.lang.OutOfMemoryError: Failed to allocate a 916 byte allocation with 8388608 free bytes and 369MB until OOM; failed due to fragmentation (required continguous free 65536 bytes for a new buffer where largest contiguous free 32768 bytes) java.nio.CharBuffer.allocate (CharBuffer.java:54) java.nio.charset.CharsetDecoder.allocateMore (CharsetDecoder.java:226) java.nio.charset.CharsetDecoder.decode (CharsetDecoder.java:188) org.java_websocket.util.Charsetfunctions.stringUtf8 (Charsetfunctions.java:77) org.java_websocket.WebSocketImpl.decodeFrames (WebSocketImpl.java:375) org.java_websocket.WebSocketImpl.decode (WebSocketImpl.java:158) org.java_websocket.client.WebSocketClient.run (WebSocketClient.java:185) java.lang.Thread.run (Thread.java:818) 複製代碼
OOM
即 OutOfMemory
異常,也就是咱們所說的 內存溢出 ,其通常表現爲應用閃退等現象。那麼咱們該如何下手去解決呢?git
首先咱們發現,咱們所加載的這些圖片的分辨率,要比咱們手機屏幕高得多,更有甚者,咱們在一個拇指大的控件上,去加載一個 4k 大圖是徹底沒有必要的,也就是說,若是咱們能讓每一個控件上都去顯示相應大小的圖片,那麼這個問題也就迎刃而解了github
那麼,要怎樣才能達到圖片與控件的對號入座?這時咱們就引進了圖片壓縮的方案:web
那麼就讓咱們開始吧:編程
咱們都知道,Android 向咱們提供了 BitmapFactory
這個類,在這個類中有着諸如:decodeResource()
decodeFile()
decodeStream()
等:websocket
public static Bitmap decodeResource(Resources res, int id) public static Bitmap decodeFile(String pathName) public static Bitmap decodeStream(InputStream is) 複製代碼
其餘的方法這裏就很少說了,由於在源碼中咱們可有i看到,幾乎全部的方法,最後都會將圖片解析爲流的形式,最後調用 decodeStream()
方法,實例化出咱們的 Bitmap
對象。網絡
雖然這些方法對咱們是再熟悉不過的了,但對於某些初學者而言,卻常常忽略了一個重要的內部類 :BitmapFactory.Options
,然而他確實咱們圖片壓縮必不可少的,爲何須要這個參數呢?Options
的對象用於肯定須要生成的 Bitmap 即目標圖片的參數。 他的用法很簡單,咱們先 new 一個 BitmapFactory.Options
對象。再去調用含有 Options
參數的方法,如app
public static Bitmap decodeResource(Resources res, int id, Options opts)
public static Bitmap decodeResourceStream(@Nullable Resources res,@Nullable TypedValue value,@Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts)
調用完以後咱們發現,除了方法放回給咱們一個實例化出來的 Bitmap
圖片以外,這個 Options
對象中長度、寬度、類型等等屬性,也都被設置成了了咱們圖片的相應屬性。因此,咱們很容易想到:經過將 Options
對象傳入,來得到圖片的原始尺寸,爲後期的壓縮作準備,說幹就幹,咱們將 Options
對象,和 Resources
中一張 4k 圖片的id
一塊傳入上訴方法中,來嘗試得到它的尺寸,結果咱們發現:程序 OOM
崩潰了!框架
爲何會發生這種狀況?首先咱們想一想咱們爲何要得到這個Options
對象?時爲了得到圖片的尺寸大小;那咱們爲何要得到原圖尺寸大小?是爲了按照原圖尺寸和控件尺寸的比例,將其壓縮爲適合顯示的大小?那咱們又爲何要去壓縮它爲合適的大小呢?是由於若是按照原大小去調用相應的 decode...()
方法解析圖片,會致使內存佔有率太高觸發OOM
異常,進而致使程序崩潰啊!沒想到的是:結果咱們爲了得到 Options
而調用了相應的 decode...()
方法,的確 Options
是複製了,但因爲該方法適用於生成圖片,也就是 Bitmap
對象的。因此程序也在解析這張超大圖的過程當中OOM
崩潰了
那麼難道就沒方法了嗎?
有的,我以前說過:Option
內部有着衆多參數,其中有一個叫作: inJustDecodeBounds
。這個參數默認值爲false
。但若是咱們先把這個參數設置爲 true 時,該方法便不在會去生成相應的 Bitmap
,而僅僅是去測量圖片的各類屬性,如長度、寬度、類型等等,而後放回一個 null
。因此,咱們很容易想到:能夠先經過將 inJustDecodeBounds
的值設爲 true
,再去調用相應的相應的 decode...()
方法,最後再將inJustDecodeBounds
的值改回 false
。這種作法有兩個好處:
OOM
而崩潰。但這偏偏是被不少人所忽略的一點。
public static void calculateOptionsById(@NonNull Resources res,@NonNull BitmapFactory.Options options, int imgId) {
BitmapFactory.decodeResource(res, imgId, options);
}
複製代碼
你們可能發現,這裏只將
inJustDecodeBounds
設爲true
卻沒有改回false
,這是由於得到Options
只是圖片壓縮的第一步,咱們在後續方法中將會進行修改
咱們繼續看 Options
的構成。咱們發現,其中有個名爲 inSampleSize
的數據成員,他就是關鍵所在,那麼他有着什麼意義呢?
這裏我給你們舉個例子,好比我這有張 4000*1000 像素的圖片:
inSampleSize
的值設爲 4
時,最後生成出來的圖片大小將會是:1000 x 250 像素inSampleSize
的值設爲5
時,最後生成出來的圖片大小將會是:800 x 200 像素。這是個什麼概念?這不只僅是長寬都變爲原來四分之一或者五分之一這麼簡單,而是其圖片大小,直接變爲原圖的 1/(n^2)
!也就是說:
2MB
,那麼當 inSampleSize
賦值爲4
加載時就只須要 0.125MB
inSampleSize
賦值爲 5
呢?只須要 0.08 MB
!連100k
都不到的小圖啊!public static int calculateInSamplesizeByOptions(@NonNull BitmapFactory.Options options, int reqWidth, int reqHeight) {
int inSamplesize = 1;
int originalWidth = options.outWidth;
int originalHeight = options.outHeight;
if (originalHeight > reqHeight || originalWidth > reqWidth) {
int heightRatio = originalHeight / reqHeight;
int widthRatio = originalWidth / reqWidth;
inSamplesize = heightRatio > widthRatio ? heightRatio : widthRatio;
}
return inSamplesize;
}
複製代碼
咱們發現,這裏我先計算出了,原圖尺寸與目標大小大比例,在三目運算符中,將inSamplesize
賦值爲較大的一個。爲何不用小的那一個呢?這裏我就賣個關子,你們能夠在評論區中發表本身的想法
通過前面的兩個步驟,想必你們已經能勾勒處這最後一步的作法了,思路很是簡單:
Options
對象Options 的 inJustDecodeBounds
設置爲true
calculateOptionsById
得到原圖尺寸到Options
中calculateInSamplesizeByOptions
得到相應的inSampleSize
對象Options
的inJustDecodeBounds
改回 false
decode...()
方法(這裏是 decodeResource
)得到壓縮後的 Bitmap
對象public static Bitmap decodeBitmapById (@NonNull Resources res, int resId, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
calculateOptionsById(res, options, resId);
options.inSampleSize = calculateInSamplesizeByOptions(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(res, resId, options);
return bitmap;
}
複製代碼
很是棒,咱們趕忙看看效果:
太棒了,幾乎和原圖效果一摸同樣,但軟件運行的流暢性確大大提升了!可是,這真的就完美了嗎?
最求完美的咱們可能會有個想法:若是調用咱們方法的人,或者說特殊時候的咱們。不想用這個已經寫好的 decodeBitmapById
方法,而是像本身經過前兩個方法:calculateOptionsById
calculateInSamplesizeByOptions
來實現圖片壓縮功能,這是問題就出現了:
calculateOptionsById
前可能忘記,設置 inJustDecodeBound
爲 true
,進而致使計算超大圖時,直接發生 OOM
calculateInSamplesizeByOptions
後可能忘記,設置inJustDecodeBounds
爲 false
,進而致使沒法得到Bitmap
對象,一臉懵逼calculateInSamplesizeByOptions
沒把沒回的值賦給 options.inSampleSize
,白忙活一場因此,咱們須要在優化一下:
首先,在calculateOptionsById
中,默認將 options.inJustDecodeBounds
設置爲true
:
public static void calculateOptionsById(@NonNull Resources res,@NonNull BitmapFactory.Options options, int imgId) {
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, imgId, options);
}
複製代碼
其次,在 calculateInSamplesizeByOptions
最後,默認將 options.inJustDecodeBounds
設置爲false
:
public static int calculateInSamplesizeByOptions(@NonNull BitmapFactory.Options options, int reqWidth, int reqHeight) {
int inSamplesize = 1;
int originalWidth = options.outWidth;
int originalHeight = options.outHeight;
if (originalHeight > reqHeight || originalWidth > reqWidth) {
int heightRatio = originalHeight / reqHeight;
int widthRatio = originalWidth / reqWidth;
inSamplesize = heightRatio > widthRatio ? heightRatio : widthRatio;
}
options.inJustDecodeBounds = false;
return inSamplesize;
}
複製代碼
爲何不在該方法後面,對 options.inSampleSize
進行賦值呢?這主要是防止,有時咱們可能只想獲得計算相應比例來作其餘操做,而不想改變原有屬性,因此是否賦值,就交給用戶去選擇吧
好了,到這裏爲止,歷時有關圖片壓縮的全部坑坑窪窪都已經總結好了,咱們從頭理以邊思路:
options.inJustDecodeBounds
參數賦值true
時,不生成圖片的特性,將原圖尺寸保存在 Options
中options
中原圖尺寸與目標(控件)尺寸的比例,對 options.inSampleSize
進行設置Android 讓你的 Room 搭上 RxJava 的順風車 從重複的代碼中解脫出來
ViewModel 和 ViewModelProvider.Factory:ViewModel 的建立者
單例模式-全局可用的 context 對象,這一篇就夠了
縮放手勢 ScaleGestureDetector 源碼解析,這一篇就夠了
Android 屬性動畫框架 ObjectAnimator、ValueAnimator ,這一篇就夠了
看完這篇再不會 View 的動畫框架,我跪搓衣板
Android 自定義時鐘控件 時針、分針、秒針的繪製這一篇就夠了
android 自定義控件之-繪製鐘表盤
Android 進階自定義 ViewGroup 自定義佈局
Android 逐幀動畫( Drawable 動畫),這一篇就夠了
按期分享Android開發
溼貨,追求文章幽默與深度
的完美統一。
源碼 Demo 連接:Drop 我第一次寫的 Android 項目,但願你們點歌 star~ 謝謝!