Bitmap知識點集合

前言

今天聊聊Bitmap相關的面試題/知識點,看看你是否都弄明白了呢?html

  • Bitmap是什麼,怎麼存儲圖片?面試

  • Bitmap內存如何計算?緩存

  • Bitmap內存 和drawable目錄的關係。socket

  • Bitmap加載優化?不改變圖片質量的狀況下怎麼優化?ide

  • inJustDecodeBounds是什麼?學習

  • Bitmap內存複用怎麼實現?測試

  • 高清大圖加載該怎麼處理?優化

  • 如何跨進程傳遞大圖?this

Bitmap是什麼,怎麼存儲圖片。

Bitmap,位圖,本質上是一張圖片的內容在內存中的表達形式。它將圖片的內容看作是由存儲數據的有限個像素點組成;每一個像素點存儲該像素點位置的ARGB值,每一個像素點的ARGB值肯定下來,這張圖片的內容就相應地肯定下來。其中,A表明透明度,RGB表明紅綠藍三種顏色通道值。spa

Bitmap內存如何計算

Bitmap一直都是Android中的內存大戶,計算大小的方式有三種:

  • 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目錄對應的屏幕密度對照表,來自郭霖的博客:

對照表

剛纔的案例,咱們是把圖片放到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,那麼decodebitmap爲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內存,而後才能進行復用。

高清大圖加載該怎麼處理?

若是是高清大圖,那就說明不容許進行圖片壓縮,好比微博長圖,清明上河圖。

因此咱們就要對圖片進行局部顯示,這就用到BitmapRegionDecoder屬性,主要用於顯示圖片的某一塊矩形區域。

好比我要顯示左上角的100 * 100區域:

    fun setImagePart() {
        val inputStream: InputStream = assets.open("test.jpg")
        val bitmapRegionDecoder: BitmapRegionDecoder =
            BitmapRegionDecoder.newInstance(inputStream, false)
        val options = BitmapFactory.Options()
        val bitmap = bitmapRegionDecoder.decodeRegion(
            Rect(00100100), options)
        image.setImageBitmap(bitmap)
    }

實際項目使用中,咱們能夠根據手勢滑動,而後不斷更新咱們的Rect參數來實現具體的功能便可。

具體實現源碼能夠參考鴻洋的博客:https://blog.csdn.net/lmj623565791/article/details/49300989

如何跨進程傳遞大圖?

  • Bundle直接傳遞。bundle最經常使用於Activity間傳遞,也屬於跨進程的一種方式,可是傳遞的大小有限制,通常爲1M。

//intent.put的putExtra方法實質也是經過bundle
intent.putExtra("image",bitmap);

bundle.putParcelable("image",bitmap)

Bitmap之因此能夠直接傳遞,是由於其實現了Parcelable接口進行了序列化。而Parcelable的傳遞原理是利用了Binder機制,將Parcel序列化的數據寫入到一個共享內存(緩衝區)中,讀取的時候也會從這個緩衝區中去讀取字節流,而後再反序列化成對象使用。這個共享內存也就是緩存區有一個大小限制—1M,並且是公用的。因此傳圖片的話很容易就容易超過這個大小而後報錯TransactionTooLargeException

因此這個方案不可靠。

  • 文件傳輸

將圖片保存到文件,而後只傳輸文件路徑,這樣確定是能夠的,可是不高效。

  • putBinder

這個就是考點了。經過傳遞binder的方式傳遞bitmap。

//傳遞binder
val bundle = Bundle()
bundle.putBinder("bitmap", BitmapBinder(mBitmap))

//接收binder中的bitmap
val imageBinder: BitmapBinder = bundle.getBinder("bitmap"as BitmapBinder
val bitmap: Bitmap? = imageBinder.getBitmap()

//Binder子類
class BitmapBinder :Binder(){
    private var bitmap: Bitmap? = null

    fun ImageBinder(bitmap: Bitmap?) {
        this.bitmap = bitmap
    }

    fun getBitmap(): Bitmap? {
        return bitmap
    }
}

爲何用putBinder就沒有大小限制了呢?

  • 由於 putBinder中傳遞的實際上是一個文件描述符fd,文件自己被放到一個共享內存中,而後獲取到這個fd以後,只須要從共享內存中取出Bitmap數據便可,這樣傳輸就很高效了。

  • 而用 Intent/bundle直接傳輸的時候,會禁用文件描述符fd,只能在parcel的緩存區中分配空間來保存數據,因此沒法突破1M的大小限制。

文件描述符是一個簡單的整數,用以標明每個被進程所打開的文件和socket。第一個打開的文件是0,第二個是1,依此類推。

參考

https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc?id=1872

https://www.cnblogs.com/shakinghead/p/11025805.html

https://blog.csdn.net/lmj623565791/article/details/49300989

https://blog.csdn.net/ylyg050518/article/details/97671874

 

拜拜

有一塊兒學習的小夥伴能夠關注下❤️  個人公衆號——碼上積木,天天剖析一個知識點,咱們一塊兒積累知識。公衆號回覆111可得到面試題《思考與解答》以往期刊。

相關文章
相關標籤/搜索