成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Bitmap 比你想的更費內存 | 吊打 OOM

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

[[201248]]

一、前言

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

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

二、占用多少內存?

2.1 如何獲取占用的內存空間?

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

例如,現在項目內有一個 400 * 200 像素的圖片,方在 drawable-xhdpi 目錄下,在Nexus 6 設備上,運行加載它。看它輸出的尺寸。

看一下輸出的結果:

  1. I/cxmyDev: byteCound : 720000 

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

2.2 getRowBytes() 的計算依據

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

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

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

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

可以看到,最終使用的是 SkBitmap 去實現的。

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

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

  1. bitmapInRam = bitmapWidth * 4 bytes * bitmapHeight 

但是如果依據這樣的公式計算一個結果,你會發現獲得的值會比真實的值差了很多。

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

  1. 400 * 200 * 4 = 320000 

那么,問題出在哪里?

2.3 density 影響 Bitmap 內存

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

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

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

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

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

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

它們的關系如下表:

density 和 densityDpi 在 Android 中,都有標準的 API 可以拿到,利用 DisplayMetrics即可。

看到 Nexus 5 輸出的結果:

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

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

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

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

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

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

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

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

  • inDensity :圖片存放的 Drawable 文件夾代表的 densityDpi 。
  • inTargetDensity : 當前設備固有的 densityDpi 。

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

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

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

那計算圖片內存的公式,就應該調整為:

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

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

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

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

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

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

前面舉的例子中,圖片尺寸和設備的 densityDpi 都是很規整的。但是不排除有一些比較不標準的設備,加載的圖片使用上面的計算公式,依然對不上。

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

代碼依然在 BitmapFactory..cpp 中。

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

4、小結

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

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

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

最后附上Android 5.1.1 的相關源碼,供大家參考

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專欄作者“張旸”的原創稿件,轉載請通過微信公眾號聯系作者獲取授權】

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

責任編輯:武曉燕 來源: 51CTO專欄
相關推薦

2019-04-04 13:33:17

2010-05-06 09:23:45

云計算

2013-12-20 09:19:18

計算機學習

2020-04-24 09:58:18

數據泄露黑客網絡攻擊

2022-09-28 07:19:35

瀏覽器安全保證惡意擴展

2022-09-25 11:46:52

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

2017-08-14 16:36:23

ASActivity內存

2014-02-10 17:48:00

Windows 8.1

2012-09-24 11:14:06

PHP編程語言Web開發

2012-09-20 09:28:26

PHP程序Web

2015-04-13 10:30:14

2021-06-09 15:40:47

容器

2023-09-25 14:48:24

Wi-Fi 6

2023-02-10 08:13:56

Pythonf-strings

2022-09-19 15:50:10

物聯網安全工業4.0

2018-04-10 16:24:03

算法分布式一致性

2019-01-11 10:00:44

微信騰訊改版

2022-03-31 10:39:07

Linuxsudo命令

2024-09-27 09:53:22

Rust標準庫優化

2021-05-19 14:36:03

數據中心
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日日碰狠狠躁久久躁96avv | 欧美久久精品一级c片 | 99亚洲| 国产伦一区二区三区久久 | 免费精品 | 久久久91精品国产一区二区三区 | 日韩免费一区二区 | 亚洲精品影院 | 国产一区二区欧美 | 中文字幕电影在线观看 | 成人精品国产 | 久久亚洲一区二区三区四区 | 亚洲人免费视频 | 久久免费观看视频 | 日韩成人高清在线 | 亚洲高清在线 | 成av在线| 亚洲一区视频在线 | 欧美一级二级视频 | 欧美成人免费 | 91伊人| 亚洲网在线 | 欧美一区二区在线 | 亚洲精品久久久久中文字幕二区 | 男女免费在线观看视频 | 国产日批 | 日韩高清在线观看 | 精品1区2区3区 | 久久精品网 | 亚洲视频中文字幕 | 欧美日韩大片 | 九色网址 | 一区视频 | 男女啪啪高潮无遮挡免费动态 | 欧美在线视频观看 | 亚洲性人人天天夜夜摸 | 亚洲欧美激情精品一区二区 | 国产精品一区三区 | 欧美久久一区二区三区 | 97精品久久| 999精品在线观看 |