第一个线上Bug:OOM内存泄漏的惊魂夜
问题背景
2016年11月初,我入职Android开发岗位已经两个月了。在完成第一个注册功能开发后不久,突然接到紧急通知:线上用户反馈App频繁崩溃,崩溃日志显示大量OOM(Out of Memory)异常。
作为新人,这是我第一次面对线上Bug,内心既紧张又忐忑。当时我们使用的Android SDK版本是23(6.0),项目采用了MVP架构,使用Retrofit 2.0进行网络请求。
问题分析
1. 初步排查
首先查看崩溃日志,发现主要的异常信息:
java.lang.OutOfMemoryError: Failed to allocate a 8388608 byte allocation with 4194304 free bytes and 12MB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444)
2. 使用LeakCanary检测
在mentor张哥的指导下,我们决定使用LeakCanary来检测内存泄漏。首先在build.gradle中添加依赖:
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
然后在Application中初始化:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
LeakCanary.install(this);
}
}
}
3. MAT内存分析
除了LeakCanary,我们还使用MAT(Memory Analyzer Tool)进行更深入的分析。通过Android Studio的Profiler功能导出hprof文件,然后在MAT中分析内存使用情况。
问题定位
经过分析,我们发现问题出现在注册页面的RegisterActivity中。具体代码如下:
public class RegisterActivity extends AppCompatActivity implements RegisterContract.View {
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case REGISTER_SUCCESS:
// 处理注册成功逻辑
break;
case REGISTER_FAILED:
// 处理注册失败逻辑
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 启动定时任务
handler.sendEmptyMessageDelayed(REGISTER_CHECK, 1000);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 忘记移除消息,导致内存泄漏
}
}
问题分析:
- Handler持有Activity的隐式引用(this)
- 当Activity被销毁时,Handler中的消息还未处理完
- Message持有Handler引用,Handler持有Activity引用
- 导致Activity无法被GC回收,造成内存泄漏
解决方案
1. 使用WeakReference修复
修改后的代码:
public class RegisterActivity extends AppCompatActivity implements RegisterContract.View {
private MyHandler mHandler = new MyHandler(this);
private static class MyHandler extends Handler {
private WeakReference<RegisterActivity> mActivity;
public MyHandler(RegisterActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
RegisterActivity activity = mActivity.get();
if (activity != null) {
switch (msg.what) {
case REGISTER_SUCCESS:
// 安全的引用
break;
case REGISTER_FAILED:
// 安全的引用
break;
}
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.sendEmptyMessageDelayed(REGISTER_CHECK, 1000);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 移除所有消息,避免内存泄漏
mHandler.removeCallbacksAndMessages(null);
}
}
2. 使用静态内部类
另一种解决方案是使用静态内部类:
public class RegisterActivity extends AppCompatActivity {
private static class SafeHandler extends Handler {
private final WeakReference<RegisterActivity> mActivity;
public SafeHandler(RegisterActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
RegisterActivity activity = mActivity.get();
if (activity != null) {
// 处理消息
}
}
}
private SafeHandler mHandler = new SafeHandler(this);
}
验证修复效果
1. LeakCanary验证
修复后,LeakCanary不再报告内存泄漏问题。当我们退出RegisterActivity时,LeakCanary的Notification显示"没有检测到内存泄漏"。
2. MAT验证
使用MAT重新分析内存,发现Activity的引用链被正确释放,内存使用量显著下降。
3. 线上监控
部署修复版本后,线上OOM异常率从原来的5.2%下降到0.3%,问题得到根本解决。
经验总结
1. Handler内存泄漏的常见场景
- 匿名内部类Handler:持有外部类引用
- 静态Handler:持有Activity的静态引用
- 未清理的消息队列:Activity销毁后消息仍在队列中
2. 预防措施
// 正确的Handler使用方式
public class BaseActivity extends AppCompatActivity {
private SafeHandler mHandler = new SafeHandler(this);
private static class SafeHandler extends Handler {
private final WeakReference<BaseActivity> mActivity;
public SafeHandler(BaseActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
BaseActivity activity = mActivity.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
protected void handleMessage(Message msg) {
// 子类重写此方法处理消息
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
3. 最佳实践
- 使用静态内部类:避免隐式持有外部类引用
- 使用WeakReference:确保不会阻止GC
- 及时清理:在onDestroy中移除所有消息和回调
- 工具检测:开发阶段使用LeakCanary检测
- 代码审查:重点关注Handler、Thread、Timer等异步操作
4. 技术栈清单(2016年11月)
Android SDK 23 (6.0)
LeakCanary 1.5
MAT (Memory Analyzer Tool)
MVP Architecture
Retrofit 2.0
OkHttp 3.0
后续思考
这次OOM事件让我深刻认识到:
- 代码质量的重要性:不仅仅是功能实现,更要考虑内存管理
- 工具的价值:LeakCanary等工具能帮助我们快速定位问题
- 学习的必要性:Android内存管理机制需要深入学习
- 团队协作:mentor的指导对新人成长至关重要
通过这次事件,我从一个只关注功能实现的新人,开始重视代码质量和性能优化,这是技术成长的重要一步。