原文地址
git
原創文章,未經做者容許不得轉載github
眉共春山爭秀
可憐長皺
莫將清淚溼花枝,恐花也、如人瘦算法
Bitmap是Android圖片處理這塊繞不過的一個主題,在處理Bitmap緩存這方面,通常會分爲兩部分:內存緩存和磁盤緩存。
磁盤緩存這塊呢,經常使用的就是使用Bitmap的compress
函數,根據實際需求壓縮爲想要的圖片文件。對於常規的帶有透明度的圖片來講,選擇無損壓縮成PNG或者Webp文件,二次讀取展現的Bitmap通常肉眼看不出差異。而對於一些特殊的Bitmap而言,這種存儲方式就再也不適用。譬如我最近遇到的一種狀況,獲得的Bitmap數據中,透明部分佔了絕大多數。這種Bitmap能夠毫無問題地直接展現出來。但存儲爲PNG或者Webp文件後,二次讀取卻缺損嚴重,根本沒法還原。緩存
將Bitmap毫無損耗的作磁盤緩存。我想的第一種方案,就是將Bitmap的全部數據存儲爲文件,對此能夠利用他的copyPixelsToBuffer
函數,對此擴展函數以下所示:bash
fun Bitmap.saveUndamaged(dir:String){
// 文件後綴能夠是任意格式,只要是文件便可
val f = File(dir)
val byteBuffer = ByteBuffer.allocate(byteCount)
copyPixelsToBuffer(byteBuffer)
val byteArray = byteBuffer.array()
file.writeBytes(byteArray)
}
複製代碼
那麼讀取的時候,這個時候咱們有了文件的ByteArray
流,是否是隻須要利用BitmapFactory.decodeByteArray
這個函數就能夠拿到原始位圖了呢?
答案是否認,存儲的ByteArray磁盤文件只有原始位圖的RGBA信息,缺失了位圖的寬高,因此即便使用了BitmapFactory.decodeByteArray
函數,也是沒法還原位圖的。
此時,還原位圖的真正方式以下:app
// bitmap的寬高信息必須從外界傳入
fun getUndamagedBitmap(dir:String, size:Size):Bitmap{
val b = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888)
b.copyPixelsFromBuffer(ByteBuffer.wrap(File(dir).readBytes()))
return b
}
複製代碼
獲得了位圖的RGBA數據後,須要使用如上方式才能還原位圖。首先咱們建立一個等寬高的空白位圖,而後將RGBA數據填充進去。這樣就能遠遠本版的還原位圖。可是務必注意這裏的寬高必定要和原始位圖相同,不然展現的位圖將是錯亂不堪函數
目前爲止,這種方案實施起來還算可行,文件的讀取速度還算能夠接受。好一點的手機上,基本都在20ms左右徘徊*【這個時間視位圖數據量而定】*。但這種方式一樣有一個缺點,就是文件存儲空間過大。相同的Bitmap,儲存原始數據的文件比PNG圖片要打上十幾倍甚至幾十倍。因此重點強調!!!若是手機磁盤空間吃緊的話,那麼不建議使用這種方式。ui
既然原始數據存儲佔用空間大,那麼原始數據能不能再壓縮呢?針對我遇到的這種狀況,Bitmap大部分數據爲0-純透明
,利用一些壓縮算法來壓縮,讀取的時候再對數據還原是否可行呢?
對此我進行了嘗試,使用的是GZIP
壓縮,代碼展現以下:this
fun Bitmap.saveUndamaged(dir: String) {
val byteBuffer = ByteBuffer.allocate(byteCount)
copyPixelsToBuffer(byteBuffer)
val byteArray = byteBuffer.array()
// 這裏的後綴一樣能夠是任意格式,存儲不針對文件格式,只須要Byte數據
val fileOut = FileOutputStream(File(dir))
val zipOutputStream = GZIPOutputStream(fileOut)
zipOutputStream.write(byteArray)
zipOutputStream.close()
fileOut.close()
}
複製代碼
那麼,同理再讀取時,須要對文件作解壓縮處理,而後生成Bitmap:spa
val file = File(dir)
val zip = GZIPInputStream(file)
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(zip.readBytes()))
zip.close()
file.close()
複製代碼
對數據作壓縮處理後,空間佔用會小不少。但相對圖片文件來講,依然仍是比較大的。一樣的,用空間換時間,空間佔用小了。讀取時間必然就是長了,這種解壓縮讀取的話,時間相比上要比讀取原始數據文件多了一兩倍。孰輕孰重,還需根據需求自行定奪。
同理。既然能夠存儲爲文件,那麼必然能夠做內存緩存。只要稍微將上述方法,更換部分代碼便可。
緩存到內存中:
class BitmapLru(val size: Size, val data: ByteArray)
fun Bitmap.lruCache(): BitmapLru {
val array = byteArray()
val out = ByteArrayOutputStream()
val zip = GZIPOutputStream(out)
zip.write(array)
zip.close()
// 在這裏zip要及時關閉,不然讀取壓縮數據時會出現異常
val data = out.toByteArray()
out.close()
return BitmapLru(Size(width, height), data)
}
複製代碼
從內存中解壓生成原始位圖:
fun BitmapLru?.lruToBitmap(): Bitmap? {
this?.apply {
val s = System.currentTimeMillis()
val inb = ByteArrayInputStream(data)
val zip = GZIPInputStream(inb)
val bitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(zip.readBytes()))
zip.close()
inb.close()
Log.d("lruToBitmap", "lru to bitmap cost :${System.currentTimeMillis() - s} ")
return bitmap
}
return null
}
複製代碼
在這裏須要注意的是,在壓縮數據時,必定要及時關閉
GZIPOutputStream
。不然在解壓縮時,會拋出EOFException: Unexpected end of ZLIB input stream
異常。
兩種無損存儲方案,就是空間和時間的選擇問題。手機空間支持,就存儲原始文件;空間吃緊,可是時間又容許,就選擇壓縮原始數據方案。
好了~~~以上就是此次的分享,若是你們對音視頻感興趣的話,歡迎關注個人Github項目MediaLearn