Android 8下SkAndroidCodec::NewFromStream返回null问题求助
问题分析与解决方案
你在Android 8上通过Uri解码Bitmap时遇到的stream.reset()抛出IOException,以及SkAndroidCodec::NewFromStream returned null的问题,根源主要集中在流操作的可靠性上,我给你拆解下原因和对应的解决办法:
问题根源
- 部分输入流不支持mark/reset:有些ContentProvider返回的流(比如特定Uri对应的文件流)本身不支持标记和重置操作,就算用
BufferedInputStream包装,底层流的限制还是会导致reset()失败。 - mark的缓冲区长度不足:你调用
stream.mark(stream.available())时,available()只返回当前可读取的字节数,而非文件总长度。当后续decodeStream读取的字节超过这个标记长度时,mark标记会失效,reset()自然报错。 - decodeStream消耗流后无法重置:
BitmapFactory.decodeStream会把流读到末尾,哪怕之前做了mark,一旦缓冲区不够,也没法重置到初始位置,第四次reset()失败时流已经是空的,SkAndroidCodec自然无法创建解码器。
方案一:将流转为字节数组(适合中小图片)
把Uri对应的流读取到内存字节数组中,用ByteArrayInputStream操作,它天然支持多次重置,完全规避mark/reset的问题:
public Bitmap decodeSampledBitmapFromUri(Uri fileUri, int reqWidth, int reqHeight) { byte[] imageBytes = null; InputStream stream = null; try { stream = mApplicationContext.getContentResolver().openInputStream(fileUri); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = stream.read(buffer)) != -1) { baos.write(buffer, 0, len); } imageBytes = baos.toByteArray(); } catch (IOException e) { Log.e("TAG", "读取流失败: " + e.getMessage()); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { Log.e("TAG", "关闭流失败: " + e.getMessage()); } } } if (imageBytes == null) { Log.e("TAG", "图片字节数组为空"); return null; } BitmapFactory.Options options = new BitmapFactory.Options(); // 先获取图片尺寸 options.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, options); // 计算采样率 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; // 解码最终Bitmap return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, options); }
方案二:每次解码前重新打开流(适合大图片,避免OOM)
既然mark/reset不可靠,那就每次需要读取流的时候,重新调用getContentResolver().openInputStream(fileUri)获取新流,彻底摆脱对重置操作的依赖:
public Bitmap decodeSampledBitmapFromUri(Uri fileUri, int reqWidth, int reqHeight) { BitmapFactory.Options options = new BitmapFactory.Options(); InputStream stream = null; // 第一步:获取图片尺寸 options.inJustDecodeBounds = true; try { stream = mApplicationContext.getContentResolver().openInputStream(fileUri); BitmapFactory.decodeStream(stream, null, options); } catch (IOException e) { Log.e("TAG", "获取图片尺寸失败: " + e.getMessage()); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { Log.e("TAG", "关闭流失败: " + e.getMessage()); } } } // 计算采样率 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; // 第二步:解码最终Bitmap Bitmap bitmap = null; try { stream = mApplicationContext.getContentResolver().openInputStream(fileUri); bitmap = BitmapFactory.decodeStream(stream, null, options); } catch (IOException e) { Log.e("TAG", "解码Bitmap失败: " + e.getMessage()); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { Log.e("TAG", "关闭流失败: " + e.getMessage()); } } } return bitmap; }
额外注意事项
- Android 8适配:API 26对ContentProvider流处理有细节调整,部分MediaStore Uri返回的流确实不支持mark/reset,所以优先用上面两种方案更稳妥。
- 资源泄漏防范:所有流操作一定要在
finally块中关闭,避免占用系统资源。 - 内存优化:如果处理超大图片,优先选方案二,它不会把整个图片加载到内存,能有效避免OOM。
内容的提问来源于stack exchange,提问作者czane
相关产品推荐
相关产品推荐

