溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Bitmap知識(shí)點(diǎn)有哪些

發(fā)布時(shí)間:2021-10-21 11:53:50 來(lái)源:億速云 閱讀:142 作者:iii 欄目:編程語(yǔ)言

本篇內(nèi)容主要講解“Bitmap知識(shí)點(diǎn)有哪些”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Bitmap知識(shí)點(diǎn)有哪些”吧!

Bitmap是什么,怎么存儲(chǔ)圖片。

Bitmap,位圖,本質(zhì)上是一張圖片的內(nèi)容在內(nèi)存中的表達(dá)形式。它將圖片的內(nèi)容看做是由存儲(chǔ)數(shù)據(jù)的有限個(gè)像素點(diǎn)組成;每個(gè)像素點(diǎn)存儲(chǔ)該像素點(diǎn)位置的ARGB值,每個(gè)像素點(diǎn)的ARGB值確定下來(lái),這張圖片的內(nèi)容就相應(yīng)地確定下來(lái)。其中,A代表透明度,RGB代表紅綠藍(lán)三種顏色通道值。

Bitmap內(nèi)存如何計(jì)算

Bitmap一直都是Android中的內(nèi)存大戶,計(jì)算大小的方式有三種:

  • getRowBytes() 這個(gè)在 API Level 1添加的,返回的是bitmap一行所占的大小,需要乘以bitmap的高,才能得出btimap的大小

  • getByteCount() 這個(gè)是在 API Level 12添加的,其實(shí)是對(duì)getRowBytes()乘以高的封裝

  • getAllocationByteCount() 這個(gè)是在 API Level 19添加的

這里我將一張圖片放到項(xiàng)目的drawable-xxhdpi文件夾中,然后通過(guò)方法獲取圖片所占的內(nèi)存大?。?/p>

    var bitmap = BitmapFactory.decodeResource(resources, R.drawable.test)
    img.setImageBitmap(bitmap)
        
    Log.e(TAG,"dpi = ${resources.displayMetrics.densityDpi}")
    Log.e(TAG,"size = ${bitmap.allocationByteCount}")

打印出來(lái)的結(jié)果是

size=1960000

具體是怎么計(jì)算的呢?

圖片內(nèi)存=寬 * 高 * 每個(gè)像素所占字節(jié)。

這個(gè)像素所占字節(jié)又和Bitmap.Config有關(guān),Bitmap.Config是個(gè)枚舉類,用于描述每個(gè)像素點(diǎn)的信息,比如:

  • ARGB_8888。常用類型,總共32位,4個(gè)字節(jié),分別表示透明度和RGB通道。

  • RGB_565。16位,2個(gè)字節(jié),只能描述RGB通道。

所以我們這里的圖片內(nèi)存計(jì)算就得出:

寬700 * 高700 * 每個(gè)像素4字節(jié)=1960000

Bitmap內(nèi)存 和drawable目錄的關(guān)系

首先放一張drawable目錄對(duì)應(yīng)的屏幕密度對(duì)照表,來(lái)自郭霖的博客:

Bitmap知識(shí)點(diǎn)有哪些

對(duì)照表

剛才的案例,我們是把圖片放到drawable-xxhdpi文件夾,而drawable-xxhdpi文件夾對(duì)應(yīng)的dpi就是我們測(cè)試手機(jī)的dpi—480。所以圖片的內(nèi)存就是我們所計(jì)算的寬 * 高 * 每個(gè)像素所占字節(jié)。

如果我們把圖片放到其他的文件夾,比如drawable-hdpi文件夾(對(duì)應(yīng)的dpi是240),會(huì)發(fā)生什么呢?

再次打印結(jié)果:

size = 7840000

這是因?yàn)橐粡垐D片的實(shí)際占用內(nèi)存大小計(jì)算公式是:

占用內(nèi)存 = 寬 * 縮放比例 * 高 * 縮放比例 * 每個(gè)像素所占字節(jié)

這個(gè)縮放比例就跟屏幕密度DPI有關(guān)了:

縮放比例 = 設(shè)備dpi/圖片所在目錄的dpi

所以我們這張圖片的實(shí)際占用內(nèi)存位:

寬700 * (480/240) * 高700 * (480/240) * 每個(gè)像素4字節(jié) = 7840000

Bitmap加載優(yōu)化?不改變圖片質(zhì)量的情況下怎么優(yōu)化?

常用的優(yōu)化方式是兩種:

  • 修改Bitmap.Config

這一點(diǎn)剛才也說(shuō)過(guò),不同的Conifg代表每個(gè)像素不同的占用空間,所以如果我們把默認(rèn)的ARGB_8888改成RGB_565,那么每個(gè)像素占用空間就會(huì)由4字節(jié)變成2字節(jié)了,那么圖片所占內(nèi)存就會(huì)減半了。

可能一定程度上會(huì)降低圖片質(zhì)量,但是我實(shí)際測(cè)試看不出什么變化。

  • 修改inSampleSize

inSampleSize,采樣率,這個(gè)參數(shù)是用于圖片尺寸壓縮的,他會(huì)在寬高的維度上每隔inSampleSize個(gè)像素進(jìn)行一次采集,從而達(dá)到縮放圖片的效果。這種方法只會(huì)改變圖片大小,不會(huì)影響圖片質(zhì)量。

    val options=BitmapFactory.Options()
    options.inSampleSize=2    val bitmap = BitmapFactory.decodeResource(resources, R.drawable.test2,options)
    img.setImageBitmap(bitmap)

實(shí)際項(xiàng)目中,我們可以設(shè)置一個(gè)與目標(biāo)圖像大小相近的inSampleSize,來(lái)減少實(shí)際使用的內(nèi)存:

    fun getImage(): Bitmap {
        var options = BitmapFactory.Options()
        options.inJustDecodeBounds = true        BitmapFactory.decodeResource(resources, R.drawable.test2, options)
        // 計(jì)算最佳采樣率        options.inSampleSize = getImageSampleSize(options.outWidth, options.outHeight)
        options.inJustDecodeBounds = false        return BitmapFactory.decodeResource(resources, R.drawable.test2, options)
    }

inJustDecodeBounds是什么?

上面的例子大家應(yīng)該發(fā)現(xiàn)了,其中有個(gè)inJustDecodeBounds,又設(shè)置為true,又設(shè)置成false的,總感覺(jué)多此一舉,那么他到底是干嘛呢?

因?yàn)槲覀円@取圖片本身的大小,如果直接decodeResource加載一遍的話,那么就會(huì)增加內(nèi)存了,所以官方提供了這樣一個(gè)參數(shù)inJustDecodeBounds。如果inJustDecodeBounds為ture,那么decodebitmap為null,也就是不返回實(shí)際的bitmap,只把圖片的大小信息放到了options的值中。

所以這個(gè)參數(shù)就是用來(lái)獲取圖片的大小信息的同時(shí)不占用內(nèi)存。

Bitmap內(nèi)存復(fù)用怎么實(shí)現(xiàn)?

如果有個(gè)需求,是在同一個(gè)imageview中可以加載不同的圖片,那我們需要每次都去新建一個(gè)Bitmap對(duì)象,占用新的內(nèi)存空間嗎?如果我們這樣寫的話:

    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)
    }

這樣就會(huì)Bitmap就會(huì)頻繁去申請(qǐng)內(nèi)存,釋放內(nèi)存,從而導(dǎo)致大量GC,內(nèi)存抖動(dòng)。

為了防止這種情況呢,我們就可以用到inBitmap參數(shù),用于Bitmap的內(nèi)存復(fù)用。這樣同一塊內(nèi)存空間就可以被多個(gè)Bitmap對(duì)象復(fù)用,從而減少了頻繁的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)
    }

這里有幾個(gè)要注意的點(diǎn)

  • inBitmap要和 inMutable屬性配套使用,否則將無(wú)法復(fù)用。

  • Android 4.4之前,只能重用相同大小的 Bitmap 內(nèi)存區(qū)域; 4.4之后只要復(fù)用內(nèi)存空間的Bitmap對(duì)象大小比 inBitmap指向的內(nèi)存空間要小即可。

所以一般在復(fù)用之前,還要判斷下,新的Bitmap內(nèi)存是不是小于可以復(fù)用的Bitmap內(nèi)存,然后才能進(jìn)行復(fù)用。

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

如果是高清大圖,那就說(shuō)明不允許進(jìn)行圖片壓縮,比如微博長(zhǎng)圖,清明上河圖。

所以我們就要對(duì)圖片進(jìn)行局部顯示,這就用到BitmapRegionDecoder屬性,主要用于顯示圖片的某一塊矩形區(qū)域。

比如我要顯示左上角的100 * 100區(qū)域:

    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(0, 0, 100, 100), options)
        image.setImageBitmap(bitmap)
    }

實(shí)際項(xiàng)目使用中,我們可以根據(jù)手勢(shì)滑動(dòng),然后不斷更新我們的Rect參數(shù)來(lái)實(shí)現(xiàn)具體的功能即可。

具體實(shí)現(xiàn)源碼可以參考鴻洋的博客:https://blog.csdn.net/lmj623565791/article/details/49300989

如何跨進(jìn)程傳遞大圖?

  • Bundle直接傳遞。bundle最常用于Activity間傳遞,也屬于跨進(jìn)程的一種方式,但是傳遞的大小有限制,一般為1M。

//intent.put的putExtra方法實(shí)質(zhì)也是通過(guò)bundleintent.putExtra("image",bitmap);

bundle.putParcelable("image",bitmap)

Bitmap之所以可以直接傳遞,是因?yàn)槠鋵?shí)現(xiàn)了Parcelable接口進(jìn)行了序列化。而Parcelable的傳遞原理是利用了Binder機(jī)制,將Parcel序列化的數(shù)據(jù)寫入到一個(gè)共享內(nèi)存(緩沖區(qū))中,讀取的時(shí)候也會(huì)從這個(gè)緩沖區(qū)中去讀取字節(jié)流,然后再反序列化成對(duì)象使用。這個(gè)共享內(nèi)存也就是緩存區(qū)有一個(gè)大小限制—1M,而且是公用的。所以傳圖片的話很容易就容易超過(guò)這個(gè)大小然后報(bào)錯(cuò)TransactionTooLargeException。

所以這個(gè)方案不可靠。

  • 文件傳輸

將圖片保存到文件,然后只傳輸文件路徑,這樣肯定是可以的,但是不高效。

  • putBinder

這個(gè)就是考點(diǎn)了。通過(guò)傳遞binder的方式傳遞bitmap。

//傳遞binderval bundle = Bundle()
bundle.putBinder("bitmap", BitmapBinder(mBitmap))//接收binder中的bitmapval imageBinder: BitmapBinder = bundle.getBinder("bitmap") as BitmapBinderval 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就沒(méi)有大小限制了呢?

  • 因?yàn)?putBinder中傳遞的其實(shí)是一個(gè)文件描述符fd,文件本身被放到一個(gè)共享內(nèi)存中,然后獲取到這個(gè)fd之后,只需要從共享內(nèi)存中取出Bitmap數(shù)據(jù)即可,這樣傳輸就很高效了。

  • 而用 Intent/bundle直接傳輸?shù)臅r(shí)候,會(huì)禁用文件描述符fd,只能在parcel的緩存區(qū)中分配空間來(lái)保存數(shù)據(jù),所以無(wú)法突破1M的大小限制。

文件描述符是一個(gè)簡(jiǎn)單的整數(shù),用以標(biāo)明每一個(gè)被進(jìn)程所打開(kāi)的文件和socket。第一個(gè)打開(kāi)的文件是0,第二個(gè)是1,依此類推。

到此,相信大家對(duì)“Bitmap知識(shí)點(diǎn)有哪些”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI