在學(xué)習(xí)圖片加載時圖片很大的時候直接加載到內(nèi)存或則直接原圖繪制到 ImageView中會導(dǎo)致OOM問題溶耘;
解決思路是利用BitmapFactory的系列方法中有個帶有 BitmapFactory.Options 參數(shù)的方法,這個參數(shù)可以通過設(shè)置
options.inSampleSize = Int ;? //設(shè)置圖片采樣率
這個圖片采樣率的意思就是幾個像素點(diǎn)取一個基显,例如原圖的像素為 1000 * 1000? 設(shè)置采樣率為2
那么就是在橫向和縱向上每2個像素點(diǎn)采樣一次 最終得到一個 (1000/2)*(1000/2)的bitmap圖片加載到內(nèi)存中,從而達(dá)到占用內(nèi)存變小的目的足画;
但是我們獲取的圖片的大小是不確定的脐区,我們的控件尺寸在不同分辨率的設(shè)備上大小也不一樣;所以我們不能給一個固定的采樣率颇玷,需要根據(jù)獲取的圖片大小和控件大小來決定采樣率笨农;
計算采樣率:
options.inJustDecodeBounds =true
options中該屬性設(shè)置為true時,調(diào)用BitmapFactory的帶options的系列方法帖渠,bitmap不會載入內(nèi)存谒亦,方法返回null。但是方法會把bitmap的一些信息寫入options空郊,這樣就可以在不消耗大量內(nèi)存的情況下得到圖片資源的尺寸信息份招;
這里以?BitmapFactory.decodeStream(inputStream,null,options)? 方法為例:
val options = BitmapFactory.Options()
options.inJustDecodeBounds =true
BitmapFactory.decodeStream(inputStream,null,options)
//得到控件的寬高尺寸
val viewWidth =binding.imageView.measuredWidth
val viewHeight =binding.imageView.measuredHeight
//利用options中的outWidth 和 outHeight 參數(shù)和控件尺寸計算橫向和縱向的最合適采樣率
val x:Float = (options.outWidth/viewWidth).toFloat()
val y:Float = (options.outHeight/viewHeight).toFloat()
//下面這個是計算采樣率的策略,可以有不同的策略狞甚;
//如果控件的寬高至少有一個是小于圖片的寬高的時候才進(jìn)行采樣率計算
if ( viewHeight<options.outHeight || viewWidth<options.outWidth ) {
? ? // 取較大的那個作為采樣率锁摔,也有的策略取小的那個?
?????val s = if (x<y)? y? else? x
? ? ?options.inSampleSize = s.toInt(); //設(shè)置圖片采樣率
?????Log.e("qqq",""+ s +">>>>>>>" + s.toInt())
}
設(shè)置好采樣率后,重新把?options.inJustDecodeBounds = false 然后就可以采用上面的采樣率加載圖片到控件中了
val bitmap = BitmapFactory.decodeStream(inputStream,null,options)
imageView.setImageResource(bitmap)
這時候我以為大功告成了哼审,結(jié)果發(fā)現(xiàn)?
圖片竟然并沒有顯示出來谐腰,但是也并沒有什么報錯,第二次調(diào)用BitmapFactory.decodeStream(inputStream,null,options) 即時設(shè)置??options.inJustDecodeBounds = false?
返回的值還是null涩盾;怎么回事呢十气?查了一下說是我們需要在兩次decode 之間將流重置? inputStream.reset() 一下?
好了加上這句在運(yùn)行一下看看結(jié)果:
java.io.IOException: mark/reset not supported at java.io.InputStream.reset
?春霍?砸西?啥意思,大概就是說不支持?InputStream.reset,查了下芹枷,網(wǎng)上有朋友說是因?yàn)?/p>
當(dāng)給定的流不支持mark和reset就會報這個錯誤,解決方案是用BufferedInputStream把原來的流包一層.什么時候會出現(xiàn)這種錯誤呢?獲取到一個網(wǎng)絡(luò)流,這個網(wǎng)絡(luò)流不允許讀寫頭來回移動,也就不允許mark/reset機(jī)制.
BufferedInputStream zipTest=new BufferedInputStream(zip);
說的很明白了衅疙,于是我們就回到上面的獲取到inputStream的代碼,按他說的包裝一下:
val bufferedInputStream = new?BufferedInputStream(inputStream);
然后在 BitmapFactory.decodeStream(bufferedInputStream,null,options) 中傳入?bufferedInputStream鸳慈;
val bitmap = BitmapFactory.decodeStream(inputStream,null,options)
這下總該可以了吧1ヒ纭!走芋!
結(jié)果還是報錯啦@砼蟆!绿聘!
Caused by: java.io.IOException: Resetting to invalid mark
? ? ? ? at java.io.BufferedInputStream.reset(BufferedInputStream.java:450)
黑人問號臉嗽上??熄攘?兽愤?這個又是咋回事? 重置無效的標(biāo)記挪圾?由于之間完全不了解這塊的東西于是又搜了下資料浅萧,發(fā)現(xiàn)有篇相關(guān)的博客下的評論是這樣的:
*?The?maximum?read?ahead?allowed?after?a?call?to?the??????*?<code>mark</code>?method?before?subsequent?calls?to?the??????*?<code>reset</code>?method?fail.??????*?Whenever?the?difference?between?<code>pos</code>?????*?and?<code>markpos</code>?exceeds?<code>marklimit</code>,?????*?then?the??mark?may?be?dropped?by?setting?????*?<code>markpos</code>?to?<code>-1</code>.
mark(0)時沒有意思的通俗的說如果你mark(1000)之后再讀取1001個自己(超過1000),你就無法reset了哲思,就是你上面的錯誤洼畅。很好理解:你想重新獲取最近的1000個字節(jié),可以你已經(jīng)讀取的自己超過了它棚赔,你當(dāng)然無法回到開始的位置了帝簇。你在這里設(shè)置mark(0),之后只要調(diào)用了read方法就不能reset了靠益!
咦丧肴?好像是在說這個reset()調(diào)用之間要用mark(Int)標(biāo)記一下讀取的字節(jié)size,讀取超過了reset()就回不去了胧后;
于是我又在包裝bufferedInputStream后面加了一句:
bufferedInputStream.mark(1024*1024) //考慮到我讀的是一個圖片流應(yīng)該不小把芋浮,字節(jié)給足!?强臁纸巷!
最后再跑一下,終于成功加載出來了圖片眶痰,而且也確實(shí)做到了動態(tài)計算采樣率瘤旨!
好了總算整明白怎么回事了!記錄一下凛驮。
然后查資料的過程中還順便了解到長圖分區(qū)域加載裆站,大概是這個類:BitmapRegionDecoder
具體看這篇大神的博客?http://www.reibang.com/p/f576fd7313da
它提供了一系列靜態(tài)方法構(gòu)造實(shí)例
拿到實(shí)例后 通過 #decodeRegion() 方法条辟,傳入一個 Rect 和 一個BitmapFactory.Options 參數(shù) 即可解碼出一張我們要的圖片解碼區(qū)域就是我們 Rect 指定的范圍黔夭,拿到 Bitmap 后當(dāng)然可以為所欲為了