作者:opLW
目录
1.InputStream不可重复度
2.解决办法
3.最终方案
1.InputStream不可重复度
- 问题 最近在使用如下代码解析从网上读取的图片字节流时,遇到一个问题:SkImageDecoder::Factory returned null
fun decodeBitmapFromStream( bufferedIns: BufferedInputStream, reqWidth: Int, reqHeight: Int ): Bitmap? { val options = BitmapFactory.Options().also { it.inJustDecodeBounds = true } BitmapFactory.decodeStream(bufferedIns, null, options) with(options) { inSampleSize = calculateSampleSize(options, reqWidth, reqHeight) inJustDecodeBounds = false } return BitmapFactory.decodeStream(bufferedIns, null, options) }
- 原因 第一次取图片尺寸的时候
bufferedIns
这个BufferedInputStream
被使用过了,再真正取图片的时候又使用了这个BufferedInputStream
,此时流的起始位置已经被移动过了,所以导致的IOException。 - 为什么字节流不可以重复读呢? 可以查看这篇文章 InputStream为什么不能被重复读取?
2.解决办法
-
字节流就好比一根管道,只负责传输字节,如果数据被获取了,那么管道里就没有内容了。
-
那么有什么解决办法呢?
- 重复读取两次
- 顾名思义。我们可以先打开一次数据流,获取图片的尺寸信息,然后再打开重新打开一次真正的生成bitmap。
- 用ByteArrayOutputStream存着
// 先用ByteArrayOutputStream存着 val byteArrayOutputStream = ByteArrayOutputStream() val buffer = ByteArray(1024) var len: Int while (true) { len = inputStream.read(buffer) if ( len == -1 ) break byteArrayOutputStream.write(buffer, 0, len) } byteArrayOutputStream.flush() // 每次要使用时,进行转换 val ins = ByteArrayInputStream(byteArrayOutputStream.toByteArray())
- 总结 这两个方法各有缺陷。要么需要重新读,浪费时间,要么需要用数组装着,对于大文件来说(如图片)会浪费空间。 有没有更好的方法呢?
- 重复读取两次
-
使用InputStream的reset和mark方法
-
在 StackOverFlow 上找到了一个类似的问题。里面提到要将
InputStream
重置,需要使用到mark
和reset
这两个方法。- mark方法 InputStream的mark方法有一段注释如下
大概意思是说调用Marks the current position in this input stream. A subsequent call to the reset method repositions this stream at the last marked position so that subsequent reads re-read the same bytes.
mark
方法后,会记录一个标记。接下来第一次调用到reset
方法时,会从标记位开始读。 - reset方法 将流的的标记位置设为上一次调用mark时流所在的位置,从而可以重新读取流的数据。
- mark方法 InputStream的mark方法有一段注释如下
-
于是在第一次读取图片大小时,进行了
mark
操作,并在读完之后进行了reset
。..... bufferedIns.mark(0) BitmapFactory.decodeStream(bufferedIns, null, options) bufferedIns.reset() .....
可惜的是遇到了另外一个错误: java.io.IOException: Mark has been invalidated
-
原来是
mark
方法有一个参数readlimit
,这个参数决定了你在相邻的一组mark
和reset
之间可以读多少个字节。比如:
mark(20)
表明你在下次reset
之前还可以读取20个字节。如果读取超过了20个字节,这个mark
标记就会无效,此时再调用reset
就会报错。而且并不是所有的InputStream子类都支持通过这种方式重复读取数据,在调用
mark
方法之前,应该先调用markSupported()
,该方法会返回一个bool
表示是否支持重复读取。详细的解读可以看这篇文章输入流InputStream的reset()和mark()方法注意事项 -
那么这个
readlimit
参数应该设置为多少呢?- 第一次读取流时,只要保证能够读取到含有图片的尺寸信息的数据即可,这要求对各种格式的图片的编码有一定的了解。由于目前暂时不知道,所以此路暂时不通…?
- 如果设置的太小,会引起报错;如果设置的大一点,又不知道多大才合适。于是乎我们可以调用
InputStream.available()
方法,这个方法会返回一个预估值表示大概还有多少字节的数据可以读取。
-
3.最终方案
fun decodeBitmapFromStream(
bufferedIns: BufferedInputStream,
reqWidth: Int,
reqHeight: Int
): Bitmap? {
val options = BitmapFactory.Options().also {
it.inJustDecodeBounds = true
}
bufferedIns.mark(bufferedIns.available())
BitmapFactory.decodeStream(bufferedIns, null, options)
bufferedIns.reset()
with(options) {
inSampleSize = calculateSampleSize(options, reqWidth, reqHeight)
inJustDecodeBounds = false
}
return BitmapFactory.decodeStream(bufferedIns, null, options)
}
万水千山总是情,麻烦手下别留情。
如若讲得有不妥,文末留言告知我,
如若觉得还可以,收藏点赞要一起。
opLW原创七言律诗,转载请注明出处