日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Bitmap 比你想的更費(fèi)內(nèi)存 | 吊打 OOM

一、前言

公司主營業(yè)務(wù):成都做網(wǎng)站、網(wǎng)站建設(shè)、移動(dòng)網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。創(chuàng)新互聯(lián)公司是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來驚喜。創(chuàng)新互聯(lián)公司推出古田免費(fèi)做網(wǎng)站回饋大家。

在一個(gè) App 中,無可避免的會(huì)有一些 Bitmap 的資源,會(huì)被打包在 apk 中,隨著 apk 發(fā)布出去。而當(dāng)你在使用這些 Bitmap 的資源的時(shí)候,它到底需要占用多少內(nèi)存空間?這是一個(gè)很實(shí)際的問題,把握不好就可能引發(fā)各種 OOM 的錯(cuò)誤。

本文就來探討一下,本地的 Bitmap 到底占用多少內(nèi)存空間?

二、占用多少內(nèi)存?

2.1 如何獲取占用的內(nèi)存空間?

既然需要說道一個(gè) Bitmap 資源,加載到內(nèi)存中所要占用的空間,那就需要有一個(gè)明確的獲取方法,來確定的知道它到底占用了多少空間。而 Android 確實(shí)也為我們提供了類似的 API,那就是 Bitmap.getByteCount() 。

例如,現(xiàn)在項(xiàng)目內(nèi)有一個(gè) 400 * 200 像素的圖片,方在 drawable-xhdpi 目錄下,在Nexus 6 設(shè)備上,運(yùn)行加載它??此敵龅某叽纭?/p>

看一下輸出的結(jié)果:

 
 
 
 
  1. I/cxmyDev: byteCound : 720000 

可以看到,getByteCount() 是根據(jù) getRowBytes() * getHeight() 計(jì)算出來的。getHeight() 方法它是 Bitmap 的高度,而 getRowBytes() 又是什么?

2.2 getRowBytes() 的計(jì)算依據(jù)

getRowBytes() 方法,最終調(diào)用的是一個(gè) nativeRowBytes() 的方法,它是一個(gè)native 的方法。

既然要查就查到底,看看 native 的代碼是如何實(shí)現(xiàn)的(文內(nèi) native 的源碼,都是基于Android 5.1.1,文末會(huì)有在線查看地址,并且已經(jīng)附帶行號(hào),方便查閱)。

先看看 Bitmap.cpp 的代碼中 rowBytes() 是如何實(shí)現(xiàn)的。

這里閱讀的是 Android 5.1.1 的源碼,實(shí)際上從 Android 6 開始,會(huì)使用 LocalScopedBitmap 去操作,它其實(shí)也只是對(duì) SkBitmap 做了一個(gè)封裝而已。如下圖所示,rowBytes() 是使用的 LocalScoopedBitmap 來操作的,有興趣的可以繼續(xù)看看它是如何實(shí)現(xiàn)的。

可以看到,最終使用的是 SkBitmap 去實(shí)現(xiàn)的。

在 SkBitmap.cpp 里就可以確認(rèn) ,色彩度為 ARGB_8888 圖片,每像素會(huì)占用 4 bytes 的大小。

看這個(gè)樣子,結(jié)合前面提到的 Bitmap.getByteCount() 的計(jì)算公式就是:

 
 
 
 
  1. bitmapInRam = bitmapWidth * 4 bytes * bitmapHeight 

但是如果依據(jù)這樣的公式計(jì)算一個(gè)結(jié)果,你會(huì)發(fā)現(xiàn)獲得的值會(huì)比真實(shí)的值差了很多。

前面 Demo 中的圖片,加載到內(nèi)存中,占用的內(nèi)存是:720000 。但是用我們這里得到的計(jì)算方式,計(jì)算的結(jié)果是。

 
 
 
 
  1. 400 * 200 * 4 = 320000 

那么,問題出在哪里?

2.3 density 影響 Bitmap 內(nèi)存

2.1 中的 Demo ,明確指出了需要圖片存放的 Drawable 目錄,以及使用的設(shè)備,其實(shí)它們都是有關(guān)系的,不是無關(guān)系的路人甲。

關(guān)于圖片而言,放在不同的 Drawable 目錄下,對(duì)應(yīng)的不同 density 的設(shè)備。density 是設(shè)備的固有參數(shù),伴隨著 density 的,還有 densityDpi,它也是與設(shè)備相關(guān)的,表示屏幕每英寸對(duì)應(yīng)多少個(gè)點(diǎn)(非像素點(diǎn))。

它們之間的關(guān)系,可以直接查閱官方文檔,這里就不贅述了。

https://developer.android.com/guide/practices/screens_support.html

這里說到的 density ,其實(shí)就是代表不同的 drawable-xxx 目錄。

上面是官方提供的一張比較經(jīng)典的圖,可以看到,不同的目錄,代表不同的 density ,例如 xhdpi 代表的 density 就是 2。而這里的 density 對(duì) densityDip 的基準(zhǔn)是 160 ,也就是說,mdpi 對(duì)應(yīng)的 densityDpi 是 160 ,xhdpi 對(duì)應(yīng)的 densityDpi 是 320。

它們的關(guān)系如下表:

density 和 densityDpi 在 Android 中,都有標(biāo)準(zhǔn)的 API 可以拿到,利用 DisplayMetrics即可。

看到 Nexus 5 輸出的結(jié)果:

 
 
 
 
  1. I/cxmyDev: density : 3.0 
  2. I/cxmyDev: densityDpi : 480 

了解了設(shè)備的 density 和 densityDpi ,在繼續(xù)看看加載 Bitmap 的過程,使用的是 BitmapFactory.decodeResource() 方法。

從源碼上可以看出,它實(shí)際上是分兩步完成的。

  1. 使用 openRawResource() 方法獲取圖片的原始流。
  2. 使用 decodeResourceStream() 方法,對(duì)數(shù)據(jù)流進(jìn)行解碼和適配。

對(duì)于一個(gè)文件流而言,在這里我們是不需要關(guān)心的。主要影響圖片內(nèi)存的是 decodeResourceStream() 方法中,對(duì)數(shù)據(jù)流進(jìn)行解碼和適配的時(shí)候,都做了哪些處理。

在這個(gè)方法中,會(huì)傳遞一個(gè) Options 的對(duì)象,用于配置當(dāng)前圖片的解碼和適配。

從代碼中可以了解到,影響圖片內(nèi)存占比的因素有 inDensity 和 inTargetDensity 兩個(gè)。

Options 中這兩個(gè)值,都是可以設(shè)置的,如果不對(duì)其進(jìn)行額外的操作,它們默認(rèn)情況下,分別表示的含義:

  • inDensity :圖片存放的 Drawable 文件夾代表的 densityDpi 。
  • inTargetDensity : 當(dāng)前設(shè)備固有的 densityDpi 。

而使用他們的代碼,都是在 native 中,繼續(xù)追看 BitmapFactory.cpp 的源碼(源碼太多,只貼關(guān)鍵點(diǎn))

可以看到,它實(shí)際上是會(huì)通過兩個(gè) density 計(jì)算出一個(gè)比例值 scale ,它會(huì)去對(duì)圖片原始的像素進(jìn)行 scale 表示的比例的縮放。

也就是說同一張圖片,放在不同 drawable 文件夾下的圖片,在不同的設(shè)備上,實(shí)際上加載出來的尺寸也是不同的。

那計(jì)算圖片內(nèi)存的公式,就應(yīng)該調(diào)整為:

 
 
 
 
  1. scale = targetDensity / inDensity 
  2. bitmapInRam = (bitmapWidth*scale) * (bitmapHeight*scale) * 4 bytes 

再來使用新的公式,計(jì)算一下上面圖片的尺寸:

 
 
 
 
  1. 400 * (480/320) * 200 *(480/320) * 4 = 720000 

可以看到,最終得出的和我們程序中計(jì)算的值一致 了,所以這就是我們最終得到的計(jì)算圖片在內(nèi)存中,占比的公式了。

再改寫上面的 Demo ,把細(xì)節(jié)點(diǎn)都輸出出來。

看看我們關(guān)心的 Log 輸出:

 
 
 
 
  1. I/cxmyDev: byteCound : 720000 
  2. I/cxmyDev: rowBytes : 2400 
  3. I/cxmyDev: height : 300 
  4. I/cxmyDev: width : 600 
  5. I/cxmyDev: density : 3.0 
  6. I/cxmyDev: densityDpi : 480 

3.4 查缺補(bǔ)漏

前面舉的例子中,圖片尺寸和設(shè)備的 densityDpi 都是很規(guī)整的。但是不排除有一些比較不標(biāo)準(zhǔn)的設(shè)備,加載的圖片使用上面的計(jì)算公式,依然對(duì)不上。

這個(gè)問題,還是需要在源碼中找答案,對(duì)于不那么標(biāo)準(zhǔn)的 densityDpi 的設(shè)備而言,根據(jù)這個(gè)scale 計(jì)算出來的尺寸,可能是一個(gè) float 值,也就是存在小數(shù)的情況,而圖片的尺寸,都是以 int 類型為單位。所以 Android 為了規(guī)避這樣的問題,做了個(gè)容差值(0.5),去轉(zhuǎn)換成 int 類型。

代碼依然在 BitmapFactory..cpp 中。

所以 getByteCount() 這個(gè) Api 得到的尺寸,可能和我們前面使用公式計(jì)算的尺寸,略微有些偏差,這個(gè)值就是在小數(shù)點(diǎn)之間。

4、小結(jié)

好了,到這里就講清楚了一個(gè)本地的 Bitmap ,加載到內(nèi)存中,到底會(huì)占用多少內(nèi)存。

決定 Bitmap 占用內(nèi)存大小的因素,和圖片文件在磁盤上占用的空間一點(diǎn)關(guān)系都沒有,總結(jié)來說,有以下幾點(diǎn):

  • 色彩格式:比如 ARGB_8888 、RGB_5555 這種,單位像素占的內(nèi)存空間不同。
  • 圖片本身的像素尺寸。
  • 圖片文件存放的 Drawable 目錄。xhdpi 和 xxhdpi 可是不一樣的。
  • 目標(biāo)設(shè)備的 densityDpi 值。

最后附上Android 5.1.1 的相關(guān)源碼,供大家參考

Bitmap.cpp :

http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/jni/android/graphics/Bitmap.cpp

SkBitmap.cpp:

http://androidxref.com/5.1.1_r6/xref/external/skia/src/core/SkBitmap.cpp

BitmapFactory.cpp:

http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/jni/android/graphics/BitmapFactory.cpp

【本文為專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過微信公眾號(hào)聯(lián)系作者獲取授權(quán)】

戳這里,看該作者更多好文


分享文章:Bitmap 比你想的更費(fèi)內(nèi)存 | 吊打 OOM
當(dāng)前鏈接:http://www.dlmjj.cn/article/dphgghd.html