前段时间在做相机 App 的 RAW 导出功能,测试机上一直正常,换到内存小一些的设备就直接闪退。日志很典型:
java.lang.OutOfMemoryError: Failed to allocate a 183009612 byte allocation
183MB —— 一张未压缩的位图。问题不在于「分配了 183MB」,而在于同时存在好几份。
这是最容易被忽略的一条。OutOfMemoryError 继承自 Error,不是 Exception:
try {
renderRawToBitmap()
} catch (e: Exception) { // 抓不到 OutOfMemoryError!
showError()
}
所以崩溃直接穿透到顶层。要兜住得显式写:
try {
renderRawToBitmap()
} catch (e: OutOfMemoryError) {
showError("内存不足,请关闭其他应用后重试")
}
峰值内存往往不是「一张图」的大小,而是几份中间结果叠加。养成用完立刻回收的习惯:
val bitmap = decodeSampled(path)
try {
process(bitmap)
} finally {
bitmap.recycle()
}
用 ByteBuffer.allocateDirect() 分配的内存走的是本地内存,堆内存压力看不出来,但它真的占着物理内存,回收还依赖 Cleaner,时机不确定。
处理大块像素数据时,改用堆内缓冲反而更可控:
// 之前:183MB 躺在堆外,GC 看不见
val buf = ByteBuffer.allocateDirect(size)
// 之后:走堆内,受 GC 管辖,压力可见也可控
val buf = ByteBuffer.allocate(size)
readBytes() 会把整个文件读进内存。大文件改成流式处理:
FileInputStream(file).use { input ->
val out = FileOutputStream(target)
out.use { input.copyTo(it) }
}
排查 OOM 时,与其盯着「哪一次分配爆了」,不如算一下同一时刻最多有几份大对象共存。把中间结果的释放时机理顺,峰值往往能降一半。
这次改完,实测峰值从 1007MB 降到了 549MB,小内存设备不再闪退。
版权声明
本站所有文章除特别注明外,均由 Cokee 创作,采用 CC BY-NC-SA 4.0 许可协议授权,请您在转载时注明文章来源为 Cokee 的小站。