
图解Picasso位图解码内幕inSampleSize采样、inBitmap复用与OutOfMemoryError防御机制【免费下载链接】picassoA powerful image downloading and caching library for Android项目地址: https://gitcode.com/gh_mirrors/pic/picassoPicasso 是 Android 上经典的图片加载库但很少有人系统了解它的位图解码内幕inSampleSize采样如何防止大图撑爆内存、为什么 Picasso 3 没有使用inBitmap复用、以及OutOfMemoryErrorOOM发生时它如何兜底防御而不让 App 闪退。本文带你从源码出发一次看懂这套解码与防 OOM 机制。一张图看懂解码发生在哪先看官方示例 Apppicasso-sample的运行效果上面这个 2×3 的图片网格中每张 Picasso 画作都来自 PicassoSampleAdapter.kt 的加载请求。一次请求要经历取数据 → 位图解码 → 变换 → 写入内存缓存 → 上屏。其中真正吃内存的是解码这一步。项目名Picasso正是向画家巴勃罗·毕加索致敬——Paparazzi 截图测试的基准图就是他的立体主义名作解码入口BitmapHunter 的 hunt() 流程所有请求的解码都集中在 BitmapHunter.kt 中完成BitmapHunter.kt#L101-L111hunt()先查内存缓存命中直接返回跳过解码——这是最便宜的一条路未命中才走requestHandler.load(...)最终由 BitmapUtils.kt 的decodeStream完成真正的位图解码网络请求的入口在 NetworkRequestHandler.kt#L70拿到响应体后调用decodeStream(body.source(), request)。解码完成后BaseDispatcher.kt#L294-L303 的performComplete会把位图写回内存缓存供后续相同请求直接复用inSampleSize 采样先量尺寸再降采样为什么需要采样一张 4000×3000 的 JPEG全尺寸解码约需4000 × 3000 × 4 ≈ 45MB堆内存。而它最终可能只显示在一个 200×200 的ImageView里——白白浪费 90% 以上的内存这正是 OOM 的常见诱因。inSampleSize的原理值为 4 时解码器每 4×4 个像素只取 1 个输出尺寸降为原来的 1/4内存占用降到约 1/16。Picasso 的两阶段解码API 28 以下BitmapUtils.kt#L43-L55 中createBitmapOptions会为带尺寸约束resize()的请求创建一个BitmapFactory.Options并把inJustDecodeBounds置为 true。随后 decodeStreamPreP 执行经典的两阶段解码// 第1阶段只解析尺寸不分配位图内存 BitmapFactory.decodeStream(bufferedSource.peek().inputStream(), null, options) // 第2阶段算好 inSampleSize 后正式解码 calculateInSampleSize(request.targetWidth, request.targetHeight, options!!, request) BitmapFactory.decodeStream(bufferedSource.inputStream(), null, options)采样倍率由 BitmapUtils.kt#L197-L221 的ratio函数计算其中有个容易忽略的细节val heightRatio height / requestHeight val widthRatio width / requestWidth if (request.centerInside) { max(heightRatio, widthRatio) // centerInside取较大者保证整图可见 } else { min(heightRatio, widthRatio) // centerCrop取较小者保证铺满 }centerCrop和centerInside两种缩放策略对应不同的采样选择这决定了采样后的位图是否真的够用。另外 decodeStreamPreP#L127-L146 对 WebP 网络流先读成字节数组再解码是为了绕开 WebP 流解码时的一个 JNI 崩溃。API 28交给 ImageDecoder 一步到位在 Android P 及以上BitmapUtils.kt#L180-L195 改用新的ImageDecoder不再需要手动两阶段解码ImageDecoder.decodeBitmap(imageSource) { imageDecoder, imageInfo, source - imageDecoder.isMutableRequired true if (request.hasSize()) { // 按目标尺寸直接解码内部完成等效的采样 val ratio ratio(targetWidth, targetHeight, width, height, request) imageDecoder.setTargetSize(width / ratio, height / ratio) } }setTargetSize让系统在解码时直接输出目标尺寸比先采样成 2 的幂次再缩略更精准也不会产生中间大图。对比项API 28BitmapFactoryAPI 28ImageDecoder解码方式两阶段先取 bounds 再采样单阶段setTargetSize采样粒度只能是 1/2/4/8… 2 的幂次任意目标尺寸内存峰值低解码即为目标尺寸低且由框架管理inBitmap 复用Picasso 为什么弃用了它inBitmap是 Android 提供的另一项内存优化让BitmapFactory复用一个旧位图的像素缓冲区来解码新位图省去新分配 旧回收的开销能显著降低 GC 压力。但在 Picasso 3 中没有inBitmap的使用。原因在于inBitmap要求复用池管理、尺寸/配置匹配且复用缓冲区后旧位图立即失效与 Picasso 的 LRU 缓存缓存里存的就是 Bitmap 本体直接冲突——你没法既把 Bitmap 缓存在PlatformLruCache里又把它当解码缓冲区用掉API 28 的ImageDecoder由系统内部管理复用缓冲BitmapFactory的inBitmap也在新 API 上被标记为过时Picasso 选择更简单的防线组合LRU 缓存复用 采样降尺寸 失败兜底避免引入复用池的复杂度。OutOfMemoryError 防御机制四层防线即便做了采样超大图、异常并发仍可能触发 OOM。Picasso 的防御是分层的第 1 层内存缓存命中即跳过解码。hunt()开头BitmapHunter.kt#L101-L111先查缓存配合 MemoryPolicy.kt 的NO_CACHE/NO_STORE策略让调用方能控制只读不写例如一次性请求避免挤占缓存。第 2 层限制缓存规模。Utils.kt#L150-L156 中内存缓存大小被限制为应用可用堆的约 15%// Target ~15% of the available heap. return (1024L * 1024L * memoryClass / 7).toInt()PlatformLruCache.kt 在此基础上做 LRU 淘汰缓存满了就按最久未使用逐出旧位图保证总占用不失控。第 3 层采样从源头控制单次解码的内存峰值。如前所述inSampleSize/setTargetSize确保解码产物与显示尺寸匹配而不是原图尺寸。第 4 层错误不崩溃降级为失败回调。这是最关键的一层。BitmapHunter.kt#L78-L92 的run()中hunt()抛出的**任何异常包括OutOfMemoryError**都会被捕获} catch (e: Exception) { exception e dispatcher.dispatchFailed(this) }Error类型OOM 属于Error在 BitmapHunter.kt#L145-L149 中被显式重新抛出再由dispatchFailed转入 BaseDispatcher.kt#L308-L311 的performError——它只把失败交付给 Action即展示错误图、回调onError异常被拦截在工作线程内不会穿透到主线程导致崩溃。这就是捕获 OOM → 派发失败 → 记录统计日志的完整闭环见 CHANGELOG.md#L125 的说明配合 StatsEventListener.kt 还能在 logcat 里看到每次解码的内存统计方便定位问题图片。实践建议清单一定调用resize()并传上目标尺寸否则inJustDecodeBounds路径不会启用大图全尺寸解码Request.kt#L195 中hasSize()决定了是否走采样一次性大图解码如get()后处理可考虑NO_STORE避免污染 LRU 缓存用 StatsEventListener.kt 在调试期观察每张图的解码内存找到内存大户解码失败/OOM 时 Picasso 只会回调onErrorUI 层应始终配置error()占位图兜底。小结Picasso 的位图解码体系可以概括为内存缓存优先 → 两阶段inSampleSize采样新系统由ImageDecoder.setTargetSize替代→ 15% 堆上限的 LRU 缓存兜底 → OOM 被捕获并降级为普通失败回调。它没有采用inBitmap复用而是用这套更简单、可预测的防线组合在少用内存和永不因解码崩溃之间取得了平衡。理解了 BitmapHunter.kt 与 BitmapUtils.kt 这两个文件你就掌握了 Picasso 图片加载的核心。【免费下载链接】picassoA powerful image downloading and caching library for Android项目地址: https://gitcode.com/gh_mirrors/pic/picasso创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考