問:drawable和mipmap的區(qū)別是什么霞溪?
答:根據(jù)官方說明:
應(yīng)用圖標(biāo)的圖片資源存放在mipmap系列文件夾中孵滞,而其余圖片存放在drawable系列文件夾中
1、mipmap紋理映射技術(shù)會(huì)將資源縮放到設(shè)備分辨率大小鸯匹,drawable會(huì)將資源縮放到設(shè)備匹配的倍數(shù)大小
2坊饶、官方推薦開發(fā)者將位圖等資源放在對(duì)應(yīng)dpi的drawable/下,而不是放在mipmap/下殴蓬。這樣各種dpi可直接找到對(duì)應(yīng)資源匿级,減少了mipmap精確適配時(shí)需要縮放計(jì)算,也不會(huì)因?yàn)閳D片縮放導(dǎo)致顯示問題
3染厅、高密度系統(tǒng)的設(shè)備去使用低密度目錄下的圖片資源時(shí)痘绎,會(huì)將圖片長(zhǎng)寬自動(dòng)放大以去適應(yīng)高密度的精度,當(dāng)然圖片占用的內(nèi)存會(huì)更大肖粮。
所以如果能提各種dpi的對(duì)應(yīng)資源那是最好孤页,可以達(dá)到較好內(nèi)存使用效果。如果提供的圖片資源有限涩馆,那么圖片資源應(yīng)該盡量放在高密度文件夾下行施,這樣可以節(jié)省圖片放大的內(nèi)存開支
打包時(shí),可以根據(jù)目標(biāo)設(shè)備打不同的dpi圖片的包上架到應(yīng)用市場(chǎng)中去凌净,節(jié)省APK應(yīng)用包體積悲龟。
問:Bitmap內(nèi)存占用怎么算的?如加載一張1080*1920的圖片冰寻,內(nèi)存占用多少须教?
答:Bitmap的內(nèi)存占用的大小是通過:
寬 * 高 * 單位像素所占字節(jié) //1080 * 1920 * 單位像素所占字節(jié)(ARGB值不同,占用字節(jié)不同)
Bitmap.Config中有四種不同的ARGB: ALPHA_8、RGB_565轻腺、ARGB_4444乐疆、ARGB_8888
ALPHA_8:每個(gè)像素占8位,沒有色彩贬养,只有透明度A-8 即10801920(8/8/1024/1024)= 1.98M
RGB_565:每個(gè)像素每個(gè)像素占16位挤土,沒有透明度 5+6+5 = 16 即10801920(16/8/1024/1024) = 3.96M
ARGB_4444:每個(gè)像素占16位,4+4+4+4 = 16 即 10801920(16/8/1024/1024) = 3.96M
ARGB_8888:每個(gè)像素占32位误算,8+8+8+8 = 32 即 10801920(32/8/1024/1024) = 7.92M
注意:加載圖片所在內(nèi)存還和圖片放置的目錄有關(guān)系:放在mdpi仰美、xhdpi之下是不一樣的。
在Android 160dpi是系統(tǒng)默認(rèn)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)設(shè)備上運(yùn)行咖杂,當(dāng)圖片放置在xhdpi中時(shí),獲取圖片寬高依然是352 * 484蚊夫。當(dāng)圖片放置在mdpi中時(shí)诉字,獲取寬高是 704 * 968,設(shè)備是320dpi的設(shè)備知纷,當(dāng)放置在mdpi時(shí)壤圃,系統(tǒng)認(rèn)為圖片需要放大,xhdpi是mdpi的兩倍琅轧,所以獲取bitmap的寬高放大了兩倍伍绳。
當(dāng)圖片都放置在xhdpi時(shí),使用320dpi(xhdpi)設(shè)備獲取圖片寬高是352 * 484鹰晨,當(dāng)使用480dpi(xxhdpi)設(shè)備獲取圖片寬高位 528 * 726墨叛,即在480dpi設(shè)備上時(shí),xhdpi下的圖片都認(rèn)為要被放大480/320(3/2)倍模蜡。
結(jié)論:
1、在同一個(gè)設(shè)備上扁凛,圖片放在依次放在由低到高的分辨率目錄中(mdpi~xxxhdpi)忍疾,圖片的 Bitmap 內(nèi)存的大小不斷減小。
2谨朝、在同一個(gè)分辨率目錄中卤妒,依次運(yùn)行在由低到高的分辨率設(shè)備上,圖片的 Bitmap 的大小不斷增加字币。
所以:如果只使用一套圖片時(shí)则披,盡量把圖片放到最大分辨率目錄中
問:系統(tǒng)如何選擇drawable進(jìn)行加載
答:Android系統(tǒng)中,在加載圖片時(shí)洗出,會(huì)根據(jù)系統(tǒng)自身的dpi設(shè)備大小優(yōu)先匹配最近的一個(gè)drawable目錄士复,如果當(dāng)前目錄沒有找到,則向上查找,一直找到nodpi阱洪,如果都沒有找到則向下開始查找(肯定能找到便贵,如果找不到編譯器就報(bào)錯(cuò)了)。如:設(shè)備hdpi 則優(yōu)先找drawable-hdpi目錄下的資源冗荸,如果沒有則向上 xhdpi承璃、xxhdpi、xxxhdpi蚌本、nodpi盔粹,都沒有的話則開始查找mdpi...
問:Bitmap導(dǎo)致的OOM如何解決
答:Android加載大圖時(shí)極容易產(chǎn)生OOM,采用壓縮算法程癌、緩存玻佩、軟引用、及時(shí)對(duì)不再使用的bitmap對(duì)象recycle釋放等方式解決席楚。
通常會(huì)有四種壓縮方案:
1咬崔、質(zhì)量壓縮:
/**
* 將圖片 bitmap 壓縮到指定大小 targetSize 以內(nèi) ,單位是 kb
* 這里的大小指的是 “文件大小”,而不是 “內(nèi)存大小”
**/
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()
}
質(zhì)量壓縮通過減少圖片色彩度烦秩,不會(huì)減少圖片像素及寬高垮斯,所以不會(huì)減少加載到內(nèi)存中所占的內(nèi)存大小,只會(huì)減少圖片所占磁盤的存儲(chǔ)大小只祠,是一種有損壓縮兜蠕。
2、采樣率壓縮:Options.inSampleSize
/**
* 將圖片 [byteArray] 壓縮到 寬度小于 [targetWidth]抛寝、高度小于 [targetHeight]
*
**/
fun compressInSampleSize(byteArray: ByteArray, targetWidth: Int, targetHeight: Int): Bitmap{
val options = BitmapFactory.Options()
//設(shè)置inJustDecodeBounds = true 只加載圖片寬高等信息熊杨,不加載圖片到內(nèi)存
options.inJustDecodeBounds = true
BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)
//默認(rèn)采樣率為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時(shí)不變,2時(shí)寬高都變?yōu)樵瓉淼?/2蛮位,所占用內(nèi)存大小就會(huì)變?yōu)樵瓉淼?/4较沪,以此類推。
由于 inSampleSize 只能為 2 的整次冪失仁,所以無法精確控制大小
3尸曼、縮放壓縮:Matrix矩陣
/**
* 將圖片 [bitmap] 壓縮到指定寬高范圍內(nèi)
**/
fun compressScale(bitmap: Bitmap, targetWidth: Int, targetHeight: Int): Bitmap {
return try {
//計(jì)算縮放大小
val scale = Math.min(targetWidth * 1.0f / bitmap.width, targetHeight * 1.0f / bitmap.height)
//創(chuàng)建矩陣對(duì)象
val matrix = Matrix()
matrix.setScale(scale, scale)
//利用矩陣對(duì)bitmap原始圖片進(jìn)行壓縮生成新的bitmap
val scaledBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true)
scaledBitmap
} catch (e: Exception) {
e.printStackTrace()
bitmap
}
}
縮放壓縮使用的是通過矩陣對(duì)圖片進(jìn)行縮放,縮放后圖片的 寬度萄焦、高度以及占用的內(nèi)存都會(huì)改變控轿。
4、色彩模式壓縮:Options.inPreferredConfig = Bitmap.Config.XXXX
/**
* 將圖片格式更改為 Bitmap.Config.RGB_565,減少圖片占用的內(nèi)存大小
**/
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)
}
}
色彩模式壓縮后圖片的寬高不會(huì)產(chǎn)生變化解幽,由于圖片的存儲(chǔ)格式改變贴见,與 ARGB_8888 相比,每個(gè)像素的占用的字節(jié)由 8 變?yōu)?4 躲株, 所以圖片占用的內(nèi)存也為原來的一半片部。
緩存
簡(jiǎn)單說一下緩存吧,后續(xù)看Glide源碼時(shí)再來了解霜定。目前來講緩存一般有內(nèi)存緩存和磁盤緩存(網(wǎng)絡(luò)緩存也不打算吧)
內(nèi)存緩存:Android SDK中提供了一個(gè)LruCache档悠,用于內(nèi)存緩存。
磁盤緩存:Android SDK中不提供磁盤緩存的類望浩,但google官方推薦的一個(gè)叫DiskLruCache算法辖所。