OOM(Out of Memory),即“内存溢出”,是当应用程序向系统请求的内存超出了虚拟机(VM)或系统所能分配的最大限制时抛出的一个严重错误(Error)。在 Android 中,这意味着应用的堆内存(Heap)已经耗尽,无法再为新的对象分配空间。
OOM 会直接导致应用崩溃,是影响应用稳定性的主要因素之一。理解和解决 OOM 问题对于提升应用质量至关重要。
每个 Android 应用都有一个独立的、受限的堆内存上限。这个上限因设备而异,取决于设备的 RAM 大小和制造商的配置。你可以通过 ActivityManager.getMemoryClass() 获取当前设备为应用分配的大致堆内存限制(单位:MB)。
OOM 的根本原因可以归结为两类:内存泄漏(Memory Leaks) 和 内存抖动/不合理使用(Memory Churn / Inefficient Usage)。
内存泄漏是指程序中某些对象已经不再被使用,但由于存在其他正在使用的对象对它们的引用,导致垃圾回收器(GC)无法回收这些无用对象。随着时间的推移,泄漏的对象越积越多,最终耗尽堆内存。
常见的内存泄漏场景包括:
Activity 或其他短生命周期 Context 的引用,那么即使 Activity 已经销毁,也无法被回收。Activity)更长(例如在后台线程中执行),就会导致外部类无法被回收。Cursor、FileStream、Bitmap、BroadcastReceiver(未注销)等需要手动关闭或注销的资源,如果忘记调用 close() 或 unregisterReceiver(),会造成资源句柄和相关内存的泄漏。List, Map)中添加对象后,忘记在适当时机移除它们。这类问题并非由“泄漏”引起,而是由于在短时间内创建了大量对象,或加载了单个过大的对象,超出了内存上限。
图片宽度 * 图片高度 * 每个像素占用的字节数。例如,一张 4000x3000 的 ARGB_8888 图片会占用 4000 * 3000 * 4 = 48MB 内存,极易导致 OOM。SparseArray 替代 HashMap<Integer, Object>)可以显著减少内存占用。排查 OOM 需要借助专业的工具来分析内存使用情况和定位泄漏源。
在现代 Android Studio 版本中,App Inspection 是进行运行时诊断的首选工具。
View > Tool Windows > App Inspection)。.hprof 文件。.hprof 文件:
MyLeakActivity 的实例数在你离开该页面后应该为 0。如果大于 0,说明发生了泄漏。LeakCanary 是一个强大的、自动化的内存泄漏检测库。强烈建议在 Debug 版本中集成。
Activity、Fragment 等组件在销毁后是否被正确回收。如果发现泄漏,它会生成一份包含完整引用链的报告,并以通知的形式展现出来,让你能够轻松定位到泄漏代码。在 build.gradle.kts (或 build.gradle) 文件中添加依赖:
// build.gradle.kts
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
}
LeakCanary 会在应用启动时自动完成初始化,无需额外代码。
定位问题后,需要采取针对性的措施来修复和优化。
避免使用长生命周期的 Context:
Context 且其生命周期与应用无关时(如初始化单例、工具类),应使用 context.applicationContext。Activity。使用 WeakReference (弱引用):当需要在一个长生命周期对象中引用一个短生命周期对象(如在异步任务中引用 Activity)时,使用 WeakReference。弱引用不会阻止 GC 回收其引用的对象。
优化前(错误示例 - 内存泄漏)
class MyLeakActivity : AppCompatActivity() {
// 错误:非静态内部类会持有 MyLeakActivity 的隐式引用
private val mHandler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
// ... 处理消息 ...
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 发送一个延迟消息,当 Activity 关闭后,消息仍在队列中,
// Handler 实例继续存活,导致 Activity 实例也无法被回收。
mHandler.postDelayed({ /* do something */ }, 10000)
}
}
优化后(正确示例 - 使用静态内部类和弱引用)
class MySafeActivity : AppCompatActivity() {
// 1. 定义一个静态内部类 Handler,它不持有外部类的隐式引用
private class SafeHandler(activity: MySafeActivity) : Handler(Looper.getMainLooper()) {
// 2. 使用弱引用持有 Activity
private val activityReference: WeakReference<MySafeActivity> = WeakReference(activity)
override fun handleMessage(msg: Message) {
// 3. 在使用前,通过 get() 获取 Activity 实例并进行空检查
val activity = activityReference.get()
if (activity == null || activity.isFinishing) {
return // Activity 已被回收,安全退出
}
// 在这里安全地操作 Activity
activity.updateUi()
}
}
private val mHandler = SafeHandler(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mHandler.postDelayed({ /* do something */ }, 10000)
}
fun updateUi() { /* ... */ }
override fun onDestroy() {
super.onDestroy()
// 4. (最佳实践) 在 Activity 销毁时移除所有待处理的消息和回调,避免不必要的工作
mHandler.removeCallbacksAndMessages(null)
}
}
使用静态内部类或 Kotlin 的文件级函数:将需要长时间在后台运行的内部类改为静态内部类,这样它就不会持有外部类的隐式引用。如果需要与外部类交互,可以通过 WeakReference 传递其引用。
及时释放资源:
onDestroy 或适当的生命周期回调中,调用 unregisterReceiver()。try-with-resources (Java 7+) 或 Kotlin 的 use 函数来自动关闭 Cursor, InputStream 等 Closeable 资源。FileInputStream(file).use { stream ->
// 对 stream 进行操作,函数结束时会自动关闭
}
高效加载 Bitmap:
ImageView 的尺寸,加载一个缩小版的图片到内存。使用 BitmapFactory.Options 的 inSampleSize 属性。RGB_565 代替 ARGB_8888,内存占用能减少一半。使用优化的数据结构:
SparseArray<E> 替代 HashMap<Integer, E>。SparseBooleanArray 替代 HashMap<Integer, Boolean>。LongSparseArray<E> 替代 HashMap<Long, E>。key 进行自动装箱,减少了内存分配。谨慎使用 onDraw:避免在 onDraw 方法中创建新的对象(如 Paint, Path)。这些对象应该在初始化时创建并复用。
使用 largeHeap 属性:如果你的应用确实需要大量内存(如图片编辑、大型游戏),可以在 AndroidManifest.xml 的 <application> 标签中添加 android:largeHeap="true"。但这只是一个备用方案,治标不治本,首要任务还是优化内存使用。
OOM 是一个复杂但可控的问题。解决 OOM 的最佳实践可以总结为:
Context 和静态引用,及时释放资源。通过系统性的排查和持续的优化,你可以显著提升应用的稳定性,为用户提供更可靠的体验。