Bitmap

  1. 基本概念(是什么,應用場景)以及BitMap的編碼原理(做引導)

  2. BitMap類在Android類中的基本實現(xiàn)(基本結構)

  3. recycle

Bitmap

位圖的像素都分配有特定的位置和顏色值。每個像素的顏色信息由RGB組合或者灰度值表示。
根據(jù)位深度,可將位圖分為1、4、8、16、24及32位圖像等。每個像素使用的信息位數(shù)越多,可用的顏色就越多,顏色表現(xiàn)就越逼真,相應的數(shù)據(jù)量越大。例如,位深度為1的像素位圖只有兩個可能的值(黑色和白色),所以又稱為二值位圖。位深度為8的圖像有28(即256)個可能的值。位深度為8的灰度模式圖像有256個可能的灰色值。
Config解析:

Bitmap.Config.ALPHA_8:顏色信息只由透明度組成,占8位。
Bitmap.Config.ARGB_4444:顏色信息由透明度與R(Red),G(Green),B(Blue)四部分組成,每個部分都占4位,總共占16位。已經(jīng)被廢棄,因為顯示質量不好。
Bitmap.Config.ARGB_8888:顏色信息由透明度與R(Red),G(Green),B(Blue)四部分組成,每個部分都占8位,總共占32位。是Bitmap默認的顏色配置信息,也是最占空間的一種配置。
Bitmap.Config.RGB_565:顏色信息由R(Red),G(Green),B(Blue)三部分組成,R占5位,G占6位,B占5位,總共占16位。如果不需要 alpha 通道,特別是資源本身為 jpg 格式的情況下,用這個格式比較理想

這個在skia庫中可以看到


image.png

當需要做性能優(yōu)化或者防止OOM(Out Of Memory),我們通常會使用Bitmap.Config.RGB_565這個配置。

大多數(shù)情況下其實我們并不需要argb中的alpha通道,在背景已知的情況下,rgb和argb是可以互相轉換的。而多數(shù)情況下我們都是白色背景。(直接使用target的 rgb就可以了)
Source => Target = (BGColor + Source) =
Target.R = ((1 - Source.A) * BGColor.R) + (Source.A * Source.R)
Target.G = ((1 - Source.A) * BGColor.G) + (Source.A * Source.G)
Target.B = ((1 - Source.A) * BGColor.B) + (Source.A * Source.B)

小問題:RGB_565這個數(shù)字是怎么定的?為什么不取555?
文件讀取是按byte來的。

和矢量圖的比較
1.文件小,圖像中保存的是線條和圖塊的信息,所以矢量圖形文件與分辨率和圖像大小無關,只與圖像的復雜程度有關,圖像文件所占的存儲空間較小。
2矢量圖無限放大不模糊,大部分位圖都是由矢量導出來的
3.矢量圖最大的缺點是難以表現(xiàn)色彩層次豐富的逼真圖像效果。

移動端開發(fā)中位圖的應用很少,因為很少遇到這種需要無限縮放的場景。對于少數(shù)有縮放需要的場景,Bitmap類提供了一種特殊而且有趣的方式。這就是(九點圖)

Bitmap的相關類很多,但是只要按照一個基本思路梳理 ,就會很清晰

  1. 文件和Bitmap的相互轉換
    1.1 文件轉換為Bitmap

Bitmap是一個final類,因此不能被繼承。Bitmap只有一個構造方法,且該構造方法是沒有任何訪問權限修飾符修飾,也就是說該構造方法是friendly,但是谷歌稱Bitmap的構造方法是private(私有的),感覺有點不嚴謹。不管怎樣,一般情況下,我們不能通過構造方法直接新建一個Bitmap對象。
從文件創(chuàng)建Bitmap類就離不開BitmapFactory

BitmapFactory類提供了四類方法:decodeFile、decodeRe-source、decodeStream和decodeByteArray,分別用于支持從文件系統(tǒng)、資源、輸入流以及字節(jié)數(shù)組中加載出一個Bitmap對象,其中decodeFile和decodeResource又間接調(diào)用了decode-Stream方法,這四類方法最終是在Android的底層實現(xiàn)的,對應著BitmapFactory類的幾個native方法。

其實核心思想也很簡單,那就是采用BitmapFactory.Options來加載所需尺寸的圖片。
通過BitmapFactory.Options來縮放圖片,主要是用到了它的inSampleSize參數(shù),即采樣率。當inSampleSize為1時,采樣后的圖片大小為圖片的原始大??;當inSampleSize大于1時,比如為2,那么采樣后的圖片其寬/高均為原圖大小的1/2,而像素數(shù)為原圖的1/4,其占有的內(nèi)存大小也為原圖的1/4。

(1)將BitmapFactory.Options的inJustDecodeBounds參數(shù)設為true并加載圖片。
這一步并不會讀取文件的像素區(qū)塊。只會去從
(2)從BitmapFactory.Options中取出圖片的原始寬高信息,它們對應于outWidth和outHeight參數(shù)。
(3)根據(jù)采樣率的規(guī)則并結合目標View的所需大小計算出采樣率inSampleSize。
(4)將BitmapFactory.Options的inJustDecodeBounds參數(shù)設為false,然后重新加載圖片。

我們現(xiàn)在有個需求,要求將一張圖片進行模糊,然后作為 ImageView 的 src 呈現(xiàn)給用戶,而我們的原始圖片大小為 1080*1920,如果我們直接拿來模糊的話,一方面模糊的過程費時費力,另一方面生成的圖片又占用內(nèi)存,實際上在模糊運算過程中可能會存在輸入和輸出并存的情況,此時內(nèi)存將會有一個短暫的峰值。

1.2 從Bitmap轉換為文件
Bitmap支持的文件格式

Bitmap在內(nèi)存中的大小是可以簡單計算出來的了。但是文件不同,文件可以進行壓縮

一種比較典型的壓縮方式比如

image.png
image.png

位圖的格式有很多種, 每種的壓縮算法都不同。


image.png

但是目前Android中的Bitmap只支持三種

image.png
image.png

CompressFormat解析:
和剛才的Bitmap.Config相比,這個內(nèi)部類只會在壓縮文件等時被用到

Bitmap.CompressFormat.JPEG:表示以JPEG壓縮算法進行圖像壓縮,壓縮后的格式可以是".jpg"或者".jpeg",是一種有損壓縮。

Bitmap.CompressFormat.PNG:表示以PNG壓縮算法進行圖像壓縮,壓縮后的格式可以是".png",是一種無損壓縮。這意味著在解析時,可能會忽略掉 質量。

Bitmap.CompressFormat.WEBP:是一種同時提供了有損壓縮無損壓縮(可逆壓縮)的圖片文件格式
而在日常應用中發(fā)現(xiàn),同樣的圖片質量(70%)下,這三種格式的大小是有差異的:

泡泡圖文發(fā)布不同格式對比

如果對存儲性能要求更嚴格的話(存儲器空間不足)或者有大量圖片存儲,可以考慮使用webp格式。

題外話,如果本地有大量的圖片資源文件,可以考慮批量將png圖轉換成webp格式。

2.Bitmap的調(diào)整
Bitmap自身的調(diào)整也是一件非常有意思的事情。 說道bitmap就不得不提Matrix。
可以說,這倆如影相隨。
???

這里寫什么# 矩陣變換?

3. 手動recycler()是否有必要?

Google對這個問題其實已經(jīng)解釋,但是比較含混:
首先我們看下這個方法到底做了些什么:
我們剛才已經(jīng)看過Bitmap的java類中所包含的只是一些方便我們?nèi)∮玫男畔ⅰ?/p>

主要方法包括recycler都在能去JNI層查看。

Paste_Image.png
Paste_Image.png

這段話其實看得人也比較迷糊。前一句還比較簡單。大致是說其主要數(shù)據(jù)都是存在native的內(nèi)存中,無輪是malloc了native memory哪些,輪不到dalvik來管。所以在“交互接口”上得自己管理好資源的分配和釋放。 如果處理得不好,有可能java虛擬機自己跑得還挺歡,進程首先內(nèi)存就不夠用了。
怎么辦 ,就需要我們顯式去調(diào)用recycler()

所以2.3.3 之前的代碼應該怎么寫呢,得靠你自己來實現(xiàn)一個引用計數(shù)器。

Paste_Image.png

對于我們java程序員來說,這個真的有點難。我就想做個圖片,你還得讓我實現(xiàn)一個引用計數(shù)器?
即便是對于今天的C++ 程序員來說,也已經(jīng)有智能指針來幫助他做這些事情了。

那么 現(xiàn)在的版本里,recycler是否有效果?我們寫一個小demo先試試吧:
我選用的targetApi為25 ,源碼非常簡單

Paste_Image.png

結果如圖:


Paste_Image.png

我們發(fā)現(xiàn),其實手動調(diào)用recycler并沒有將內(nèi)存釋放掉。

那是以前,現(xiàn)在又提到到了3.0之后 Dalvik 又把這些東西都收到自己的堆里, 并且和Bitmap聯(lián)系起來。
怎么聯(lián)系起來的?
我們先看看Bitmap的構造方法,這是個私有的構造方法。是在native層構建了之后,再回調(diào)過來的。

Paste_Image.png

在這個方法中 ,有這樣一句話(API25):

Paste_Image.png

厲害了,native層分配的內(nèi)存大小居然是業(yè)務中自己計算出來的,連同析構函數(shù)一同給了這個注冊器。

這個注冊器最終會調(diào)用到VMRunTime的registerNativeAllocation
會將native對象的大小通知給dalvik,如果當前的native內(nèi)存分配過大,可能會引發(fā)一次GC,這也是為什么我們看到了上面的效果、

Paste_Image.png

(https://android.googlesource.com/platform/libcore/+/master/libart/src/main/java/dalvik/system/VMRuntime.java)

最終實際進行gc的地方:

Paste_Image.png

(https://android.googlesource.com/platform/dalvik/+/kitkat-release/vm/alloc/HeapSource.cpp)

當然這只是其中一種的gc觸發(fā)路徑。在別的很多情況下都有可能,但是recycler并不會觸發(fā)gc,或者說recycler 方法并不能在性能上帶來提升。gc的事情還是去交給gc去做吧。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容