前言
今天聊聊Bitmap相關的面試題/知識點,看看你是否都弄明白了呢?面試
Bitmap是什麼,怎麼存儲圖片?ide
Bitmap內存如何計算?測試
Bitmap內存 和drawable目錄的關係。優化
Bitmap加載優化?不改變圖片質量的狀況下怎麼優化?code
inJustDecodeBounds是什麼?對象
Bitmap內存複用怎麼實現?進程
高清大圖加載該怎麼處理?圖片
如何跨進程傳遞大圖?內存
Bitmap是什麼,怎麼存儲圖片。
Bitmap,位圖,本質上是一張圖片的內容在內存中的表達形式。它將圖片的內容看作是由存儲數據的有限個像素點組成;每一個像素點存儲該像素點位置的ARGB值,每一個像素點的ARGB值肯定下來,這張圖片的內容就相應地肯定下來。其中,A表明透明度,RGB表明紅綠藍三種顏色通道值。
Bitmap內存如何計算
Bitmap一直都是Android中的內存大戶,計算大小的方式有三種:get
getRowBytes() 這個在 API Level 1添加的,返回的是bitmap一行所佔的大小,須要乘以bitmap的高,才能得出btimap的大小
getByteCount() 這個是在 API Level 12添加的,實際上是對getRowBytes()乘以高的封裝
getAllocationByteCount() 這個是在 API Level 19添加的
這裏我將一張圖片放到項目的drawable-xxhdpi文件夾中,而後經過方法獲取圖片所佔的內存大小:
var bitmap = BitmapFactory.decodeResource(resources, R.drawable.test)
img.setImageBitmap(bitmap)
Log.e(TAG,"dpi = ${resources.displayMetrics.densityDpi}")
Log.e(TAG,"size = ${bitmap.allocationByteCount}")
打印出來的結果是
size=1960000
具體是怎麼計算的呢?
圖片內存=寬 高 每一個像素所佔字節。
這個像素所佔字節又和Bitmap.Config有關,Bitmap.Config是個枚舉類,用於描述每一個像素點的信息,好比:
ARGB_8888。經常使用類型,總共32位,4個字節,分別表示透明度和RGB通道。
RGB_565。16位,2個字節,只能描述RGB通道。
因此咱們這裏的圖片內存計算就得出:
寬700 高700 每一個像素4字節=1960000
Bitmap內存 和drawable目錄的關係
對照表
剛纔的案例,咱們是把圖片放到drawable-xxhdpi文件夾,而drawable-xxhdpi文件夾對應的dpi就是咱們測試手機的dpi—480。因此圖片的內存就是咱們所計算的寬 高 每一個像素所佔字節。
若是咱們把圖片放到其餘的文件夾,好比drawable-hdpi文件夾(對應的dpi是240),會發生什麼呢?
再次打印結果:
size = 7840000
這是由於一張圖片的實際佔用內存大小計算公式是:
佔用內存 = 寬 縮放比例 高 縮放比例 每一個像素所佔字節
這個縮放比例就跟屏幕密度DPI有關了:
縮放比例 = 設備dpi/圖片所在目錄的dpi
因此咱們這張圖片的實際佔用內存位:
寬700 (480/240) 高700 (480/240) 每一個像素4字節 = 7840000
Bitmap加載優化?不改變圖片質量的狀況下怎麼優化?
經常使用的優化方式是兩種:
修改Bitmap.Config
這一點剛纔也說過,不一樣的Conifg表明每一個像素不一樣的佔用空間,因此若是咱們把默認的ARGB_8888改爲RGB_565,那麼每一個像素佔用空間就會由4字節變成2字節了,那麼圖片所佔內存就會減半了。
可能必定程度上會下降圖片質量,可是我實際測試看不出什麼變化。
修改inSampleSize
inSampleSize,採樣率,這個參數是用於圖片尺寸壓縮的,他會在寬高的維度上每隔inSampleSize個像素進行一次採集,從而達到縮放圖片的效果。這種方法只會改變圖片大小,不會影響圖片質量。
val options=BitmapFactory.Options()
options.inSampleSize=2
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.test2,options)
img.setImageBitmap(bitmap)
實際項目中,咱們能夠設置一個與目標圖像大小相近的inSampleSize,來減小實際使用的內存:
fun getImage(): Bitmap {
var options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeResource(resources, R.drawable.test2, options)
// 計算最佳採樣率
options.inSampleSize = getImageSampleSize(options.outWidth, options.outHeight)
options.inJustDecodeBounds = false
return BitmapFactory.decodeResource(resources, R.drawable.test2, options)
}
inJustDecodeBounds是什麼?
上面的例子你們應該發現了,其中有個inJustDecodeBounds,又設置爲true,又設置成false的,總感受畫蛇添足,那麼他究竟是幹嗎呢?
由於咱們要獲取圖片自己的大小,若是直接decodeResource加載一遍的話,那麼就會增長內存了,因此官方提供了這樣一個參數inJustDecodeBounds。若是inJustDecodeBounds爲ture,那麼decode的bitmap爲null,也就是不返回實際的bitmap,只把圖片的大小信息放到了options的值中。
因此這個參數就是用來獲取圖片的大小信息的同時不佔用內存。
Bitmap內存複用怎麼實現?
若是有個需求,是在同一個imageview中能夠加載不一樣的圖片,那咱們須要每次都去新建一個Bitmap對象,佔用新的內存空間嗎?若是源碼交易咱們這樣寫的話:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.actvitiy_bitmap)
btn1.setOnClickListener {
img.setImageBitmap(getBitmap(R.drawable.test))
}
btn2.setOnClickListener {
img.setImageBitmap(getBitmap(R.drawable.test2))
}
}
fun getBitmap(resId: Int): Bitmap {
var options = BitmapFactory.Options()
return BitmapFactory.decodeResource(resources, resId, options)
}
這樣就會Bitmap就會頻繁去申請內存,釋放內存,從而致使大量GC,內存抖動。
爲了防止這種狀況呢,咱們就能夠用到inBitmap參數,用於Bitmap的內存複用。這樣同一塊內存空間就能夠被多個Bitmap對象複用,從而減小了頻繁的GC。
val options by lazy {
BitmapFactory.Options()
}
val reuseBitmap by lazy {
options.inMutable = true
BitmapFactory.decodeResource(resources, R.drawable.test, options)
}
fun getBitmap(resId: Int): Bitmap {
options.inMutable = true
options.inBitmap = reuseBitmap
return BitmapFactory.decodeResource(resources, resId, options)
}
這裏有幾個要注意的點
inBitmap要和 inMutable屬性配套使用,不然將沒法複用。
在 Android 4.4以前,只能重用相同大小的 Bitmap 內存區域; 4.4以後只要複用內存空間的Bitmap對象大小比 inBitmap指向的內存空間要小便可。
因此通常在複用以前,還要判斷下,新的Bitmap內存是否是小於能夠複用的Bitmap內存,而後才能進行復用。