最近線上有用戶反饋在App使用過程當中遇到大圖的時,App異常的卡頓,甚至會出現崩潰的狀況。後來排查了一番,發現一個同事在處理圖片時,直接原圖加載沒有作任何「壓縮」。這個case的出現,也就引出了這篇文章的必要性。android
我們平常開發過程當中,都會使用各類各樣的圖片庫好比Glide。因爲全部圖片操做都是一股腦的交給圖片庫去處理,因此即便在遇到大圖加載的時候,也沒法「復現」這類問題。程序員
由於主流的圖片庫都幫我們對大圖進行了處理(正印證了那句話:當你能輕鬆進去的時候,你就該明白,不是你厲害,只是有人在前面替你開路——「魯訊」)。api
既然話都說開了,我們做爲新時代下的福報程序員,那就必需要在這條路上探探深淺。其實圖片壓縮的方式有不少種,今天我們只要一種,那就是Google原生的高效加載大圖的方案。app
進行壓縮以前,我們先來感覺一下不壓縮會怎樣...ide
我隨便new了一下項目,搞了一個這樣的圖:學習
其實也不是特別大,就是一張1080P的圖。測試
而後隨便的用一個ImageView去加載一下:ui
iv.setImageResource(R.drawable.test)
複製代碼
當我嘗試run的時候,我高估了個人測試機....沒有加載出來,就直接崩了。Logcat也是夠直接,無情吐槽: this
這麼一張圖,一共須要132710400Bytes的內存,也就是132m....等等,不對?!分辨率1080 * 1920的圖片怎麼可能會使用100+m的內存?google
咱們都知道,正常一個圖片被加載到內存裏的文件大小 = 圖片分辨率的寬 * 圖片分辨率的高 * 色彩格式。帶入這個公式內存大小 = 1080 * 1920 * 4 = 7.9m,毫不多是100+m這麼多!
這裏可能有朋友會有疑問,爲啥JPEG的格式會4,JPEG格式沒有alpha通道,不該該佔這麼大的空間。其實具體幾,仍是須要看這張圖最終Bitmap.Config解出來的值,我這張圖解出來是ARGB_8888,因此仍是要*4。
若是你也有這個疑問,那麼接下來的內容你要好好看咯。這個知識點恐怕是盲區...
做爲一個番外的內容部分。這一章節其實和圖片壓縮沒有什麼關係,只是額外聊一聊drawble這個文件夾
上述問題的根本緣由就是在於文件放置的位置,我只在drawble文件夾下放置了圖片資源。
因此...這種case下,若是加載這個資源的手機是一個高密度屏幕,那麼這張圖片被展現時,並不是1080 * 1920...
接下來我們來看一看,爲何資源文件隨便放會帶來這麼大的問題!(如下內容,部分來自於官方文檔)
文檔中提到,若是資源提供不當,會致使縮放失真...。這裏爲何系統要進行縮放其實也很好理解:
對於系統來講,若是它向下(低密度)才找到須要引用的資源文件,那麼最佳的策略即是將找到的圖片資源總體放大。由於那裏的圖,預期是給低分辨率手機準備的。
那麼同理,若是系統向上(高密度)找到了須要引用的資源文件,那麼縮小無疑是最佳的選擇。由於那裏的圖,預期是給高分辨率手機準備的。
因此基於此,上述中OOM的內存值132710400bytes是這麼算出來的:1080 * 4(這個4是手機dpi640 / 資源dpi160 所得) * 1920 * 4 * 4
小貼士:dpi = 手機分辨率長寬各自平方之和開方,除以對角線長度(單位英寸)。 固然咱們也能夠經過api:resources.displayMetrics.xdpi。這裏獲得的值就基本等於當前手機的dpi
因此,強制加載這麼大的一張圖,是否是不負責任!這麼大,硬往裏塞,擱誰誰受得了?
既然我們已經明確硬來是不行了,因此仍是要採起一些技巧的。文章中開篇就道出了問題的所在:
Images come in all shapes and sizes. In many cases they are larger than required for a typical application user interface (UI). For example, the system Gallery application displays photos taken using your Android devices's camera which are typically much higher resolution than the screen density of your device.
Given that you are working with limited memory, ideally you only want to load a lower resolution version in memory. The lower resolution version should match the size of the UI component that displays it. An image with a higher resolution does not provide any visible benefit, but still takes up precious memory and incurs additional performance overhead due to additional on the fly scaling.
簡單翻譯一下就是:太大就不要硬塞,縮到合適的尺寸再塞
文檔裏還有比較有意思的一句話:There are several libraries that follow best practices for loading images. You can use these libraries in your app to load images in the most optimized manner. We recommend the Glide
官方推薦,最爲致命
其實文檔中直接貼出了能夠Ctrl +C/V就能使用的代碼:
imageView.setImageBitmap(
decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100)
)
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
// Raw height and width of image
val (height: Int, width: Int) = options.run { outHeight to outWidth }
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight: Int = height / 2
val halfWidth: Int = width / 2
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
fun decodeSampledBitmapFromResource( res: Resources, resId: Int, reqWidth: Int, reqHeight: Int ): Bitmap {
// First decode with inJustDecodeBounds=true to check dimensions
return BitmapFactory.Options().run {
inJustDecodeBounds = true
BitmapFactory.decodeResource(res, resId, this)
// Calculate inSampleSize
inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)
// Decode bitmap with inSampleSize set
inJustDecodeBounds = false
BitmapFactory.decodeResource(res, resId, this)
}
}
複製代碼
代碼很好理解,就是將須要加載的圖片,按目標所需的加載尺寸進行一次採樣,經過採樣的值進行等比縮放。
不過這裏有一個有趣的細節:官方的代碼裏是將採樣結果進行了 * 2 (inSampleSize *= 2
)。當時經過實戰咱們會發現,inSampleSize
並不必定要傳2的冪,傳3傳5傳其餘也是有效果的。
文檔中提到這麼一句話:
Note: A power of two value is calculated because the decoder uses a final value by rounding down to the nearest power of two, as per the inSampleSize documentation.(以2的冪做爲計算結果,是根據inSampleSize文檔,解碼器經過四捨五入到最接近的2的冪來使用最終值。)
按照文檔的解釋inSampleSize爲2/3時,效果同樣,畢竟3最接近2的冪的值仍是2。當時事實跑起來會發現,2和3的結果並不同:
當inSampleSize = 3時,圖片長和寬就是比減小了3倍...因此真是不知道官網的葫蘆裏賣的什麼藥。
到這,該嘮的基本也就嘮完了...內容並不深奧,但也算是必備的知識點~