在学习图片加载时图片很大的时候直接加载到内存或则直接原图绘制到 ImageView中会导致OOM问题;
解决思路是利用BitmapFactory的系列方法中有个带有 BitmapFactory.Options 参数的方法,这个参数可以通过设置
options.inSampleSize = Int ; //设置图片采样率
这个图片采样率的意思就是几个像素点取一个,例如原图的像素为 1000 * 1000 设置采样率为2
那么就是在横向和纵向上每2个像素点采样一次 最终得到一个 (1000/2)*(1000/2)的bitmap图片加载到内存中,从而达到占用内存变小的目的;
但是我们获取的图片的大小是不确定的,我们的控件尺寸在不同分辨率的设备上大小也不一样;所以我们不能给一个固定的采样率,需要根据获取的图片大小和控件大小来决定采样率;
计算采样率:
options.inJustDecodeBounds =true
options中该属性设置为true时,调用BitmapFactory的带options的系列方法,bitmap不会载入内存,方法返回null。但是方法会把bitmap的一些信息写入options,这样就可以在不消耗大量内存的情况下得到图片资源的尺寸信息;
这里以 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 参数和控件尺寸计算横向和纵向的最合适采样率
val x:Float = (options.outWidth/viewWidth).toFloat()
val y:Float = (options.outHeight/viewHeight).toFloat()
//下面这个是计算采样率的策略,可以有不同的策略;
//如果控件的宽高至少有一个是小于图片的宽高的时候才进行采样率计算
if ( viewHeight<options.outHeight || viewWidth<options.outWidth ) {
// 取较大的那个作为采样率,也有的策略取小的那个
val s = if (x<y) y else x
options.inSampleSize = s.toInt(); //设置图片采样率
Log.e("qqq",""+ s +">>>>>>>" + s.toInt())
}
设置好采样率后,重新把 options.inJustDecodeBounds = false 然后就可以采用上面的采样率加载图片到控件中了
val bitmap = BitmapFactory.decodeStream(inputStream,null,options)
imageView.setImageResource(bitmap)
这时候我以为大功告成了,结果发现
图片竟然并没有显示出来,但是也并没有什么报错,第二次调用BitmapFactory.decodeStream(inputStream,null,options) 即时设置 options.inJustDecodeBounds = false
返回的值还是null;怎么回事呢?查了一下说是我们需要在两次decode 之间将流重置 inputStream.reset() 一下
好了加上这句在运行一下看看结果:
java.io.IOException: mark/reset not supported at java.io.InputStream.reset
???啥意思,大概就是说不支持 InputStream.reset,查了下,网上有朋友说是因为
当给定的流不支持mark和reset就会报这个错误,解决方案是用BufferedInputStream把原来的流包一层.什么时候会出现这种错误呢?获取到一个网络流,这个网络流不允许读写头来回移动,也就不允许mark/reset机制.
BufferedInputStream zipTest=new BufferedInputStream(zip);
说的很明白了,于是我们就回到上面的获取到inputStream的代码,按他说的包装一下:
val bufferedInputStream = new BufferedInputStream(inputStream);
然后在 BitmapFactory.decodeStream(bufferedInputStream,null,options) 中传入 bufferedInputStream;
val bitmap = BitmapFactory.decodeStream(inputStream,null,options)
这下总该可以了吧!!!
结果还是报错啦!!!
Caused by: java.io.IOException: Resetting to invalid mark
at java.io.BufferedInputStream.reset(BufferedInputStream.java:450)
黑人问号脸????这个又是咋回事? 重置无效的标记?由于之间完全不了解这块的东西于是又搜了下资料,发现有篇相关的博客下的评论是这样的:
* 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个字节,可以你已经读取的自己超过了它,你当然无法回到开始的位置了。你在这里设置mark(0),之后只要调用了read方法就不能reset了!
咦?好像是在说这个reset()调用之间要用mark(Int)标记一下读取的字节size,读取超过了reset()就回不去了;
于是我又在包装bufferedInputStream后面加了一句:
bufferedInputStream.mark(1024*1024) //考虑到我读的是一个图片流应该不小把,字节给足!!!
最后再跑一下,终于成功加载出来了图片,而且也确实做到了动态计算采样率!
好了总算整明白怎么回事了!记录一下。
然后查资料的过程中还顺便了解到长图分区域加载,大概是这个类:BitmapRegionDecoder
具体看这篇大神的博客 https://www.jianshu.com/p/f576fd7313da
它提供了一系列静态方法构造实例
拿到实例后 通过 #decodeRegion() 方法,传入一个 Rect 和 一个BitmapFactory.Options 参数 即可解码出一张我们要的图片解码区域就是我们 Rect 指定的范围,拿到 Bitmap 后当然可以为所欲为了