2026-01-12 13:30:25 +08:00
|
|
|
|
# 内存优化(LeakCanary原理)
|
2026-01-12 17:14:58 +08:00
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
|
|
|
|
|
内存泄漏是Android开发中的常见问题,会导致应用内存占用持续增长,最终可能引发OOM(Out of Memory)崩溃。LeakCanary是Square公司开源的内存泄漏检测工具,本文档深入解析其工作原理和使用方法。
|
|
|
|
|
|
|
|
|
|
|
|
## 内存泄漏基础
|
|
|
|
|
|
|
|
|
|
|
|
### 什么是内存泄漏
|
|
|
|
|
|
|
|
|
|
|
|
内存泄漏是指程序在运行过程中,由于某些原因导致已分配的内存无法被回收,造成内存浪费和潜在的性能问题。
|
|
|
|
|
|
|
|
|
|
|
|
### Android中的内存泄漏场景
|
|
|
|
|
|
|
|
|
|
|
|
1. **静态变量持有Context**
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 错误示例
|
|
|
|
|
|
public class MyApplication {
|
|
|
|
|
|
private static Context sContext; // 持有Activity引用
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 正确做法
|
|
|
|
|
|
public class MyApplication {
|
|
|
|
|
|
private static Context sAppContext; // 使用Application Context
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
2. **非静态内部类持有外部类引用**
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 错误示例
|
|
|
|
|
|
public class MainActivity extends Activity {
|
|
|
|
|
|
private Handler mHandler = new Handler() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void handleMessage(Message msg) {
|
|
|
|
|
|
// 隐式持有MainActivity引用
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 正确做法:使用静态内部类 + WeakReference
|
|
|
|
|
|
public class MainActivity extends Activity {
|
|
|
|
|
|
private static class MyHandler extends Handler {
|
|
|
|
|
|
private WeakReference<MainActivity> mActivityRef;
|
|
|
|
|
|
|
|
|
|
|
|
MyHandler(MainActivity activity) {
|
|
|
|
|
|
mActivityRef = new WeakReference<>(activity);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void handleMessage(Message msg) {
|
|
|
|
|
|
MainActivity activity = mActivityRef.get();
|
|
|
|
|
|
if (activity != null) {
|
|
|
|
|
|
// 使用activity
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
3. **监听器未注销**
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 错误示例
|
|
|
|
|
|
public class MainActivity extends Activity {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
|
|
EventBus.getDefault().register(this); // 注册但未注销
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 正确做法
|
|
|
|
|
|
@Override
|
|
|
|
|
|
protected void onDestroy() {
|
|
|
|
|
|
super.onDestroy();
|
|
|
|
|
|
EventBus.getDefault().unregister(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
4. **资源未关闭**
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 错误示例
|
|
|
|
|
|
FileInputStream fis = new FileInputStream(file);
|
|
|
|
|
|
// 使用后未关闭
|
|
|
|
|
|
|
|
|
|
|
|
// 正确做法:使用try-with-resources
|
|
|
|
|
|
try (FileInputStream fis = new FileInputStream(file)) {
|
|
|
|
|
|
// 使用fis
|
|
|
|
|
|
} // 自动关闭
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## LeakCanary工作原理
|
|
|
|
|
|
|
|
|
|
|
|
### 核心机制
|
|
|
|
|
|
|
|
|
|
|
|
LeakCanary通过以下步骤检测内存泄漏:
|
|
|
|
|
|
|
|
|
|
|
|
1. **对象引用监控**: 监控Activity/Fragment等关键对象的生命周期
|
|
|
|
|
|
2. **弱引用检测**: 使用WeakReference + ReferenceQueue检测对象是否被回收
|
|
|
|
|
|
3. **堆转储分析**: 当检测到泄漏时,dump堆内存进行分析
|
|
|
|
|
|
4. **泄漏路径分析**: 使用Shark库分析GC Root到泄漏对象的引用链
|
|
|
|
|
|
|
|
|
|
|
|
### 工作流程
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Activity创建
|
|
|
|
|
|
↓
|
|
|
|
|
|
LeakCanary.watch() 监控
|
|
|
|
|
|
↓
|
|
|
|
|
|
Activity销毁
|
|
|
|
|
|
↓
|
|
|
|
|
|
等待5秒(GC时间)
|
|
|
|
|
|
↓
|
|
|
|
|
|
检查对象是否被回收
|
|
|
|
|
|
↓
|
|
|
|
|
|
未回收 → 触发堆转储
|
|
|
|
|
|
↓
|
|
|
|
|
|
分析引用链
|
|
|
|
|
|
↓
|
|
|
|
|
|
生成泄漏报告
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 源码关键点
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. ObjectWatcher
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 监控对象是否被回收
|
|
|
|
|
|
public class ObjectWatcher {
|
|
|
|
|
|
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
|
|
|
|
|
|
private final Map<String, KeyedWeakReference> watchedObjects = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
|
|
public void watch(Object watchedObject, String referenceName) {
|
|
|
|
|
|
KeyedWeakReference reference = new KeyedWeakReference(
|
|
|
|
|
|
watchedObject, referenceName, queue, clock);
|
|
|
|
|
|
watchedObjects.put(key, reference);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. HeapDumper
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 堆转储
|
|
|
|
|
|
public interface HeapDumper {
|
|
|
|
|
|
File dumpHeap();
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. HeapAnalyzer
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 分析堆转储文件
|
|
|
|
|
|
public class HeapAnalyzer {
|
|
|
|
|
|
public AnalysisResult checkForLeaks(File heapDumpFile) {
|
|
|
|
|
|
// 使用Shark库分析hprof文件
|
|
|
|
|
|
// 查找GC Root到泄漏对象的路径
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## LeakCanary集成
|
|
|
|
|
|
|
|
|
|
|
|
### 添加依赖
|
|
|
|
|
|
|
|
|
|
|
|
```gradle
|
|
|
|
|
|
dependencies {
|
|
|
|
|
|
// LeakCanary
|
|
|
|
|
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 自动安装(无需代码)
|
|
|
|
|
|
|
|
|
|
|
|
LeakCanary 2.0+ 使用ContentProvider自动初始化,无需手动配置。
|
|
|
|
|
|
|
|
|
|
|
|
### 手动监控
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 监控自定义对象
|
|
|
|
|
|
LeakCanary.INSTANCE.getObjectWatcher().watch(
|
|
|
|
|
|
myObject,
|
|
|
|
|
|
"MyObject被监控"
|
|
|
|
|
|
);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 常见内存泄漏案例
|
|
|
|
|
|
|
|
|
|
|
|
### 1. Handler泄漏
|
|
|
|
|
|
|
|
|
|
|
|
**问题代码**:
|
|
|
|
|
|
```java
|
|
|
|
|
|
public class MainActivity extends Activity {
|
|
|
|
|
|
private Handler mHandler = new Handler() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void handleMessage(Message msg) {
|
|
|
|
|
|
// Handler持有MainActivity的隐式引用
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
|
|
// 发送延迟消息
|
|
|
|
|
|
mHandler.sendEmptyMessageDelayed(0, 60000);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**解决方案**:
|
|
|
|
|
|
```java
|
|
|
|
|
|
public class MainActivity extends Activity {
|
|
|
|
|
|
private static class MyHandler extends Handler {
|
|
|
|
|
|
private WeakReference<MainActivity> mActivityRef;
|
|
|
|
|
|
|
|
|
|
|
|
MyHandler(MainActivity activity) {
|
|
|
|
|
|
mActivityRef = new WeakReference<>(activity);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void handleMessage(Message msg) {
|
|
|
|
|
|
MainActivity activity = mActivityRef.get();
|
|
|
|
|
|
if (activity == null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 处理消息
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private MyHandler mHandler = new MyHandler(this);
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
protected void onDestroy() {
|
|
|
|
|
|
super.onDestroy();
|
|
|
|
|
|
mHandler.removeCallbacksAndMessages(null);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 单例持有Context
|
|
|
|
|
|
|
|
|
|
|
|
**问题代码**:
|
|
|
|
|
|
```java
|
|
|
|
|
|
public class AppManager {
|
|
|
|
|
|
private static AppManager sInstance;
|
|
|
|
|
|
private Context mContext;
|
|
|
|
|
|
|
|
|
|
|
|
private AppManager(Context context) {
|
|
|
|
|
|
mContext = context; // 可能持有Activity
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static AppManager getInstance(Context context) {
|
|
|
|
|
|
if (sInstance == null) {
|
|
|
|
|
|
sInstance = new AppManager(context);
|
|
|
|
|
|
}
|
|
|
|
|
|
return sInstance;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**解决方案**:
|
|
|
|
|
|
```java
|
|
|
|
|
|
public class AppManager {
|
|
|
|
|
|
private static AppManager sInstance;
|
|
|
|
|
|
private Context mContext;
|
|
|
|
|
|
|
|
|
|
|
|
private AppManager(Context context) {
|
|
|
|
|
|
// 使用Application Context
|
|
|
|
|
|
mContext = context.getApplicationContext();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static AppManager getInstance(Context context) {
|
|
|
|
|
|
if (sInstance == null) {
|
|
|
|
|
|
sInstance = new AppManager(context);
|
|
|
|
|
|
}
|
|
|
|
|
|
return sInstance;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 匿名内部类
|
|
|
|
|
|
|
|
|
|
|
|
**问题代码**:
|
|
|
|
|
|
```java
|
|
|
|
|
|
public class MainActivity extends Activity {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
|
|
|
|
|
|
|
|
// 匿名内部类持有外部类引用
|
|
|
|
|
|
new Thread(new Runnable() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void run() {
|
|
|
|
|
|
// 长时间运行的任务
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
// ...
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}).start();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**解决方案**:
|
|
|
|
|
|
```java
|
|
|
|
|
|
public class MainActivity extends Activity {
|
|
|
|
|
|
private static class MyRunnable implements Runnable {
|
|
|
|
|
|
private WeakReference<MainActivity> mActivityRef;
|
|
|
|
|
|
|
|
|
|
|
|
MyRunnable(MainActivity activity) {
|
|
|
|
|
|
mActivityRef = new WeakReference<>(activity);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void run() {
|
|
|
|
|
|
MainActivity activity = mActivityRef.get();
|
|
|
|
|
|
if (activity == null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 执行任务
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
|
|
new Thread(new MyRunnable(this)).start();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 内存优化最佳实践
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 使用Application Context
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 优先使用Application Context
|
|
|
|
|
|
Context appContext = context.getApplicationContext();
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 及时释放资源
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
@Override
|
|
|
|
|
|
protected void onDestroy() {
|
|
|
|
|
|
super.onDestroy();
|
|
|
|
|
|
// 注销监听器
|
|
|
|
|
|
// 取消网络请求
|
|
|
|
|
|
// 停止动画
|
|
|
|
|
|
// 释放资源
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 使用WeakReference
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 对于可能持有Activity引用的对象,使用WeakReference
|
|
|
|
|
|
private WeakReference<Activity> mActivityRef;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4. 避免静态变量持有View/Context
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 错误
|
|
|
|
|
|
private static TextView sTextView;
|
|
|
|
|
|
|
|
|
|
|
|
// 正确:使用WeakReference或避免静态变量
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5. 使用对象池
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 复用对象,减少GC压力
|
|
|
|
|
|
ObjectPool<MyObject> pool = new ObjectPool<>(10);
|
|
|
|
|
|
MyObject obj = pool.acquire();
|
|
|
|
|
|
// 使用后归还
|
|
|
|
|
|
pool.release(obj);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 内存分析工具
|
|
|
|
|
|
|
|
|
|
|
|
### 1. Android Profiler
|
|
|
|
|
|
|
|
|
|
|
|
- 实时内存监控
|
|
|
|
|
|
- 堆转储分析
|
|
|
|
|
|
- 内存分配追踪
|
|
|
|
|
|
|
|
|
|
|
|
### 2. MAT (Memory Analyzer Tool)
|
|
|
|
|
|
|
|
|
|
|
|
- 分析hprof文件
|
|
|
|
|
|
- 查找内存泄漏
|
|
|
|
|
|
- 查看对象引用关系
|
|
|
|
|
|
|
|
|
|
|
|
### 3. LeakCanary
|
|
|
|
|
|
|
|
|
|
|
|
- 自动检测泄漏
|
|
|
|
|
|
- 可视化引用链
|
|
|
|
|
|
- 开发阶段使用
|
|
|
|
|
|
|
|
|
|
|
|
## 内存优化指标
|
|
|
|
|
|
|
|
|
|
|
|
- **峰值内存**: 根据设备配置设定上限(如512MB)
|
|
|
|
|
|
- **内存泄漏**: 0个
|
|
|
|
|
|
- **GC频率**: 尽量减少Full GC
|
|
|
|
|
|
- **内存抖动**: 避免频繁分配/释放
|
|
|
|
|
|
|
|
|
|
|
|
## 相关链接
|
|
|
|
|
|
|
|
|
|
|
|
- [[README]]
|
|
|
|
|
|
- [[09-调试与工具链]]
|
|
|
|
|
|
- [[05-进程与线程通信]]
|