从URL获取大图转为Bitmap时返回null该如何解决
问题原因
- 大尺寸图片的像素总量过高,解码时需要的内存超过了Android应用的单进程内存阈值,
BitmapFactory.decodeStream会直接返回null且不抛出异常 - 原生网络InputStream是不可重试的流,若解码过程中发生网络波动导致流读取中断,也会出现解码失败返回null的情况
- 部分高版本Android系统对未做采样的大图解码做了直接拦截,避免OOM导致应用崩溃
解决方案
核心优化思路是先做流缓冲解决网络读取不稳定问题,再通过采样率压缩按需降低图片分辨率,大幅减少解码需要的内存占用。
优化后的完整代码如下:
public Bitmap getBitmapFromURL(String imageUrl, int targetWidth, int targetHeight) { HttpURLConnection connection = null; InputStream inputStream = null; BufferedInputStream bufferedInputStream = null; try { URL url = new URL(imageUrl); connection = (HttpURLConnection) url.openConnection(); connection.setDoInput(true); connection.connect(); inputStream = connection.getInputStream(); bufferedInputStream = new BufferedInputStream(inputStream); // 标记流位置,方便读取完图片参数后重置流重新解码 bufferedInputStream.mark(bufferedInputStream.available()); // 第一步:仅读取图片宽高参数,不加载全量像素到内存 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(bufferedInputStream, null, options); bufferedInputStream.reset(); // 第二步:计算匹配目标显示尺寸的采样率 options.inSampleSize = calculateInSampleSize(options, targetWidth, targetHeight); options.inJustDecodeBounds = false; // 不需要透明度的场景下使用RGB_565,比默认ARGB_8888省一半内存 options.inPreferredConfig = Bitmap.Config.RGB_565; options.inTempStorage = new byte[16 * 1024]; return BitmapFactory.decodeStream(bufferedInputStream, null, options); } catch (IOException e) { e.printStackTrace(); return null; } finally { // 手动回收流和连接资源,避免内存泄漏 try { if (bufferedInputStream != null) bufferedInputStream.close(); if (inputStream != null) inputStream.close(); if (connection != null) connection.disconnect(); } catch (IOException e) { e.printStackTrace(); } } } // 采样率计算方法,返回2的整数次幂作为采样率 private int calculateInSampleSize(BitmapFactory.Options options, int targetWidth, int targetHeight) { final int imgHeight = options.outHeight; final int imgWidth = options.outWidth; int inSampleSize = 1; if (imgHeight > targetHeight || imgWidth > targetWidth) { final int halfHeight = imgHeight / 2; final int halfWidth = imgWidth / 2; // 取最大的采样率,保证采样后的宽高都大于等于目标显示尺寸 while ((halfHeight / inSampleSize) >= targetHeight && (halfWidth / inSampleSize) >= targetWidth) { inSampleSize *= 2; } } return inSampleSize; }
使用说明
- 调用方法时传入图片展示控件的像素宽高作为
targetWidth和targetHeight即可,无需用原图尺寸作为参数 - 该方法必须在子线程中调用,Android系统禁止主线程执行网络请求操作
- 如果图片需要支持透明通道,删掉
options.inPreferredConfig = Bitmap.Config.RGB_565配置即可
额外优化建议
- 如果加载的是超长长图、全景图这类超大尺寸图片,建议使用
BitmapRegionDecoder按需加载局部显示区域,无需将整张图片加载到内存 - 不再使用的Bitmap及时调用
recycle()回收内存,避免内存堆积
内容的提问来源于stack exchange,提问作者uyarc
相关产品推荐
相关产品推荐

