🪄 一、前言
随着 Android 应用功能日益复杂,内存问题几乎无处不在。
一个轻微的内存泄漏可能暂时不会崩溃,但会悄悄积累,最终导致:
- App 启动变慢
- 页面切换卡顿
- 内存持续上涨
- 最终崩溃:
OutOfMemoryError
本文将系统讲解:
- 什么是内存泄漏与溢出
- 常见泄漏场景与原理
- 如何定位与修复
- 实战优化案例
🧩 二、基础概念区分
✅ 1. 内存泄漏(Memory Leak)
对象 不再被需要,但依然被某处引用,导致 GC 无法回收。
举例:
class LeakActivity : AppCompatActivity() {
companion object {
var instance: LeakActivity? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
instance = this // ❌ 静态引用 Activity
}
}
🔹 问题分析:
- Activity 销毁后仍被静态变量持有
- 无法被 GC 回收
- 多次进入页面 → 内存持续上涨 → 崩溃
❌ 2. 内存溢出(Out Of Memory, OOM)
程序占用的内存超出系统分配上限。
常见错误:
java.lang.OutOfMemoryError: Failed to allocate a 1234567 byte allocation with 512 free bytes
触发条件:
- 图片、视频、Bitmap 占用过大
- 循环引用 + 泄漏叠加
- 加载过多资源或缓存未释放
⚙️ 三、Android 内存管理机制简述
-
GC(垃圾回收)机制
- Android 使用 引用计数 + 可达性分析(Reachability Analysis)
- 当对象不可达时,GC 自动释放其内存
- 常见算法:标记-清除(Mark-Sweep)、复制算法(Copying)
-
内存分配限制
- 每个 App 都有最大内存上限(如 256MB~512MB)
- 可通过
Runtime.getRuntime().maxMemory()查看
-
内存回收的盲区
- GC 只能回收无引用对象
- 若错误地长期持有引用 → 无法自动释放
🧠 四、常见内存泄漏场景与修复
1️⃣ 静态变量持有 Context / Activity
object Singleton {
var context: Context? = null
}
➡️ 原因:静态对象生命周期与应用一致,持有 Activity 会阻止回收。
✅ 正确写法:
object Singleton {
private lateinit var appContext: Context
fun init(context: Context) {
appContext = context.applicationContext // ✅ 使用 ApplicationContext
}
}
2️⃣ 非静态内部类的 Handler / Runnable
class LeakActivity : AppCompatActivity() {
private val handler = Handler(Looper.getMainLooper()) {
// ...
true
}
}
➡️ 原因:Handler 是内部类,默认持有外部类引用。
若消息队列中仍有未处理的 Message,则 Activity 无法释放。
✅ 修复方案:
class LeakActivity : AppCompatActivity() {
private val handler = SafeHandler(this)
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
}
private class SafeHandler(activity: LeakActivity) : Handler(Looper.getMainLooper()) {
private val ref = WeakReference(activity)
override fun handleMessage(msg: Message) {
ref.get()?.let {
// 安全访问 Activity
}
}
}
}
✅ 使用静态内部类 + WeakReference 可避免引用 Activity。
3️⃣ 匿名类、Lambda 闭包引用外部对象
button.setOnClickListener {
textView.text = "Clicked" // ❌ 引用了外部 View
}
➡️ 原因:匿名内部类持有外部类引用。
若闭包未释放(如长生命周期任务),会导致泄漏。
✅ 解决:
- 避免在延时任务、全局回调中使用 Activity 引用
- 使用
WeakReference或回调接口解耦
4️⃣ 单例 / 静态持有 View、Bitmap
object ImageCache {
val cacheMap = mutableMapOf<String, Bitmap>()
}
➡️ 原因:Bitmap 未释放会导致 native 层泄漏。
✅ 修复:
fun clear() {
for (bmp in cacheMap.values) {
bmp.recycle()
}
cacheMap.clear()
}
或使用
LruCache自动管理缓存回收。
5️⃣ 未关闭的资源
val cursor = db.query("table", null, null, null, null, null, null)
// ❌ 未关闭
✅ 修复:
cursor.use {
// 自动关闭
}
同理:
FileInputStreamCursorBroadcastReceiverSensorManager/LocationManager
⚠️ 所有“注册类资源”都必须在
onDestroy()解绑。
🔧 五、定位工具与分析方法
🔹 1. LeakCanary(推荐)
Square 出品的内存泄漏检测神器。
集成步骤:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
}
使用方式:
运行 App → 退出页面 → LeakCanary 自动检测泄漏
→ 通知栏出现泄漏报告。
报告内容包括:
- 哪个对象未释放
- 被哪个引用链持有
- 泄漏来源类与字段
示例报告:
ActivityLeakActivity has leaked:
┬───
│ GC Root: System class
│ LeakActivity instance
│ ↳ handler (android.os.Handler)
│ ↳ callback (android.os.Message)
🔹 2. Android Studio Profiler
路径:
View → Tool Windows → Profiler → Memory
功能:
- 实时内存使用图
- Heap Dump 分析
- Object Reference 跟踪
- GC 事件记录
🔹 3. MAT (Memory Analyzer Tool)
Eclipse 提供的专业堆分析工具。
适合分析 .hprof 文件中的复杂泄漏链。
💥 六、内存溢出案例分析
场景:加载超大图片导致 OOM
val bitmap = BitmapFactory.decodeFile(path)
imageView.setImageBitmap(bitmap)
假设原图 4000×4000,ARGB_8888 占用约 64MB → 直接崩溃。
✅ 解决方案:
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(path, options)
options.inSampleSize = calculateInSampleSize(options, 800, 800)
options.inJustDecodeBounds = false
val bitmap = BitmapFactory.decodeFile(path, options)
核心思想:按需压缩加载(采样率缩放)。
🧰 七、内存优化实战技巧总结
| 类别 | 优化策略 |
|---|---|
| Context 使用 | 使用 ApplicationContext,避免 Activity 泄漏 |
| Handler/线程 | 使用静态内部类 + WeakReference |
| 图片资源 | 使用 Glide/Picasso 自动回收管理 |
| 缓存 | 使用 LruCache 替代静态 Map |
| 生命周期绑定 | 在 onDestroy/onStop 中清理资源 |
| Leak 检测 | 集成 LeakCanary,养成调试习惯 |
| 异步任务 | 避免长时间持有 Activity 引用 |
🧠 八、总结
| 项目 | 内容 |
|---|---|
| 内存泄漏 | 不再需要的对象仍被引用 |
| 内存溢出 | 占用内存超过系统限制 |
| 检测工具 | LeakCanary / Profiler / MAT |
| 常见场景 | Handler、静态引用、闭包、未关闭资源 |
| 解决策略 | 弱引用、解绑资源、合理使用 Context |
| 实战重点 | 预防 > 修复;监控 > 临时清理 |
📦 九、延伸阅读