android 關於Bitmap壓縮處理解析

諸葛佩奇發表於2018-07-10

在開發過程中,我們或多或少的都會接觸到Bitmap這個東西,用的不好的話就會出現OOM問題,同時,也會有壓縮的需求,所以今天就來理一理關於Bitmap的一些內容。

關於Bitmap的Config的理解

A:透明度 R:紅色 G:綠 B:藍

/**
     * Possible bitmap configurations. A bitmap configuration describes
     * how pixels are stored. This affects the quality (color depth) as
     * well as the ability to display transparent/translucent colors.
     */
    public enum Config {
      
        ALPHA_8     (1),
 
        RGB_565     (3),
        
        @Deprecated
        ARGB_4444   (4),

         */
        ARGB_8888   (5);

    }
複製程式碼

Bitmap.Config ARGB_4444:每個畫素佔四位,即A=4,R=4,G=4,B=4,那麼一個畫素點佔4+4+4+4=16位

Bitmap.Config ARGB_8888:每個畫素佔四位,即A=8,R=8,G=8,B=8,那麼一個畫素點佔8+8+8+8=32位

Bitmap.Config RGB_565:每個畫素佔四位,即R=5,G=6,B=5,沒有透明度,那麼一個畫素點佔5+6+5=16位

Bitmap.Config ALPHA_8:每個畫素佔四位,只有透明度,沒有顏色。

記憶體計算

一張 1024 * 1024 畫素,採用ARGB8888格式,一個畫素32位,每個畫素就是4位元組,佔有記憶體就是4M若採用RGB565,一個畫素16位,每個畫素就是2位元組,佔有記憶體就是2M。

Glide載入圖片預設格式RGB565,Picasso為ARGB8888,預設情況下,Glide佔用記憶體會比Picasso低,色彩不如Picasso鮮豔,自然清晰度就低。

通常我們優化Bitmap時,當需要做效能優化或者防止OOM(Out Of Memory),我們通常會使用Bitmap.Config.RGB_565這個配置,因為Bitmap.Config.ALPHA_8只有透明度,顯示一般圖片沒有意義,Bitmap.Config.ARGB_4444顯示圖片不清楚,Bitmap.Config.ARGB_8888佔用記憶體最多。

圖片載入

如果我們想要載入一張大圖到記憶體中,如果不進行壓縮的話,那麼很顯然就會出現OOM的崩潰,

譬如我們載入一張5440*3000的大圖到手機上面,如果不進行壓縮處理的話,那麼就會出現OOM。

程式碼清單,

java.lang.OutOfMemoryError
android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
android.graphics.BitmapFactory.decodeStreamInternal(BitmapFactory.java:703)
android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:679)
android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:446)
android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:480)
com.example.ly.bitmapdemo.MainActivity.onCreate(MainActivity.java:21)
                                                                               
複製程式碼

導致這種情況的發生的根據原因就是記憶體溢位,Android給每個APP的記憶體都是有限的,所以不能容忍這種情況的發生,所以我們就必須進行壓縮一下。

圖片壓縮

我們在上傳一張圖片到伺服器時一般都會先進行壓縮一下,這樣不僅可以節省流量同時也可以節約上傳的時候。最近碰到專案裡碰到一個問題:頭像上傳時總是出現超時的問題,客戶那邊出現的頻率非常高,而我的手機卻基本沒出現過,檢查了一下原因,大概有兩個:

  1. 客戶的網速不是太好,3G速度不夠,上傳圖片需要很長時間導致超時產生。
  2. 圖片的體積很大,佔了3、4M,沒經過壓縮直接上傳,導致超時產生。

針對以上兩種情況,主要對於第二種原因進行優化。總結來說就是將圖片進行壓縮再上傳,經過一系列的操作,發現超時現象基本不會出現了,原先2M的圖片,經過壓縮只有50Kb不到,壓縮率達到60%,效果很明顯。

原理分析

如何將一張大圖壓縮到100kb以下並且保持不失真的特性?這就需要用到下面這個類了。

主要運用BitmapFactory.Options

BitmapFactory.Options縮放圖片主要用到inSample取樣率

inSample = 1,取樣後圖片的寬高為原始寬高 inSample > 1,例如2,寬高均為原圖的寬高的1/2

一個採用ARGB8888的1024 * 1024 的圖片 inSample = 1,佔用記憶體就 1024 * 1024 * 4 = 4M inSample = 2,佔用記憶體就 512 * 512 * 4 = 1M

BitmapFactory 給我們提供了一個解析圖片大小的引數類 BitmapFactory.Options ,把這個類的物件的 inJustDecodeBounds 引數設定為 true,這樣解析出來的 Bitmap 雖然是個 null,但是 options 中可以得到圖片的寬和高以及圖片的型別。得到了圖片實際的寬和高之後我們就可以進行壓縮設定了,主要是計算圖片的取樣率。

 // 第一次解析將inJustDecodeBounds設定為true,用以獲取圖片大小,並且不需要將Bitmap物件載入到記憶體中
 BitmapFactory.Options options = new BitmapFactory.Options();
 options.inJustDecodeBounds = true;
 BitmapFactory.decodeFile(filePath, options); // 第一次解析
複製程式碼

接下來就需要進行選定壓縮的取樣率了。目前市場上的主流手機解析度一般最低是720*1280了所以就按照此解析度進行壓縮

		//原始圖片的寬度與720的比值,然後向上取整這裡為8
		int wRatio = (int) Math.ceil(options.outWidth / (float) 720);
		//原始圖片的高度與1280的比值,然後向上取整這裡為3
        int hRatio = (int) Math.ceil(options.outHeight / (float) 1280);

		//獲取取樣率
        if (wRatio > 1 && hRatio > 1) {
            if (wRatio > hRatio) {
                options.inSampleSize = wRatio;
            } else {
                options.inSampleSize = hRatio;
            }
        }
複製程式碼

經過上面這個取樣率進行壓縮後的寬和高肯定是小於7201270的,我們計算的結果是:680375

我們來實際比較一下壓縮結果:

原先:5440*3000 如果採用ARGB_8888模式的話,那麼如果不壓縮直接載入到記憶體的話,那麼它將佔:

5440/1024 * 3000/1024 *4 = 62.25M,不崩潰才怪呢~

那麼現在:680*375 見證奇蹟的時候,680/1024 * 375/1024 *4=0.9M

兩者一比較的話,那麼效果還是比較明顯的,相差大約64倍,所以還是可以的。當然了經過上面的壓縮方法,我們將壓縮後的圖片上傳到伺服器的話,那麼將會大大的減少流量同時也會減少上傳超時的機率的。

當然了,如果還嫌大的話,我們可以進一步增加壓縮的比例,可以設定成480*800,那麼這樣的話,質量肯定是有所下降的。


參考連結

1、http://www.jianshu.com/p/3950665e93e6

2、http://www.jianshu.com/p/9aaed7310180


關於作者:

1. 簡書 http://www.jianshu.com/users/18281bdb07ce/latest_articles

2. 部落格 http://crazyandcoder.github.io/

3. github https://github.com/crazyandcoder

4. 開源中國 https://my.oschina.net/crazyandcoder/blog

相關文章