自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Bitmap 比你想的更費內(nèi)存 | 吊打 OOM

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

[[201248]]

一、前言

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

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

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

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

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

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

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

  1. I/cxmyDev: byteCound : 720000 

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

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

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

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

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

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

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

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

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

  1. bitmapInRam = bitmapWidth * 4 bytes * bitmapHeight 

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

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

  1. 400 * 200 * 4 = 320000 

那么,問題出在哪里?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

可以看到,它實際上是會通過兩個 density 計算出一個比例值 scale ,它會去對圖片原始的像素進行 scale 表示的比例的縮放。

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

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

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

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

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

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

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

看看我們關(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 查缺補漏

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

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

代碼依然在 BitmapFactory..cpp 中。

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

4、小結(jié)

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

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

  • 色彩格式:比如 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

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

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

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2013-12-20 09:19:18

計算機學(xué)習(xí)

2019-04-04 13:33:17

2010-05-06 09:23:45

云計算

2020-04-24 09:58:18

數(shù)據(jù)泄露黑客網(wǎng)絡(luò)攻擊

2022-09-25 11:46:52

瀏覽器擴展程序廣告攔截器

2022-09-28 07:19:35

瀏覽器安全保證惡意擴展

2017-08-14 16:36:23

ASActivity內(nèi)存

2014-02-10 17:48:00

Windows 8.1

2012-09-24 11:14:06

PHP編程語言Web開發(fā)

2012-09-20 09:28:26

PHP程序Web

2015-04-13 10:30:14

2021-06-09 15:40:47

容器

2018-04-10 16:24:03

算法分布式一致性

2022-09-19 15:50:10

物聯(lián)網(wǎng)安全工業(yè)4.0

2023-02-10 08:13:56

Pythonf-strings

2022-03-31 10:39:07

Linuxsudo命令

2023-09-25 14:48:24

Wi-Fi 6

2019-01-11 10:00:44

微信騰訊改版

2024-09-27 09:53:22

Rust標(biāo)準(zhǔn)庫優(yōu)化

2021-05-19 14:36:03

數(shù)據(jù)中心
點贊
收藏

51CTO技術(shù)棧公眾號