Android Bitmap(位圖)詳解

1、背景android

在Android開發中,任何一個APP都離不開圖片的加載和顯示問題。這裏的圖片來源分爲三種:項目圖片資源文件(通常爲res/drawable目錄下的圖片文件)、手機本地圖片文件、網絡圖片資源等。圖片的顯示咱們通常採用ImageView做爲載體,經過ImageView的相應API便可設置其顯示的圖片內容。c++

咱們知道:若是是須要展現項目中的圖片資源文件,咱們只須要調用ImageView的setImageResource(int id)方法並傳入該圖片資源的id(通常爲R.drawable.xxx)便可。可是若是是須要展現手機本地的某張圖片或者網絡上的某個圖片資源,又該怎麼辦呢?——問題Agit

爲了回答問題A,咱們先思考一個更深的問題B:Android中是如何將某一張圖片的內容加載到內存中繼而由ImageView顯示的呢?github

咱們知道:若是咱們想經過TextView展現一個本地txt文件的內容,咱們只須要由該文件建立幷包裝一個輸入流對象。經過該輸入流對象便可獲得一個表明該文件內容的字符串對象,再將該字符串對象交由TextView展現便可。換句話說,這個txt文件的內容在內存中的表達形式就是這個字符串對象。web

類推一下,雖然圖片文件也是文件,可是咱們顯然不可能對圖片文件也採用這種方式:即經過該圖片創建幷包裝一個輸入流對象再獲取一個字符串對象。畢竟不管如何咱們都沒法將某個圖片的內容表示爲一個字符串對象(細想一下就知道了,你能經過一段話100%準確地描述一張圖片嗎?顯然不現實)。那麼,這就引入了問題C:既然字符串對象不行,那麼咱們該以哪一種對象來在內存中表示某個圖片的內容呢?答案就是:Bitmap對象!算法

2、基本概述緩存

Bitmap,即位圖。它本質上就是一張圖片的內容在內存中的表達形式。那麼,Bitmap是經過什麼方式表示一張圖片的內容呢?安全

Bitmap原理:從純數學的角度,任何一個面都由無數個點組成。可是對於圖片而言,咱們不必用無數個點來表示這個圖片,畢竟單獨一個微小的點人類肉眼是看不清的。換句話說,因爲人類肉眼的能力有限,咱們只須要將一張圖片表示爲 有限但足夠多的點便可。點的數量不能無限,由於無限的點信息量太大沒法存儲;可是點的數量也必須足夠多,不然視覺上沒法造成連貫性。這裏的點就是像素。好比說,某個1080*640的圖片,這裏的像素總數即爲1080X640個。性能優化

將圖片內容表示爲有限但足夠多的像素的集合,這個「無限→有限」的思想極其迷人。因此,咱們只須要將每一個像素的信息存儲起來,就意味着將整個圖片的內容進行了表達。微信

像素信息:每一個像素的信息,無非就是ARGB四個通道的值。其中,A表明透明度,RGB表明紅綠藍三種顏色通道值。每一個通道的值範圍在0~255之間,即有256個值,恰好能夠經過一個字節(8bit)進行表示。因此,每一個通道值由一個字節表示,四個字節表示一個像素信息,這彷佛是最好的像素信息表示方案。

可是這裏忽略了兩個現實的需求問題:

①在實際需求中,咱們真的須要這麼多數量的顏色嗎?上述方案是256X256X256種。有的時候,咱們並不須要這麼豐富的顏色數量,因此能夠適當減小表示每一個顏色通道的bit位數。這麼作的好處是節省空間。也就是說,每一個顏色通道都採用8bit來表示是表明所有顏色值的集合;而咱們能夠採用少於8bit的表示方式,儘管這會缺失一部分顏色值,可是隻要顏色夠用便可,而且這還能夠節省內存空間。

②咱們真的須要透明度值嗎?若是咱們須要某個圖片做爲背景或者圖標,這個圖片透明度A通道值是必要的。可是若是咱們只是普通的圖片展現,好比拍攝的照片,透明度值毫無心義。細想一下,你但願你手機自拍的照片透明或者半透明嗎?hell no! 所以,透明度這個通道值是否有必要表示也是根據需求自由變化的。

具體每一個像素點存儲ARGB值的方案介紹,後面會詳細介紹。

總結:Bitmap對象本質是一張圖片的內容在內存中的表達形式。它將圖片的內容看作是由存儲數據的有限個像素點組成;每一個像素點存儲該像素點位置的ARGB值。每一個像素點的ARGB值肯定下來,這張圖片的內容就相應地肯定下來了。

如今回答一下問題A和問題B:Android就是將全部的圖片資源(不管是何種來源)的內容以Bitmap對象的形式加載到內存中,再經過ImageView的setImageBitmap(Bitmap b)方法便可展現該Bitmap對象所表示的圖片內容。

3、詳細介紹

一、Bitmap.Config

Config是Bitmap的一個枚舉內部類,它表示的就是每一個像素點對ARGB通道值的存儲方案。取值有如下四種:

ARGB_8888:這種方案就是上面所說的每一個通道值採8bit來表示,每一個像素點須要4字節的內存空間來存儲數據。該方案圖片質量是最高的,可是佔用的內存也是最大的

ARGB_4444:這種方案每一個通道都是4位,每一個像素佔用2個字節,圖片的失真比較嚴重。通常不用這種方案。

RGB_565:這種方案RGB通道值分別佔五、六、5位,可是沒有存儲A通道值,因此不支持透明度。每一個像素點佔用2字節,是ARGB_8888方案的一半。

ALPHA_8:這種方案不支持顏色值,只存儲透明度A通道值,使用場景特殊,好比設置遮蓋效果等。

比較分析:通常咱們在ARGB_8888方式和RGB_565方式中進行選取:不須要設置透明度時,好比拍攝的照片等,RGB_565是個節省內存空間的不錯的選擇;既要設置透明度,對圖片質量要求又高,就用ARGB_8888。

 

二、Bitmap的壓縮存儲

Bitmap是圖片內容在內存中的表示形式,那麼若是想要將Bitmap對象進行持久化存儲爲一張本地圖片,須要對Bitmap對象表示的內容進行壓縮存儲。根據不一樣的壓縮算法能夠獲得不一樣的圖片壓縮格式(簡稱爲圖片格式),好比GIF、JPEG、BMP、PNG和WebP等。這些圖片的(壓縮)格式能夠經過圖片文件的後綴名看出。

換句話說:Bitmap是圖片在內存中的表示,GIF、JPEG、BMP、PNG和WebP等格式圖片是持久化存儲後的圖片。內存中的Bitmap到磁盤上的GIF、JPEG、BMP、PNG和WebP等格式圖片通過了」壓縮」過程,磁盤上的GIF、JPEG、BMP、PNG和WebP等格式圖片到內存中的Bitmap通過了「解壓縮」的過程。

那麼,爲何不直接將Bitmap對象進行持久化存儲而是要對Bitmap對象進行壓縮存儲呢?這麼作依據的思想是:當圖片持久化保存在磁盤上時,咱們應該儘量以最小的體積來保存同一張圖片的內容,這樣有利於節省磁盤空間;而當圖片加載到內存中以顯示的時候,應該將磁盤上壓縮存儲的圖片內容完整地展開。前者即爲壓縮過程,目的是節省磁盤空間;後者即爲解壓縮過程,目的是在內存中展現圖片的完整內容。

 

三、有損壓縮和無損壓縮

Bitmap壓縮存儲時的算法有不少種,可是總體可分爲兩類:有損壓縮和無損壓縮。

①有損壓縮
有損壓縮的基本依據是:人的眼睛對光線的敏感度遠高於對顏色的敏感度,光線對景物的做用比顏色的做用更爲重要。有損壓縮的原理是:保持顏色的逐漸變化,刪除圖像中顏色的忽然變化。生物學中的大量實驗證實,人類大腦會自發地利用與附近最接近的顏色來填補所丟失的顏色。有損壓縮的具體實現方法就是刪除圖像中景物邊緣的某些顏色部分。當在屏幕上看這幅圖時,大腦會利用在景物上看到的顏色填補所丟失的顏色部分。利用有損壓縮技術,某些數據被有意地刪除了,而且在圖片從新加載至內存中時這些數據也不會還原,所以被稱爲是「有損」的。有損壓縮技術能夠靈活地設置壓縮率。
無能否認,利用有損壓縮技術能夠在位圖持久化存儲的過程當中大大地壓縮圖片的存儲大小,可是會影響圖像質量,這一點在壓縮率很高時尤爲明顯。因此須要選擇恰當的壓縮率。

②無損壓縮
無損壓縮的基本原理是:相同的顏色信息只需保存一次。具體過程是:首先會肯定圖像中哪些區域是相同的,哪些是不一樣的。包括了重複數據的區域就能夠被壓縮,只須要記錄該區域的起始點便可。
從本質上看,無損壓縮的方法經過刪除一些重複數據,也能在位圖持久化存儲的過程當中減小要在磁盤上保存的圖片大小。可是,若是將該圖片從新讀取到內存中,重複數據會被還原。所以,無損壓縮的方法並不能減小圖片的內存佔用量,若是要減小圖片佔用內存的容量,就必須使用有損壓縮方法。
無損壓縮方法的優勢是可以比較好地保存圖像的質量,可是相對來講這種方法的壓縮率比較低。
對比分析:有損壓縮壓縮率高並且能夠靈活設置壓縮率,而且刪除的數據不可還原,所以能夠減小圖片的內存佔用,可是對圖片質量會有必定程度的影響;無損壓縮能夠很好地保存圖片質量,也能保證必定的壓縮率雖然沒有有損壓縮那麼高,而且無損壓縮刪除的數據在從新加載至內存時會被還原,所以不能夠減小圖片的內存佔用。
 
四、位深與色深
咱們知道了圖片在內存中和在磁盤上的兩種不一樣的表示形式:前者爲Bitmap,後者爲各類壓縮格式。這裏介紹一下位深與色深的概念:
①色深
色深指的是每個像素點用多少bit來存儲ARGB值,屬於圖片自身的一種屬性。色深能夠用來衡量一張圖片的色彩處理能力(即色彩豐富程度)。
典型的色深是8-bit、16-bit、24-bit和32-bit等。
上述的Bitmap.Config參數的值指的就是色深。好比ARGB_8888方式的色深爲32位,RGB_565方式的色深是16位。
 
②位深
位深指的是在對Bitmap進行壓縮存儲時存儲每一個像素所用的bit數,主要用於存儲。因爲是「壓縮」存儲,因此位深通常小於或等於色深 。
舉個例子:某張圖片100像素*100像素 色深32位(ARGB_8888),保存時位深度爲24位,那麼: 
該圖片在內存中所佔大小爲:100 * 100 * (32 / 8) Byte 
在文件中所佔大小爲 100 * 100 * ( 24/ 8 ) * 壓縮率 Byte
 
五、常見的壓縮格式
Bitmap的壓縮格式就是最終持久化存儲獲得的圖片格式,通常由後綴名便可看出該圖片採用了何種壓縮方式。不一樣的壓縮方式的壓縮算法不同。常見的主要有:
①Gif 
Gif是一種基於LZW算法的無損壓縮格式,其壓縮率通常在50%左右。Gif可插入多幀,從而實現動畫效果。所以Gif圖片分爲靜態GIF和動畫GIF兩種GIF格式。
因爲Gif以8位顏色壓縮存儲單個位圖,因此它最多隻能用256種顏色來表現物體,對於色彩複雜的物體它就力不從心了。所以Gif不適合用於色彩很是豐富的圖片的壓縮存儲,好比拍攝的真彩圖片等。
 
②BMP
BMP是標準圖形格式,它是包括Windows在內多種操做系統圖像展示的終極形式。其本質就是Bitmap對象直接持久化保存的位圖文件格式,因爲沒有進行壓縮存儲,所以體積很是大,故而不適合在網絡上傳輸。同時也是由於這種格式是對Bitmap對象的直接存儲而沒有進行壓縮,所以咱們在討論壓縮格式時每每忽略這一種。
 
③PNG
PNG格式自己的設計目的是替代GIF格式,因此它與GIF 有更多類似的地方。PNG格式也屬於無損壓縮,其位深爲32位,也就是說它支持全部的顏色類型。
一樣是無損壓縮,PNG的壓縮率高於Gif格式,並且PNG支持的顏色數量也遠高於Gif,所以:若是是對靜態圖片進行無損壓縮,優先使用PNG取代Gif,由於PNG壓縮率高、色彩好;可是PNG不支持動畫效果。因此Gif仍然有用武之地。
PNG缺點是:因爲是無損壓縮,所以PNG文件的體積每每比較大。若是在項目中多處使用PNG圖片文件,那麼在APP瘦身時須要對PNG文件進行優化以減小APP體積大小。具體作法後面會詳細介紹。
 
④JPEG
JPEG是一種有損壓縮格式,JPEG圖片以24位顏色壓縮存儲單個位圖。也就是說,JPEG不支持透明通道。JPEG也不支持多幀動畫。
由於是有損壓縮,因此須要注意控制壓縮率以避免圖片質量太差。
JPG和JPEG沒有區別,全名、正式擴展名是JPEG。但因DOS、Windows95等早期系統採用的8.3命名規則只支持最長3字符的擴展名,爲了兼容採用了.jpg。也因歷史習慣和兼容性的考慮,.jpg目前更流行。
JPEG2000做爲JPEG的升級版,其壓縮率比JPEG高約30%左右,同時支持有損和無損壓縮。JPEG2000格式有一個極其重要的特徵在於它能實現漸進傳輸,即先傳輸圖像的輪廓,而後逐步傳輸數據,不斷提升圖像質量,讓圖像由朦朧到清晰顯示。此外,JPEG2000還支持所謂的「感興趣區域」特性,也就是能夠任意指定影像上感興趣區域的壓縮質量;另外,JPEG2000還能夠選擇指定的部分先解壓縮來加載到內存中。JPEG2000和JPEG相比優點明顯,且向下兼容,所以可取代傳統的JPEG格式。
 
 ⑤WebP
WebP 是 Google 在 2010 年發佈的圖片格式,但願以更高的壓縮率替代 JPEG。它用 VP8 視頻幀內編碼做爲其算法基礎,取得了不錯的壓縮效果。WebP支持有損和無損壓縮、支持完整的透明通道、也支持多幀動畫,而且沒有版權問題,是一種很是理想的圖片格式。WebP支持動圖,基本取代gif。
WebP不只集成了PNG、JPEG和Gif的全部功能,並且相同質量的無損壓縮WebP圖片體積比PNG小大約26%;若是是有損壓縮,相同質量的WebP圖片體積比JPEG小25%-34%。
不少人會認爲,既然WebP功能完善、壓縮率更高,那直接用WebP取代上述全部的圖片壓縮格式不就好了嗎?其實否則,WebP也有其缺點:咱們知道JPEG是有損壓縮而PNG是無損壓縮,因此JPEG的壓縮率高於PNG;可是有損壓縮的算法決定了其壓縮時間必定是高於無損壓縮的,也就是說JPEG的壓縮時間高於PNG。而WebP不管是無損仍是有損壓縮,壓縮率都分別高於PNG和JPEG;與其相對應的是其壓縮時間也比它們長的多。經測試,WebP圖片的編碼時間比JPEG長8倍。能夠看出,時間和空間是一對矛盾;若是想要節省更多的空間,必然要付出額外的時間;若是想要節省時間,那麼必然要付出空間的代價。這取決於咱們在實際中對於時空不一樣的需求程度來作出選擇。
無論怎麼說,WebP仍是一種強大的、理想的圖片壓縮格式,而且藉由 Google 在網絡世界的影響力,WebP 在幾年的時間內已經獲得了普遍的應用。看看你手機裏的 App:微博、微信、QQ、淘寶等等,每一個 App 裏都有 WebP 的身影。
另外,WebP是Android4.0才引入的一種圖片壓縮格式,若是想要在Android4.0之前的版本支持WebP格式的圖片,那麼須要藉助於第三方庫來支持WebP格式圖片,例如:webp-android-backport函數庫,該開源項目在GitHub地址爲: https://github.com/alexey-pelykh/webp-android-backport  固然考慮到通常的Android開發中只須要向下兼容到Android4.0便可,因此也能夠忽略這個問題。
 
目前來講,以上所述的五種壓縮格式,Android操做系統都提供了原生支持;可是在上層能直接調用的編碼方式只有 JPEG、PNG、WebP 這三種。具體的,能夠查看Bitmap類的枚舉內部類CompressFormat類的枚舉值來獲取上層能調用的圖片編碼方式。你會發現枚舉值也是JPEG、PNG和WEBP三種。
若是咱們想要在應用層使用Gif格式圖片,須要自行引入第三方函數庫來提供對Gif格式圖片的支持。不過通常咱們用WebP取代Gif。
所以,咱們只須要比較分析PNG、JPEG、WebP這三種壓縮格式便可。
 
比較分析:
①對於攝影類等真彩圖片:由於咱們對這類色彩豐富的圖片的透明度沒有要求(通常默認爲不透明),能夠採用JPEG有損壓縮格式,由於JPEG自己就不支持透明度,並且由於是有損壓縮,因此儘管會犧牲一丟丟照片的質量可是能夠大大減小體積。若是非要採用PNG格式,那麼首先由於PNG支持透明度通道,因此明明沒必要要的透明度值卻會被存儲;其次由於是無損壓縮,因此壓縮率不會很高從而致使保存的圖片很是大!綜上比較,建議採用JPEG格式,不要用PNG格式。
JPEG格式能夠與Bitmap.Config參數值爲RGB_565搭配使用,這是一個理想的設置。
 
②對於logo圖標、背景圖等圖片:這類圖片的特色是每每是有大塊的顏色相同的區域,這與無損壓縮的思路不謀而合(即刪除重複數據)。並且這類圖片對透明度是有要求的,所以能夠採用PNG無損壓縮格式;儘管使用PNG格式會讓圖片有點大,可是能夠在後續進行PNG圖片優化以對APP體積進行瘦身。若是非要採用JPEG格式,那麼因爲有損壓縮的原理(利用人腦的自動補全機制),可能會隨機地丟失一些線條致使最終的圖片徹底不是想要的效果。綜上比較,建議使用PNG格式,不要用JPEG格式。
PNG格式能夠與Bitmap.Config參數值爲ARGB_8888搭配使用,這是一個理想的設置。
固然,以上兩種狀況,咱們均可以使用WebP取代PNG或JPEG,若是咱們想要這麼作的話。若是你的項目中對空間的需求程度更高,你徹底有理由這麼作。可是若是你對空間需求程度還OK,你也能夠選擇分狀況使用PNG或JPEG格式。
 
六、圖片優化
圖片優化屬於Android性能優化的一種,這裏主要是針對PNG圖片的大小進行優化,畢竟PNG這種無損壓縮格式每每會致使圖片都比較大。除非你的項目已經全面支持了WebP格式,不然對PNG格式圖片的優化都會是你必須考慮的一點,這有利於減小APP的體積大小。
對PNG圖片進行優化的思想是:減小PNG圖片的體積,經常使用方式有:
①無損壓縮工具ImageOptim
ImageOptim是一種無損壓縮工具,因此你不用擔憂利用該工具對PNG圖片進行壓縮後圖片質量會受影響。它的壓縮原理是:優化PNG壓縮參數,移除冗餘元數據以及非必需的顏色配置文件等,在不犧牲圖片質量的前提下,既減小了PNG圖片的大小,又提升了其加載的速度。
ImageOptim工具的網址爲: https://imageoptim.com
 
②有損壓縮工具ImageAlpha
ImageAlpha與ImageOptim是同一個做者,不過ImageAlpha屬於有損壓縮,所以圖片質量會受到影響。因此使用ImageAlpha對PNG圖片進行壓縮後,必須讓設計師檢視一下優化後的PNG圖片,以避免影響APP的視覺效果。可是ImageAlpha的優勢是能夠極大減小PNG圖片的體積大小。
ImageAlpha工具的網址爲: https://pngmini.com
 
③有損壓縮TinyPNG
前面兩個工具是應用程序,TinyPNG是一個Web站點。你能夠上傳原PNG圖片,它對PNG圖片壓縮後你就能夠下載優化後的結果了。由於TinyPNG也是有損壓縮,因此優缺點同②
TinyPNG的網址爲: https://tinypng.com
以上方案都屬於對PNG圖片進行二次壓縮(有的是有損有的是無損),咱們須要在圖片質量和圖片大小這對矛盾中根據實際狀況進行選擇。
 
④PNG/JPEG轉換爲WebP
若是不想對PNG圖片進行二次壓縮,能夠考慮直接將其替換爲WebP格式的圖片。另外,咱們對JPEG格式的圖片也能夠這麼替換。畢竟WebP不管是與PNG仍是與JPEG格式想比,壓縮後體積大小都小不少。WebP轉換工具備:
智圖,這是一個圖片優化平臺,地址爲: https://zhitu.isux.us
iSparta,這是一個針對PNG圖片的二次壓縮和格式轉換工具,地址爲: https://isparta.github.io
 
⑤使用NinePatch格式的PNG圖
.9.png圖片格式簡稱爲NinaPatch圖,本質上仍然是PNG格式圖片。不過它的優勢是體積小、拉伸不變形,可以很好地適配Android各類機型。咱們能夠利用Android Studio提供的功能,右鍵一張PNG圖片點擊「create 9=Patch File」便可完成轉換。
總結:不管是二次壓縮仍是格式轉換,不管是有損二次壓縮仍是無損二次壓縮,咱們都須要根據實際需求進行方案和工具的選擇。
 
咱們已經知道了Android中圖片內存中的表示形式(Bitmap)和磁盤上的表示形式(各類壓縮格式),以及兩者的關係(壓縮和解壓縮的過程)。下面具體看看Bitmap的使用方式和注意事項,畢竟磁盤上存儲的圖片終究仍是要加載到內存中以Bitmap的形式進行展現的。
 
4、Bitmap的簡單使用
先看看Bitmap的簡單使用方式:
一、Bitmap的加載方法
Bitmap的工廠類BitmapFactory提供了四類靜態方法用於加載Bitmap對象:decodeFile、decodeResource、decodeStream、decodeByteArray。
分別表明從本地圖片文件、項目資源文件、流對象(能夠是網絡輸入流對象或本地文件輸入流對象)、字節序列中加載一個Bitmap對象。
以前講的圖片的三個來源,均可以找到對應的decodeXXXX方法來獲取該圖片對應的Bitmap對象。
舉個例子,假設須要經過網絡請求一張圖片資源並展現:先處理該網絡請求並獲得返回結果的輸入流對象;依據該流對象調用decodeStream方法獲得一個Bitmap對象,該對象即表示這張圖片的內容;最後經過ImageView的setImageBitmap()方法顯示該圖片便可。
 
二、Bitmap的壓縮存儲方法
Bitmap的壓縮存儲與Bitmap的加載是相反的過程,經過compress()方法來實現,該方法原型爲:
compress(Bitmap.CompressFormat format, int quality, OutputStream stream)
format參數表示壓縮存儲的格式,可選爲PNG、JPEG和WEBP;quality表示壓縮率,取值在0~100之間,100表示未壓縮,30表示壓縮爲原大小的30%,可是該參數在format值爲PNG時無效,由於PNG屬於無損壓縮沒法設置壓縮率;stream就是但願輸出到某個位置的輸出流對象,好比某個文件的輸出流對象。
經過compress()方法能夠將Bitmap按照指定的格式和壓縮率(非PNG格式時)壓縮存儲到指定的位置。
 
三、BitmapFactory.Options類
BitmapFactory是Bitmap的工廠類,經過BitmapFactory的靜態方法來建立Bitmap對象;BitmapFactory.Options類表明對Bitmap對象的屬性設置(配置)。通常狀況下,咱們調用decodeXXXX方法時不須要傳遞一個BitmapFactory.Options對象做爲參數,所以此時是利用默認的配置信息來建立Bitmap對象。若是須要對建立的Bitmap對象進行自定義的配置,那麼就須要給decodeXXXX方法傳遞一個BitmapFactory.Options對象,該對象包含了對Bitmap對象的配置信息。
經過BitmapFactory.Options類的構造器建立BitmapFactory.Options對象。該對象包含的Bitmap配置信息即爲該對象的各屬性值,主要有:

介紹一下比較很差理解的屬性:

①inJustDecodeBounds:這個屬性表示是否只掃描輪廓,默認爲false。若是該屬性爲true,decodeXXXX方法不會返回一個Bitmap對象(即不會爲Bitmap分配內存)而是返回null。那若是decodeXXXX方法再也不分配內存以建立一個Bitmap對象,那麼還有什麼用呢?答案就是:掃描輪廓。

BitmapFactory.Options對象的outWidth和outHeight屬性分別表明Bitmap對象的寬和高,可是這兩個屬性在Bitmap對象未建立以前顯然默認爲0,默認只有在Bitmap對象建立後才能被賦予正確的值。而當inJustDecodeBounds屬性爲true,雖然不會分配內存建立Bitmap對象,可是會掃描輪廓來給outWidth和outHeight屬性賦值,就至關於繞過了Bitmap對象建立的這一步提早獲取到Bitmap對象的寬高值。那這個屬性到底有啥用呢?具體用處體如今Bitmap的採樣率計算中,後面會詳細介紹。

②inSample:這個表示Bitmap的採樣率,默認爲1。好比說有一張圖片是2048像素X1024像素,那麼默認狀況下該圖片加載到內存中的Bitmap對象尺寸也是2048像素X1024像素。若是採用的是ARGB_8888方式,那麼該Bitmap對象加載所消耗的內存爲2048X1024X4/1024/1024=8M。這只是一張圖片消耗的內存,若是當前活動須要加載幾張甚至幾十張圖片,那麼會致使嚴重的OOM錯誤。

OOM錯誤:儘管Android設備內存大小可能達到好幾個G(好比4G),可是Andorid中每一個應用其運行內存都有一個閾值,超過這個閾值就會引起out of memory即OOM錯誤(內存溢出錯誤)。由於如今市場上流行的手機設備其操做系統都是在Andori原生操做系統基礎上的拓展,因此不一樣的設備環境中這個內存閾值不同。能夠經過如下方法獲取到當前應用所分配的內存閾值大小,單位爲字節: Runtime.getRuntime().maxMemory();

儘管咱們確實能夠經過設置來修改這個閾值大小以提升應用的最大分配內存(具體方式是在在Manifest中設置android.largeHeap="true"),可是須要注意的是:內存是一種很寶貴的資源,不加考慮地無腦給每一個應用提升最大分配內存是一個糟糕的選擇。由於手機總內存相比較每一個應用默認的最大分配內存雖然高不少,可是手機中的應用數量是很是多的,每一個應用都修改其運行內存閾值爲幾百MB甚至一個G,這很嚴重影響手機性能!另外,若是應用的最大分配內存很高,這意味着其垃圾回收工做也會變得更加耗時,這也會影響應用和手機的性能。因此,這個方案須要慎重考慮不能濫用。

關於這個方案的理解能夠參考一位大神的解釋:「在一些特殊的情景下,你能夠經過在manifest的application標籤下添加largeHeap=true的屬性來爲應用聲明一個更大的heap空間。而後,你能夠經過getLargeMemoryClass()來獲取到這個更大的heap size閾值。然而,聲明獲得更大Heap閾值的本意是爲了一小部分會消耗大量RAM的應用(例如一個大圖片的編輯應用)。不要輕易的由於你須要使用更多的內存而去請求一個大的Heap Size。只有當你清楚的知道哪裏會使用大量的內存而且知道爲何這些內存必須被保留時纔去使用large heap。所以請謹慎使用large heap屬性。使用額外的內存空間會影響系統總體的用戶體驗,而且會使得每次gc的運行時間更長。在任務切換時,系統的性能會大打折扣。另外, large heap並不必定可以獲取到更大的heap。在某些有嚴格限制的機器上,large heap的大小和一般的heap size是同樣的。所以即便你申請了large heap,你仍是應該經過執行getMemoryClass()來檢查實際獲取到的heap大小。」

綜上,咱們已經知道了Bitmap的加載是一個很耗內存的操做,特別是在大位圖的狀況下。這很容易引起OOM錯誤,而咱們又不能輕易地經過修改或提供應用的內存閾值來避免這個錯誤。那麼咱們該怎麼作呢?答案就是:利用這裏所說的採樣率屬性來建立一個原Bitmap的子採樣版本。這也是官方推薦的對於大位圖加載的OOM問題的解決方案。其具體思想爲:好比仍是那張尺寸爲2048像素X1024像素圖片,在inSample值默認爲1的狀況下,咱們如今已經知道它加載到內存中默認是一個2048像素X1024像素大位圖了。咱們能夠將inSample設置爲2,那麼該圖片加載到內存中的位圖寬高都會變成原寬高的1/2,即1024像素X512像素。進一步,若是inSample值設置爲4,那麼位圖尺寸會變成512像素X256像素,這個時候該位圖所消耗的內存(假設仍是ARGB_8888方式)爲512X256X4/1024/1024=0.5M,能夠看出從8M到0.5M,這極大的節省了內存資源從而避免了OOM錯誤。

切記:官方對於inSample值的要求是,必須爲2的冪,好比二、四、8...等整數值。

這裏會有兩個疑問:第一:經過設置inSample屬性值來建立一個原大位圖的子採樣版本的方式來下降內存消耗,聽不上確實很不錯。可是這不會致使圖片嚴重失真嗎?畢竟你丟失了那麼多像素點,這意味着你丟失了不少顏色信息。對這個疑問的解釋是:儘管在採樣的過程確實會丟失不少像素點,可是原位圖的尺寸也在減少,其像素密度是不變的。好比說若是inSample值爲2,那麼子採樣版本的像素點數量是原來的1/4,可是子採樣版本的顯示尺寸(區域面積)也會變成原來的1/4,這樣的話像素密碼是不變的所以圖片不用擔憂嚴重失真問題。第二:inSample值如何選取才是最佳?這其實取決於ImageView的尺寸,具體採樣率的計算方式後面會詳細介紹。

③inPreferredConfig:該屬性指定Bitmap的色深值,該屬性類型爲Bitmap.Config值。

例如你能夠指定某圖片加載爲Bitmap對象的色深模式爲ARGB_8888,即:options.inPreferredConfig=Bitmap.Config.ARGB_8888;

④isMutable:該屬性表示經過decodeXXXX方法建立的Bitmap對象其表明的圖片內容是否容許被外部修改,好比利用Canvas從新繪製其內容等。默認爲false,即不容許被外部操做修改。

利用這些屬性定製BitmapFactory.Options對象,從而靈活地按照本身的需求配置建立的Bitmap對象。

 

5、Bitmap的進階使用

一、高效地加載大位圖

上面剛說了大位圖加載時的OOM問題,解決方式是經過inSample屬性建立一個原位圖的子採樣版本以減低內存。那麼這裏的採樣率inSample值如何選取最好呢?這裏咱們利用官方推薦的採樣率最佳計算方式:基本步驟就是:①獲取位圖原尺寸 ②獲取ImageView即最終圖片顯示的尺寸  ③依據兩種尺寸計算採樣率(或縮放比例)。

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // 位圖的原寬高經過options對象獲取
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;
     //當要顯示的目標大小和圖像的實際大小比較接近時,會產生不必的採樣,先除以2再判斷以防止過分採樣
        while ((halfHeight / inSampleSize) >= reqHeight
                && (halfWidth / inSampleSize) >= reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

 

依據上面的最佳採樣率計算方法,進一步能夠封裝出利用最佳採樣率建立子採樣版本再建立位圖對象的方法,這裏以從項目圖片資源文件加載Bitmap對象爲例:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
   //由於inJustDecodeBounds爲true,因此不會建立Bitmap對象只會掃描輪廓從而給options對象的寬高屬性賦值
    BitmapFactory.decodeResource(res, resId, options);

    // 計算最佳採樣率
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // 記得將inJustDecodeBounds屬性設置回false值
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

 

二、Bitmap加載時的異步問題

因爲圖片的來源有三種,若是是項目圖片資源文件的加載,通常採起了子採樣版本加載方案後不會致使ANR問題,畢竟每張圖加載消耗的內存不會很大了。可是對於本地圖片文件和網絡圖片資源,因爲分別涉及到文件讀取和網絡請求,因此屬於耗時操做。爲了不ANR的產生,必須將圖片加載爲Bitmap對象的過程放入工做線程中;獲取到Bitmap對象後再回到UI線程設置ImageView的顯示。舉個例子,若是採用AsyncTask做爲咱們的異步處理方案,那麼代碼以下:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
         private final ImageView iv;
         private int id = 0;
 
         public BitmapWorkerTask(ImageView imageView) {
             iv = imageView;
          }
 
         // Decode image in background.
         @Override
         protected Bitmap doInBackground(Integer... params) {
             id = params[0];
            //假設ImageView尺寸爲500X500,爲了方便仍是以項目資源文件的加載方式爲例,由於這能夠複用上面封裝的方法
             return decodeSampledBitmapFromResource(getResources(), id, 500, 500);
        }
 
         @Override
         protected void onPostExecute(Bitmap bitmap) {
             iv.setImageBitmap(bitmap);
         }
     }

該方案中,doInBackground方法執行在子線程,用來處理 」圖片文件讀取操做+Bitmap對象的高效加載操做」 或 」網絡請求圖片資源操做+Bimap對象的高效加載操做」等兩種情形下的耗時操做。onPostExecute方法執行在UI線程,用於設置ImageView的顯示內容。看上去這個方案很完美,可是有一個很隱晦的嚴重問題:

由當前活動啓動了BitmapWorkerTask任務後:當咱們退出當前活動時,因爲異步任務只依賴於UI線程因此BitmapWorkerTask任務會繼續執行。正常的操做是遍歷當前活動實例的對象圖來釋放各對象的內存以銷燬該活動,可是因爲當前活動實例的ImageView引用被BitmapWorkerTask對象持有,並且仍是強引用關係。這會致使Activity實例沒法被銷燬,引起內存泄露問題。內存泄露問題會進一步致使內存溢出錯誤。

爲了解決這個問題,咱們只須要讓BitmapWorkerTask類持有ImageView的弱引用便可。這樣當活動退出時,BitmapWorkerTask對象因爲持有的是ImageView的弱引用,因此ImageView對象會被回收,繼而Activity實例獲得銷燬,從而避免了內存泄露問題。具體修改後的代碼以下:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
        private final WeakReference<ImageView> imageViewReference;
        private int data = 0;

        public BitmapWorkerTask(ImageView imageView) {
            // 用弱引用來關聯這個imageview!弱引用是避免android 在各類callback回調裏發生內存泄露的最佳方法!
            //而軟引用則是作緩存的最佳方法 二者不要搞混了!
            imageViewReference = new WeakReference<ImageView>(imageView);
        }

        // Decode image in background.
        @Override
        protected Bitmap doInBackground(Integer... params) {
            data = params[0];
            return decodeSampledBitmapFromResource(getResources(), data, 100, 100);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            //當後臺線程結束後 先看看ImageView對象是否被回收:若是被回收就什麼也不作,等着系統回收他的資源
            //若是ImageView對象沒被回收的話,設置其顯示內容便可
            if (imageViewReference != null && bitmap != null) {
                final ImageView imageView = imageViewReference.get();
                if (imageView != null) {
                    imageView.setImageBitmap(bitmap);
                }
            }
        }
    }

 

拓展:①WeakReference是弱引用,其中保存的對象實例能夠被GC回收掉。這個類一般用於在某處保存對象引用,而又不干擾該對象被GC回收,能夠用於避免內存泄露。②SoftReference是軟引用,它保存的對象實例,不會被GC輕易回收,除非JVM即將OutOfMemory,不然不會被GC回收。這個特性使得它很是適合用於設計Cache緩存。緩存能夠省去重複加載的操做,並且緩存屬於內存所以讀取數據很是快,因此咱們天然不但願緩存內容被GC輕易地回收掉;可是由於緩存本質上就是一種內存資源,因此在內存緊張時咱們須要能釋放一部分緩存空間來避免OOM錯誤。綜上,軟引用很是適合用於設計緩存Cache。可是,這只是早些時候的緩存設計思想,好比在Android2.3版本以前。在Android2.3版本以後,JVM的垃圾收集器開始更積極地回收軟引用對象,這使得本來的緩存設計思想失效了。由於若是使用軟引用來實現緩存,那麼動不動緩存對象就被GC回收掉實在是沒法接受。因此,Android2.3以後對於緩存的設計使用的是強引用關係(也就是普通對象引用關係)。不少人會問這樣不會因爲強引用的緩存對象沒法被回收從而致使OOM錯誤嗎?確實會這樣,可是咱們只須要給緩存設置一個合理的閾值就行了。將緩存大小控制在這個閾值範圍內,就不會引起OOM錯誤了。

 

三、列表加載Bitmap時的圖片顯示錯亂問題

 咱們已經知道了如何高效地加載位圖以免OOM錯誤,還知道了如何合理地利用異步機制來避免Bitmap加載時的ANR問題和內存泄露問題。如今考慮另外一種常見的Bitmap加載問題:當咱們使用列表,如ListView、GridView和RecyclerView等來加載多個Bitmap時,可能會產生圖片顯示錯亂的問題。先看一下該問題產生的緣由。以ListView爲例:

①ListView爲了提升列表展現內容在滾動時的流暢性,使用了一種item複用機制,即:在屏幕中顯示的每一個ListView的item對應的佈局只有在第一次的時候被加載,而後緩存在convertView裏面,以後滑動改變ListView時調用的getView就會複用緩存在converView中的佈局和控件,因此可使得ListView變得流暢(由於不用重複加載佈局)。

②每一個Item中的ImageView加載圖片時每每都是異步操做,好比在子線程中進行圖片資源的網絡請求再加載爲一個Bitmap對象最後回到UI線程設置該item的ImageView的顯示內容。

③ 聽上去①是一種很是合理有效的提升列表展現流暢性的機制,②看起來也是圖片加載時很常見的一個異步操做啊。其實①和②自己都沒有問題,可是①+②+用戶滑動列表=圖片顯示錯亂!具體而言:當咱們在其中一個itemA加載圖片A的時候,因爲加載過程是異步操做須要耗費必定的時間,那麼有可能圖片A未被加載完該itemA就「滾出去了」,這個itemA可能被當作緩存應用到另外一個列表項itemB中,這個時候恰好圖片A加載完成顯示在itemB中(由於ImageView對象在緩存中被複用了),本來itemB該顯示圖片B,如今顯示圖片A。這只是最簡單的一種狀況,當滑動頻繁時這種圖片顯示錯亂問題會越發嚴重,甚至讓人毫無頭緒。

那麼如何解決這種圖片顯示錯亂問題呢?解決思路其實很是簡單:在圖片A被加載到ImageView以前作一個判斷,判斷該ImageView對象是否仍是對應的是itemA,若是是則將圖片加載到ImageView當中;若是不是則放棄加載(由於itemB已經啓動了圖片B的加載,因此不用擔憂控件出現空白的狀況)。

那麼新的問題出現了,如何判斷ImageView對象對應的item已經改變了?咱們能夠採起下面的方式:

①在每次getView的複用佈局控件時,對會被複用的控件設置一個標籤(在這裏就是對ImageView設置標籤)。標籤內容必須能夠標識不一樣的item!這裏使用圖片的url做爲標籤內容,而後再異步加載圖片。

②在圖片下載完成後要加載到ImageView以前作判斷,判斷該ImageView的標籤內容是否和圖片的url同樣:若是同樣說明ImageView沒有被複用,能夠將圖片加載到ImageView當中;若是不同,說明ListView發生了滑動,致使其餘item調用了getView從而將該ImageView的標籤改變,此時放棄圖片的加載(儘管圖片已經被下載成功了)。

總結:解決ListView異步加載Bitmap時的圖片錯亂問題的方式是:爲被複用的控件對象(即ImageView對象)設置標籤來標識item,異步任務結束後要將圖片加載到ImageView時取出標籤值進行比對是否一致:若是一致意味着沒有發生滑動,正常加載圖片;若是不同意味着發生了滑動,取消加載。

 

四、Android中的Bitmap緩存策略

若是隻是加載若干張圖片,上述的Bitmap使用方式已經絕對夠用了;可是若是在應用中須要頻繁地加載大量的圖片,特別是有些圖片會被重複加載時,這個時候利用緩存策略能夠很好地提升圖片的加載速度。好比說有幾張圖片被重複加載的頻率很高,那麼能夠在緩存中保留這幾張圖片的Bitmap對象;後續若是須要加載這些圖片,則不須要花費不少時間去從新在網絡上獲取並加載這些圖片的Bitmap對象,只須要直接向緩存中獲取以前保留下來的Bitmap對象便可。

Android中對Bitmap的緩存策略分爲兩種:

  • 內存緩存:圖像存儲在設備內存中,所以訪問速度很是快。事實上,比圖像解碼過程要快得多,因此將圖像存儲在這裏是讓app更快更穩定的一個好主意。內存緩存的惟一缺點是:它只存活於app的生命週期,這意味着一旦app被Android操做系統內存管理器關閉或殺死(所有或部分),那麼儲存在那裏的全部圖像都將丟失。因爲內存緩存本質上就是一種內存資源,因此切記:內存緩存必須設置一個最大可用的內存量。不然可能會致使臭名昭著的outOfMemoryError。
  • 磁盤緩存:圖像存儲在設備的物理存儲器上(磁盤)。磁盤緩存本質上就是設備SD卡上的某個目錄。只要app不被卸載,其磁盤緩存能夠一直安全地存儲圖片,只要有足夠的磁盤空間便可。缺點是,磁盤讀取和寫入操做可能會很慢,並且老是比訪問內存緩存慢。因爲這個緣由,所以全部的磁盤操做必須在工做線程執行,UI線程以外。不然,app會凍結,並致使ANR警報。

在實際使用中,咱們不須要強行二選一,能夠兩者都使用,畢竟各有優點。因此Android中完整的圖片緩存策略爲:先嚐試在內存緩存中查找Bitmap對象,若是有直接加載使用;若是沒有,再嘗試在磁盤緩存中查找圖片文件是否存在,若是有將其加載至內存使用;若是仍是沒有,則老老實實發送網絡請求獲取圖片資源並加載使用。須要注意的是,後面兩種狀況下的操做都必須使用異步機制以免ANR的發生。

Android中經過LruCache實現內存緩存,經過DiskLruCache實現磁盤緩存,它們採用的都是LRU(Least Recently Used)最近最少使用算法來移除緩存中的最近不常訪問的內容(變相地保留了最近常常訪問的內容)。

①內存緩存LruCache

LruCache原理:LruCache底層是使用LinkedHashMap來實現的,因此LruCache也是一個泛型類。在圖片緩存中,其鍵類型是字符串,值類型爲Bitmap。利用LinkedHashMap的accessOrder屬性能夠實現LRU算法。accessOrder屬性決定了LinkedHashMap的鏈表順序:accessOrder爲true則以訪問順序維護鏈表,即被訪問過的元素會安排到鏈表的尾部;accessorder爲false則以插入的順序維護鏈表。

而LruCache利用的正是accessOrder爲true的LinkedHashMap來實現LRU算法的。具體表現爲:

1° put:經過LinkedHashMap的put方法來實現元素的插入,插入的過程仍是要先尋找有沒有相同的key的數據,若是有則替換掉舊值,而且將該節點移到鏈表的尾部。這能夠保證最近常常訪問的內容集中保存在鏈表尾部,最近不常訪問的內存集中保存在鏈表頭部位置。在插入後若是緩存大小超過了設定的最大緩存大小(閾值),則將LinkedHashMap頭部的節點(最近不常訪問的內容)刪除,直到size小於maxSize。

2° get:經過LinkedHashMap的get方法來實現元素的訪問,因爲accessOrder爲true,所以被訪問到的元素會被調整到鏈表的尾部,所以不常被訪問的元素就會留到鏈表的頭部,當觸發清理緩存時不常被訪問的元素就會被刪除,這裏是實現LRU最關鍵的地方。

3° remove:經過LinkedHashMap的remove方法來實現元素的移除。

3° size:LruCache中很重要的兩個成員變量size和maxSize,由於清理緩存的是在size>maxSize時觸發的,所以在初始化的時候要傳入maxSize定義緩存的大小,而後重寫sizeOf方法,由於LruCache是經過sizeOf方法來計算每一個元素的大小。這裏咱們是使用LruCache來緩存圖片,因此sizeOf方法須要計算Bitmap的大小並返回。

LruCache對其緩存對象採用的是強引用關係,採用maxSize來控制緩存空間大小以免OOM錯誤。並且LruCache類在Android SDK中已經提供了,在實際使用中咱們只須要完成如下幾步便可:

  • 設計LruCache的最大緩存大小:通常是經過計算當前可用的內存大小繼而來獲取到應該設置的緩存大小
  • 建立LruCache對象:傳入最大緩存大小的參數,同時重寫sizeOf方法來設置存在LruCache裏的每一個對象的大小
  • 封裝對LruCache的數據訪問和添加操做並對外提供接口以供調用

具體代碼參考以下:

//初始化LruCache對象
public void initLruCache()
{
    //獲取當前進程的可用內存,轉換成KB單位
    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    //分配緩存的大小
    int maxSize = maxMemory / 8;
    //建立LruCache對象並重寫sizeOf方法
    lruCache = new LruCache<String, Bitmap>(maxSize)
        {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                // TODO Auto-generated method stub
                return value.getWidth() * value.getHeight() / 1024;
            }
        };
}

/**
 * 封裝將圖片存入緩存的方法
 * @param key 圖片的url轉化成的key
 * @param bitmap對象
 */
private void addBitmapToMemoryCache(String key, Bitmap bitmap)
{
    if(getBitmapFromMemoryCache(key) == null)
    {
        mLruCache.put(key, bitmap);
    }
}

//封裝從LruCache中訪問數據的方法
private Bitmap getBitmapFromMemoryCache(String key)
{
    return mLruCache.get(key);
}

/**
 * 由於外界通常獲取到的是url而不是key,所以爲了方便再作一層封裝
 * @param url http url
 * @return bitmap
 */
private Bitmap loadBitmapFromMemoryCache(String url)
{
    final String key = hashKeyFromUrl(url);
    return getBitmapFromMemoryCache(key);
}

 

②磁盤緩存DiskLruCache

因爲DiskLruCache並不屬於Android SDK的一部分,須要自行設計。與LruCache實現LRU算法的思路基本上是一致的,可是有不少不同的地方:LruCache是內存緩存,其鍵對應的值類型直接爲Bitmap;而DiskLruCache是磁盤緩存,因此其鍵對應的值類型應該是一個表明圖片文件的類。其次,前者訪問或添加元素時,查找成功能夠直接使用該Bitmap對象;後者訪問或添加元素時,查找到指定圖片文件後還須要經過文件的讀取和Bitmap的加載過程才能使用。另外,前者是在內存中的數據讀寫操做因此不須要異步;後者涉及到文件操做必須開啓子線程實現異步處理。

具體DiskLruCache的設計方案和使用方式能夠參考這篇博客:https://www.jianshu.com/p/765640fe474a

有了LruCache類和DiskLruCache類,能夠實現完整的Android圖片二級緩存策略:在具體的圖片加載時:先嚐試在LruCache中查找Bitmap對象,若是有直接拿來使用。若是沒有再嘗試在DiskLruCache中查找圖片文件,若是有將其加載爲Bitmap對象再使用,並將其添加至LruCache中;若是沒有查找到指定的圖片文件,則發送網絡請求獲取圖片資源並加載爲Bitmap對象再使用,並將其添加DiskLruCache中。

 

五、Bitmap內存管理

Android設備的內存包括本機Native內存和Dalvik(相似於JVM虛擬機)堆內存兩部分。在Android 2.3.3(API級別10)及更低版本中,位圖的支持像素數據存儲在Native內存中。它與位圖自己是分開的,Bitmap對象自己存儲在Dalvik堆中。Native內存中的像素數據不會以可預測的方式釋放,可能致使應用程序短暫超出其內存限制並崩潰。從Android 3.0(API級別11)到Android 7.1(API級別25),像素數據與相關Bitmap對象一塊兒存儲在Dalvik堆上,一塊兒交由Dalvik虛擬機的垃圾收集器來進行回收,所以比較安全。

①在Android2.3.3版本以前:

在Bitmap對象再也不使用並但願將其銷燬時,Bitmap對象自身因爲保存在Dalvik堆中,因此其自身會由GC自動回收;可是因爲Bitmap的像素數據保存在native內存中,因此必須由開發者手動調用Bitmap的recycle()方法來回收這些像素數據佔用的內存空間。

 

②在Android2.3.3版本以後:

因爲Bitmap對象和其像素數據一塊兒保存在Dalvik堆上,因此在其須要回收時只要將Bitmap引用置爲null 就好了,不須要如此麻煩的手動釋放內存操做。

固然,通常咱們在實際開發中每每向下兼容到Android4.0版本,因此你懂得。

 

③在Android3.0之後的版本,還提供了一個很好用的參數,叫options.inBitmap。若是你使用了這個屬性,那麼在調用decodeXXXX方法時會直接複用 inBitmap 所引用的那塊內存。你們都知道,不少時候ui卡頓是由於gc 操做過多而形成的。使用這個屬性能避免頻繁的內存的申請和釋放。帶來的好處就是gc操做的數量減小,這樣cpu會有更多的時間執行ui線程,界面會流暢不少,同時還能節省大量內存。簡單地說,就是內存空間被各個Bitmap對象複用以免頻繁的內存申請和釋放操做。

須要注意的是,若是要使用這個屬性,必須將BitmapFactory.Options的isMutable屬性值設置爲true,不然沒法使用這個屬性。

具體使用方式參考以下代碼:

final BitmapFactory.Options options = new BitmapFactory.Options();
        //size必須爲1 不然是使用inBitmap屬性會報異常
        options.inSampleSize = 1;
        //這個屬性必定要在用在src Bitmap decode的時候 否則你再使用哪一個inBitmap屬性去decode時候會在c++層面報異常
        //BitmapFactory: Unable to reuse an immutable bitmap as an image decoder target.
        options.inMutable = true;
        inBitmap2 = BitmapFactory.decodeFile(path1,options);
        iv.setImageBitmap(inBitmap2);
        //將inBitmap屬性表明的引用指向inBitmap2對象所在的內存空間,便可複用這塊內存區域
        options.inBitmap = inBitmap2;
        //因爲啓用了inBitmap屬性,因此後續的Bitmap加載不會申請新的內存空間而是直接複用inBitmap屬性值指向的內存空間
        iv2.setImageBitmap(BitmapFactory.decodeFile(path2,options));
        iv3.setImageBitmap(BitmapFactory.decodeFile(path3,options));
        iv4.setImageBitmap(BitmapFactory.decodeFile(path4,options));

 

補充:Android4.4之前,你要使用這個屬性,那麼要求複用內存空間的Bitmap對象大小必須同樣;可是Android4.4 之後只要求後續複用內存空間的Bitmap對象大小比inBitmap指向的內存空間要小就可使用這個屬性了。另外,若是你不一樣的imageview 使用的scaletype 不一樣,可是你這些不一樣的imageview的bitmap在加載是若是都是引用的同一個inBitmap的話,

這些圖片會相互影響。綜上,使用inBitmap這個屬性的時候 必定要當心當心再當心。

 

6、開源框架

咱們如今已經知道了,Android圖片加載的知識點和注意事項實在太多了:單個的位圖加載咱們要考慮Bitmap加載的OOM問題、異步處理問題和內存泄露問題;列表加載位圖要考慮顯示錯亂問題;頻繁大量的位圖加載時咱們要考慮二級緩存策略;咱們還有考慮不一樣版本下的Bitmap內存管理問題,在這部分最後咱們介紹了Bitmap內存複用方式,咱們須要當心使用這種方式。

那麼,能不能有一種方式讓咱們省去這麼多繁瑣的細節,方便咱們對圖片進行加載呢?答案就是:利用已有的成熟的圖片加載和緩存開源框架!好比square公司的Picasso框架、Google公司的Glide框架和Facebook公司的Fresco框架等。特別是Fresco框架,提供了三級緩存策略,很是的專業。根據APP對圖片顯示和緩存的需求從低到高排序,咱們能夠採用的方案依次爲:Bitmapfun、Picasso、Android-Universal-Image-Loader、Glide、Fresco。

這些框架能夠方便咱們實現對網絡圖片的加載和緩存操做。具體再也不贅述。

相關文章
相關標籤/搜索