一、什么是内存泄漏?
内存泄漏(Memory Leak) 是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
在 Android 中,更具体的定义是:长生命周期对象持有短生命周期对象的引用,导致短生命周期对象无法被垃圾回收器(GC)回收。
二、内存泄漏的危害
| 危害 | 说明 |
|---|---|
| OOM 崩溃 | 内存持续增长,最终触发 OutOfMemoryError,应用崩溃 |
| 卡顿/ANR | GC 频繁触发,主线程被阻塞,界面卡顿甚至 ANR |
| 电量消耗 | 后台泄漏导致进程无法被回收,持续占用 CPU 和内存 |
| 用户体验差 | 应用越来越卡,需要频繁重启 |
三、导致内存泄漏的常见情况
1. 静态变量持有 Activity/Context 引用 ⭐最常见
java
public class Singleton {
private static Singleton instance;
private Context context;
private Singleton(Context context) {
this.context = context; // ❌ 持有 Activity 引用
}
public static Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(context);
}
return instance;
}
}
原因:静态变量生命周期 = 应用生命周期,如果传入的是 Activity Context,Activity 销毁后仍被静态引用持有,无法回收。
解决 :使用 Application Context(context.getApplicationContext())。
2. 非静态内部类 / 匿名内部类持有外部类引用
java
public class MainActivity extends AppCompatActivity {
// ❌ 非静态内部类隐式持有外部 Activity 引用
private class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
// ...
}
}
// ❌ 匿名内部类同样持有 Activity 引用
private Runnable runnable = new Runnable() {
@Override
public void run() {
// 延迟执行,Activity 可能已销毁
}
};
}
原因:非静态内部类会隐式持有外部类的引用。如果内部类有耗时操作(如 Handler 延迟消息、AsyncTask、Thread),Activity 销毁时无法被回收。
解决:
-
使用 静态内部类 + 弱引用(WeakReference)
-
在
onDestroy()中移除消息/回调
java
private static class MyHandler extends Handler {
private WeakReference<MainActivity> weakRef;
MyHandler(MainActivity activity) {
weakRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = weakRef.get();
if (activity != null) {
// 安全使用
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null); // 移除所有消息
}
3. Handler 内存泄漏 ⭐高频考点
java
public class MainActivity extends AppCompatActivity {
private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 隐式持有 MainActivity 引用
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handler.sendEmptyMessageDelayed(0, 10000); // 10秒后执行
}
}
原因 :Message 会持有 Handler 引用,Handler 持有 Activity 引用,MessageQueue 持有 Message。如果 Activity 在消息执行前被销毁,整条引用链导致 Activity 泄漏。
解决 :静态内部类 + WeakReference + onDestroy() 移除消息。
4. 资源未关闭
java
// ❌ 未关闭的资源
Cursor cursor = getContentResolver().query(...);
// 忘记 cursor.close();
Bitmap bitmap = BitmapFactory.decodeResource(...);
// 忘记 bitmap.recycle(); // Android 3.0+ 后 Bitmap 内存由 GC 管理,但大 Bitmap 仍建议及时释放
InputStream is = ...;
// 忘记 is.close();
解决 :使用 try-finally 或在 Kotlin 中使用 use 自动关闭。
5. 监听器未注销
java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ❌ 注册后未在 onDestroy 中注销
getSystemService(LocationManager.class)
.requestLocationUpdates(..., listener);
}
}
常见场景:
-
LocationManager.requestLocationUpdates()未removeUpdates() -
SensorManager.registerListener()未unregisterListener() -
BroadcastReceiver动态注册后未unregisterReceiver() -
EventBus/RxJava订阅后未取消订阅
解决 :在 onDestroy() / onStop() 中及时注销。
6. 单例模式持有回调/监听器
java
public class NetworkManager {
private static NetworkManager instance;
private NetworkCallback callback;
public void setCallback(NetworkCallback callback) {
this.callback = callback; // ❌ 可能持有 Activity 引用
}
}
解决:使用弱引用,或在 Activity 销毁时手动置空。
7. WebView 内存泄漏
java
// ❌ 直接在 xml 中声明 WebView,Activity 销毁时 WebView 持有 Activity 引用
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
原因:WebView 内部逻辑复杂,会持有 Activity 引用,且不易释放。
解决:
-
使用独立的进程加载 WebView(
android:process=":web") -
动态添加/移除 WebView,销毁时从父布局移除并调用
destroy() -
使用腾讯 X5 内核等第三方方案
java
@Override
protected void onDestroy() {
if (webView != null) {
webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
webView.clearHistory();
((ViewGroup) webView.getParent()).removeView(webView);
webView.destroy();
webView = null;
}
super.onDestroy();
}
8. 集合类泄漏
java
public class CacheManager {
// ❌ 静态集合一直增长,从未清理
private static List<Object> cache = new ArrayList<>();
public void add(Object obj) {
cache.add(obj);
}
// 没有 remove() 或 clear() 机制
}
解决 :使用 LruCache 等有限容量的缓存,或设置过期清理机制。
9. 属性动画(Animator)未取消
java
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 0, 1);
animator.setDuration(3000);
animator.start();
// ❌ Activity 销毁时未取消动画
原因:动画会持有 target View 的引用,View 持有 Activity 引用。
解决 :在 onDestroy() 中 animator.cancel()。
10. 系统服务持有 Context
java
// ❌ 错误
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
某些系统服务(如 InputMethodManager)内部存在已知的内存泄漏 Bug(Android 底层问题)。
解决:使用 Application Context 获取系统服务。
11. 第三方库使用不当
-
Glide:传入 Activity Context 时,Glide 会绑定生命周期;但如果传入 Application Context 且未手动管理,可能泄漏
-
RxJava :订阅后未在
onDestroy()中dispose() -
EventBus :注册后未
unregister() -
LeakCanary:虽然它是检测工具,但本身也可能有轻微影响
四、内存泄漏检测工具
表格
| 工具 | 用途 |
|---|---|
| LeakCanary | Square 出品,自动检测并展示泄漏引用链,开发必备 |
| Android Studio Profiler | 实时查看内存分配,Heap Dump 分析 |
| MAT (Memory Analyzer Tool) | 分析 hprof 文件,查找支配树和引用链 |
| adb shell dumpsys meminfo | 查看应用内存占用情况 |
五、预防内存泄漏的黄金法则
-
能用 Application Context 就不用 Activity Context
-
静态变量不持有短生命周期对象
-
内部类尽量声明为 static,配合 WeakReference
-
有注册就有注销,有开启就有关闭
-
耗时操作/延迟任务要考虑生命周期
-
使用 Jetpack 组件(ViewModel、Lifecycle、LiveData)自动管理生命周期
-
接入 LeakCanary 在开发阶段及时发现
六、总结图
plain
长生命周期对象 ──持有引用──► 短生命周期对象
↑ │
└───────────────────────────┘
无法被 GC 回收
= 内存泄漏