9.0 KiB
9.0 KiB
内存优化(LeakCanary原理)
概述
内存泄漏是Android开发中的常见问题,会导致应用内存占用持续增长,最终可能引发OOM(Out of Memory)崩溃。LeakCanary是Square公司开源的内存泄漏检测工具,本文档深入解析其工作原理和使用方法。
内存泄漏基础
什么是内存泄漏
内存泄漏是指程序在运行过程中,由于某些原因导致已分配的内存无法被回收,造成内存浪费和潜在的性能问题。
Android中的内存泄漏场景
- 静态变量持有Context
// 错误示例
public class MyApplication {
private static Context sContext; // 持有Activity引用
}
// 正确做法
public class MyApplication {
private static Context sAppContext; // 使用Application Context
}
- 非静态内部类持有外部类引用
// 错误示例
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
}
}
}
}
- 监听器未注销
// 错误示例
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);
}
- 资源未关闭
// 错误示例
FileInputStream fis = new FileInputStream(file);
// 使用后未关闭
// 正确做法:使用try-with-resources
try (FileInputStream fis = new FileInputStream(file)) {
// 使用fis
} // 自动关闭
LeakCanary工作原理
核心机制
LeakCanary通过以下步骤检测内存泄漏:
- 对象引用监控: 监控Activity/Fragment等关键对象的生命周期
- 弱引用检测: 使用WeakReference + ReferenceQueue检测对象是否被回收
- 堆转储分析: 当检测到泄漏时,dump堆内存进行分析
- 泄漏路径分析: 使用Shark库分析GC Root到泄漏对象的引用链
工作流程
Activity创建
↓
LeakCanary.watch() 监控
↓
Activity销毁
↓
等待5秒(GC时间)
↓
检查对象是否被回收
↓
未回收 → 触发堆转储
↓
分析引用链
↓
生成泄漏报告
源码关键点
1. ObjectWatcher
// 监控对象是否被回收
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
// 堆转储
public interface HeapDumper {
File dumpHeap();
}
3. HeapAnalyzer
// 分析堆转储文件
public class HeapAnalyzer {
public AnalysisResult checkForLeaks(File heapDumpFile) {
// 使用Shark库分析hprof文件
// 查找GC Root到泄漏对象的路径
}
}
LeakCanary集成
添加依赖
dependencies {
// LeakCanary
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
}
自动安装(无需代码)
LeakCanary 2.0+ 使用ContentProvider自动初始化,无需手动配置。
手动监控
// 监控自定义对象
LeakCanary.INSTANCE.getObjectWatcher().watch(
myObject,
"MyObject被监控"
);
常见内存泄漏案例
1. Handler泄漏
问题代码:
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);
}
}
解决方案:
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
问题代码:
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;
}
}
解决方案:
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. 匿名内部类
问题代码:
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();
}
}
解决方案:
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
// 优先使用Application Context
Context appContext = context.getApplicationContext();
2. 及时释放资源
@Override
protected void onDestroy() {
super.onDestroy();
// 注销监听器
// 取消网络请求
// 停止动画
// 释放资源
}
3. 使用WeakReference
// 对于可能持有Activity引用的对象,使用WeakReference
private WeakReference<Activity> mActivityRef;
4. 避免静态变量持有View/Context
// 错误
private static TextView sTextView;
// 正确:使用WeakReference或避免静态变量
5. 使用对象池
// 复用对象,减少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
- 内存抖动: 避免频繁分配/释放