Android 加载超大图片的最佳实践一、核心问题为什么大图会崩溃内存计算一张 4000×3000 像素的图片ARGB_8888 格式4000 × 3000 × 4 字节 48MB系统给 App 的堆内存通常只有 64MB~256MB一张图就占掉 1/4~3/4 的内存直接 OOMBitmapFactory.decodeFile(huge.jpg);// 直接加载原图 → 崩溃二、按场景选方案场景核心诉求推荐方案列表/网格展示如朋友圈、商品列表快速加载、内存低、流畅滑动Glide/Coil小尺寸缩略图查看高清大图如微信查看原图、相册高清、可缩放、可拖动subsampling-scale-image-view长图/全景图如微博长截图、地图局部加载、渐进显示BitmapRegionDecoder 自定义相机拍摄后预览快速、不卡顿采样压缩 硬件加速编辑/裁剪图片完整像素、操作实时按需加载 双缓冲超大规模图片卫星图、医学影像内存可控、专业渲染Native 层/OpenGL核心原则先看使用场景再选技术方案。三、方案 1BitmapFactory 采样压缩最基础publicBitmapdecodeSampledBitmapFromFile(Stringpath,intreqWidth,intreqHeight){// 第一步只读取尺寸不加载像素BitmapFactory.OptionsoptionsnewBitmapFactory.Options();options.inJustDecodeBoundstrue;BitmapFactory.decodeFile(path,options);intwidthoptions.outWidth;// 原图宽intheightoptions.outHeight;// 原图高// 第二步计算采样率intinSampleSizecalculateInSampleSize(width,height,reqWidth,reqHeight);// 第三步按采样率加载options.inJustDecodeBoundsfalse;options.inSampleSizeinSampleSize;options.inPreferredConfigBitmap.Config.RGB_565;// 减少内存每个像素 2 字节returnBitmapFactory.decodeFile(path,options);}privateintcalculateInSampleSize(intrawW,intrawH,intreqW,intreqH){intinSampleSize1;while(rawW/inSampleSizereqW||rawH/inSampleSizereqH){inSampleSize*2;// 采样率必须是 2 的幂}returninSampleSize;}原理inSampleSize 4表示每 4×4 16 个像素只采 1 个内存降为 1/16。四、方案 2BitmapRegionDecoder 局部解码看大图必备如果用户需要查看图片细节如地图、长微博截图不能全局缩小需要局部高清加载publicBitmapdecodeRegion(Stringpath,Rectrect,intsampleSize){try{InputStreamisnewFileInputStream(path);BitmapRegionDecoderdecoderBitmapRegionDecoder.newInstance(is,false);BitmapFactory.OptionsoptionsnewBitmapFactory.Options();options.inSampleSizesampleSize;// 可以进一步采样// 只解码 rect 区域内的像素Bitmapbitmapdecoder.decodeRegion(rect,options);decoder.recycle();returnbitmap;}catch(IOExceptione){e.printStackTrace();returnnull;}}// 使用只加载屏幕可见区域RectvisibleRectnewRect(0,0,1080,1920);// 屏幕大小BitmapregiondecodeRegion(huge.jpg,visibleRect,1);// 1:1 高清局部配合手势用户滑动时动态计算新的Rect只加载新可见区域。五、方案 3Glide / Coil 等图片库推荐实际项目中不需要手写成熟的图片库已经处理了这些// GlideJavaGlide.with(context).load(file:///sdcard/huge.jpg).override(800,600)// 目标尺寸.centerCrop()// 裁剪方式.into(imageView);// CoilKotlinimageView.load(file:///sdcard/huge.jpg) {size(800,600)scale(Scale.FILL)}内部原理Glide 自动做了inSampleSize计算、内存缓存、磁盘缓存、Bitmap 复用inBitmap。六、方案 4subsampling-scale-image-view看大图神器如果需求是高清查看大图类似微信查看原图、地图浏览用这个开源库// build.gradleimplementationcom.davemorrissey.labs:subsampling-scale-image-view:3.10.0!-- XML --com.davemorrissey.labs.subscaleview.SubsamplingScaleImageViewandroid:idid/imageViewandroid:layout_widthmatch_parentandroid:layout_heightmatch_parent/// JavaSubsamplingScaleImageViewimageViewfindViewById(R.id.imageView);imageView.setImage(ImageSource.asset(huge.jpg));// 自动支持双击缩放、手势拖动、局部高清解码原理内部使用BitmapRegionDecoder 瓦片化Tile渲染只加载当前可视区域的高清瓦片。七、方案 5Native 层处理超大规模图片如果图片大到几十 MB 甚至上百 MB如卫星图、医学影像Java 层内存不够需要 JNI// C 使用 libjpeg-turbo 或 OpenGL 纹理// 图片不加载到 Java 堆直接解码到 Native 内存或 GPU 纹理// Java 层只持有一个 Native 指针longnativePtrnativeLoadHugeImage(path);// 通过 JNI 调用 Native 方法获取局部像素int[]pixelsnativeDecodeRegion(nativePtr,x,y,w,h);BitmapbitmapBitmap.createBitmap(pixels,w,h,Config.ARGB_8888);八、综合对比方案适用场景内存占用实现复杂度BitmapFactory inSampleSize普通列表缩略图低低BitmapRegionDecoder大图查看器、地图中只加载局部中Glide / Coil常规图片加载低自动管理极低subsampling-scale-image-view高清大图查看低瓦片化极低直接用Native 层超大规模专业图像可控高九、场景细分详解场景 1列表缩略图用户诉求快速看到图片不关心细节 技术诉求内存低、滑动不卡、加载快 方案 - 图片尺寸100×100 ~ 300×300 - 采样率inSampleSize 4~8 - 缓存Glide 内存缓存 磁盘缓存 - 占位图低分辨率占位加载完成替换场景 2点击查看大图用户诉求看清细节可以放大缩小 技术诉求高清、流畅手势、不OOM 方案 - 初始显示屏幕尺寸的采样图 - 放大时切换到 BitmapRegionDecoder 局部高清 - 手势双击缩放、双指捏合、拖动 - 瓦片化只加载当前可视区域场景 3长图阅读如漫画、长微博用户诉求连续阅读渐进加载 技术诉求不一次性加载全部、流畅滚动 方案 - 分块加载把长图切成多个瓦片 - 预加载当前可见区域上下各预加载 1~2 屏 - 回收滑出屏幕的瓦片及时回收场景 4编辑图片如裁剪、滤镜用户诉求操作实时反馈 技术诉求完整像素、操作不卡顿 方案 - 编辑时加载完整分辨率或用户指定比例 - 预览时双缓冲后台处理前台显示 - 导出时重新编码质量可选十、最佳实践总结1. 列表/网格缩略图 → Glide/Coil设置合理的目标尺寸 2. 查看高清大图 → subsampling-scale-image-view 3. 需要自定义交互 → BitmapRegionDecoder 手势计算 4. 图片 100MB 或特殊格式 → Native 层处理 5. 永远不要 BitmapFactory.decodeXxx() 不加 Options 直接加载一句话大图加载的核心是按需加载——要么全局压缩要么局部解码永远不要一次性把整张大图塞进内存。另一句话先看使用场景再选技术方案。脱离场景谈技术那就是…。