問:drawable和mipmap的區別是什么?
答:根據官方說明:
應用圖標的圖片資源存放在mipmap系列文件夾中,而其余圖片存放在drawable系列文件夾中
1、mipmap紋理映射技術會將資源縮放到設備分辨率大小,drawable會將資源縮放到設備匹配的倍數大小
2、官方推薦開發者將位圖等資源放在對應dpi的drawable/下,而不是放在mipmap/下。這樣各種dpi可直接找到對應資源,減少了mipmap精確適配時需要縮放計算,也不會因為圖片縮放導致顯示問題
3、高密度系統的設備去使用低密度目錄下的圖片資源時,會將圖片長寬自動放大以去適應高密度的精度,當然圖片占用的內存會更大。
所以如果能提各種dpi的對應資源那是最好,可以達到較好內存使用效果。如果提供的圖片資源有限,那么圖片資源應該盡量放在高密度文件夾下,這樣可以節省圖片放大的內存開支
打包時,可以根據目標設備打不同的dpi圖片的包上架到應用市場中去,節省APK應用包體積。
問:Bitmap內存占用怎么算的?如加載一張1080*1920的圖片,內存占用多少?
答:Bitmap的內存占用的大小是通過:
寬 * 高 * 單位像素所占字節 //1080 * 1920 * 單位像素所占字節(ARGB值不同,占用字節不同)
Bitmap.Config中有四種不同的ARGB: ALPHA_8、RGB_565、ARGB_4444、ARGB_8888
ALPHA_8:每個像素占8位,沒有色彩,只有透明度A-8 即10801920(8/8/1024/1024)= 1.98M
RGB_565:每個像素每個像素占16位,沒有透明度 5+6+5 = 16 即10801920(16/8/1024/1024) = 3.96M
ARGB_4444:每個像素占16位,4+4+4+4 = 16 即 10801920(16/8/1024/1024) = 3.96M
ARGB_8888:每個像素占32位,8+8+8+8 = 32 即 10801920(32/8/1024/1024) = 7.92M
注意:加載圖片所在內存還和圖片放置的目錄有關系:放在mdpi、xhdpi之下是不一樣的。
在Android 160dpi是系統默認dpi
//獲取圖片bitmap寬高代碼:
Drawable drawable = imageView.getDrawable();
if (drawable != null) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
Log.d(TAG, " width = " + bitmap.getWidth() + " height = " + bitmap.getHeight());
}
如果原圖大小:352 * 484
在320dpi(xhdpi)設備上運行,當圖片放置在xhdpi中時,獲取圖片寬高依然是352 * 484。當圖片放置在mdpi中時,獲取寬高是 704 * 968,設備是320dpi的設備,當放置在mdpi時,系統認為圖片需要放大,xhdpi是mdpi的兩倍,所以獲取bitmap的寬高放大了兩倍。
當圖片都放置在xhdpi時,使用320dpi(xhdpi)設備獲取圖片寬高是352 * 484,當使用480dpi(xxhdpi)設備獲取圖片寬高位 528 * 726,即在480dpi設備上時,xhdpi下的圖片都認為要被放大480/320(3/2)倍。
結論:
1、在同一個設備上,圖片放在依次放在由低到高的分辨率目錄中(mdpi~xxxhdpi),圖片的 Bitmap 內存的大小不斷減小。
2、在同一個分辨率目錄中,依次運行在由低到高的分辨率設備上,圖片的 Bitmap 的大小不斷增加。
所以:如果只使用一套圖片時,盡量把圖片放到最大分辨率目錄中
問:系統如何選擇drawable進行加載
答:Android系統中,在加載圖片時,會根據系統自身的dpi設備大小優先匹配最近的一個drawable目錄,如果當前目錄沒有找到,則向上查找,一直找到nodpi,如果都沒有找到則向下開始查找(肯定能找到,如果找不到編譯器就報錯了)。如:設備hdpi 則優先找drawable-hdpi目錄下的資源,如果沒有則向上 xhdpi、xxhdpi、xxxhdpi、nodpi,都沒有的話則開始查找mdpi...
問:Bitmap導致的OOM如何解決
答:Android加載大圖時極容易產生OOM,采用壓縮算法、緩存、軟引用、及時對不再使用的bitmap對象recycle釋放等方式解決。
通常會有四種壓縮方案:
1、質量壓縮:
/**
* 將圖片 bitmap 壓縮到指定大小 targetSize 以內 ,單位是 kb
* 這里的大小指的是 “文件大小”,而不是 “內存大小”
**/
fun compressQuality(bitmap: Bitmap, targetSize: Int, declineQuality: Int = 10): ByteArray {
val baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
var quality = 100
while ((baos.toByteArray().size / 1024) > targetSize) {
baos.reset()
quality -= declineQuality
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos)
}
return baos.toByteArray()
}
質量壓縮通過減少圖片色彩度,不會減少圖片像素及寬高,所以不會減少加載到內存中所占的內存大小,只會減少圖片所占磁盤的存儲大小,是一種有損壓縮。
2、采樣率壓縮:Options.inSampleSize
/**
* 將圖片 [byteArray] 壓縮到 寬度小于 [targetWidth]、高度小于 [targetHeight]
*
**/
fun compressInSampleSize(byteArray: ByteArray, targetWidth: Int, targetHeight: Int): Bitmap{
val options = BitmapFactory.Options()
//設置inJustDecodeBounds = true 只加載圖片寬高等信息,不加載圖片到內存
options.inJustDecodeBounds = true
BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)
//默認采樣率為1,采樣率 > 1, 采樣率 inSampleSize 只能為 2 的整次冪,比如:2、4、8、16
var inSampleSize = 1
while (options.outWidth / inSampleSize > targetWidth || options.outHeight / inSampleSize > targetHeight) {
inSampleSize *= 2
}
options.inJustDecodeBounds = false
options.inSampleSize = inSampleSize
val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)
return bitmap
}
采樣率壓縮其原理是縮放 bitmap 的尺寸,采樣率inSampleSize為1時不變,2時寬高都變為原來的1/2,所占用內存大小就會變為原來的1/4,以此類推。
由于 inSampleSize 只能為 2 的整次冪,所以無法精確控制大小
3、縮放壓縮:Matrix矩陣
/**
* 將圖片 [bitmap] 壓縮到指定寬高范圍內
**/
fun compressScale(bitmap: Bitmap, targetWidth: Int, targetHeight: Int): Bitmap {
return try {
//計算縮放大小
val scale = Math.min(targetWidth * 1.0f / bitmap.width, targetHeight * 1.0f / bitmap.height)
//創建矩陣對象
val matrix = Matrix()
matrix.setScale(scale, scale)
//利用矩陣對bitmap原始圖片進行壓縮生成新的bitmap
val scaledBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true)
scaledBitmap
} catch (e: Exception) {
e.printStackTrace()
bitmap
}
}
縮放壓縮使用的是通過矩陣對圖片進行縮放,縮放后圖片的 寬度、高度以及占用的內存都會改變。
4、色彩模式壓縮:Options.inPreferredConfig = Bitmap.Config.XXXX
/**
* 將圖片格式更改為 Bitmap.Config.RGB_565,減少圖片占用的內存大小
**/
fun compressRGB565(byteArray: ByteArray): Bitmap {
return try {
val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.RGB_565
val compressedBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)
compressedBitmap
} catch (e: Exception) {
e.printStackTrace()
BitmapFactory.decodeByteArray(ByteArray(0), 0, 0)
}
}
色彩模式壓縮后圖片的寬高不會產生變化,由于圖片的存儲格式改變,與 ARGB_8888 相比,每個像素的占用的字節由 8 變為 4 , 所以圖片占用的內存也為原來的一半。
緩存
簡單說一下緩存吧,后續看Glide源碼時再來了解。目前來講緩存一般有內存緩存和磁盤緩存(網絡緩存也不打算吧)
內存緩存:Android SDK中提供了一個LruCache,用于內存緩存。
磁盤緩存:Android SDK中不提供磁盤緩存的類,但google官方推薦的一個叫DiskLruCache算法。