一、内存泄漏的本质
定义:当对象不再被使用时,由于被其他对象直接或间接持有引用,导致GC无法回收其内存空间的现象。
危害:
-
内存占用持续增长,最终导致OOM
-
应用卡顿,GC频率增加
-
系统整体性能下降
二、典型内存泄漏场景
1. 静态变量持有Activity引用
public class LeakActivity extends Activity {
private static Context sContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sContext = this; // 静态变量持有Activity引用
}
}
原理:静态变量的生命周期与应用进程一致,导致Activity无法被回收。
2. 非静态内部类/匿名内部类
public class MainActivity extends Activity {
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
// 匿名内部类隐式持有外部类引用
}
};
}
原理:非静态内部类会隐式持有外部类的引用,如果内部类生命周期长于外部类(如被线程持有),就会导致泄漏。
3. Handler泄漏
public class LeakActivity extends Activity {
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 延迟消息可能导致Activity泄漏
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
// ...
}
}, 10000);
}
}
原理:
-
Handler作为非静态内部类持有Activity引用
-
Message持有Handler引用
-
MessageQueue持有Message引用
-
主线程的Looper生命周期与应用一致
4. 单例模式不当使用
public class AppManager {
private static AppManager sInstance;
private Context mContext;
private AppManager(Context context) {
this.mContext = context; // 传入Activity Context
}
public static AppManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new AppManager(context);
}
return sInstance;
}
}
原理:单例生命周期与应用一致,如果持有Activity引用会导致泄漏。
5. 资源未释放
public class ResourceActivity extends Activity {
private MediaPlayer mMediaPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMediaPlayer = MediaPlayer.create(this, R.raw.music);
mMediaPlayer.start();
}
// 缺少onDestroy中的资源释放
}
原理:未关闭的Cursor、未停止的MediaPlayer、未注销的BroadcastReceiver等都会持有Context引用。
6. 集合类泄漏
public class CollectionLeak {
private static List<Activity> sActivities = new ArrayList<>();
public static void addActivity(Activity activity) {
sActivities.add(activity);
}
// 缺少移除逻辑
}
原理:全局集合持有对象引用且未及时清理。
7. WebView泄漏
public class WebViewActivity extends Activity {
private WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWebView = new WebView(this);
setContentView(mWebView);
}
}
原理:
-
WebView会持有Activity引用
-
WebView内部线程可能无法及时销毁
8. 动画未取消
public class AnimationActivity extends Activity {
private ObjectAnimator mAnimator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = findViewById(R.id.view);
mAnimator = ObjectAnimator.ofFloat(view, "alpha", 0, 1);
mAnimator.start();
}
// 缺少onDestroy中的动画取消
}
原理:无限动画会持有View引用,而View又持有Context引用。
三、检测内存泄漏的工具
-
LeakCanary
-
自动检测Activity/Fragment泄漏
-
提供泄漏引用链
-
集成简单,适合开发阶段使用
-
-
Android Profiler
-
实时内存监控
-
堆转储(Heap Dump)分析
-
分配追踪(Allocation Tracking)
-
-
MAT(Memory Analyzer Tool)
-
强大的堆转储分析
-
查找Dominator Tree
-
计算对象保留大小
-
-
adb shell dumpsys meminfo
-
查看进程内存概况
-
分析Activity实例数量
-
四、内存泄漏的预防与解决
通用解决方案
-
使用弱引用(WeakReference)
private static WeakReference<Context> sContextRef;
-
及时释放资源
-
在onDestroy中取消注册、停止动画、关闭资源等
-
-
使用Application Context
-
对于生命周期与应用一致的对象,使用getApplicationContext()
-
特定场景解决方案
-
Handler解决方案
// 方案1:静态内部类+弱引用 private static class SafeHandler extends Handler { private final WeakReference<Activity> mActivityRef; public SafeHandler(Activity activity) { mActivityRef = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { Activity activity = mActivityRef.get(); if (activity != null) { // 处理消息 } } } // 方案2:在onDestroy中移除消息 @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }
-
单例模式改进
public class AppManager { private static AppManager sInstance; private Context mContext; // Application Context private AppManager(Context context) { this.mContext = context.getApplicationContext(); } }
-
WebView解决方案
@Override protected void onDestroy() { if (mWebView != null) { mWebView.stopLoading(); mWebView.getSettings().setJavaScriptEnabled(false); mWebView.clearHistory(); mWebView.removeAllViews(); mWebView.destroy(); mWebView = null; } super.onDestroy(); }
五、内存泄漏分析技巧
-
分析堆转储
-
查找重复的Activity实例
-
检查Bitmap是否过大
-
查看集合类是否异常增长
-
-
跟踪引用链
-
从GC Roots到泄漏对象的路径
-
重点关注静态变量、单例、线程等
-
-
比较堆转储
-
操作前后对比内存变化
-
识别异常增长的对象
-
-
监控关键指标
-
Activity实例数量
-
Fragment实例数量
-
Bitmap内存占用
-
通过深入理解这些内存泄漏场景和解决方案,你可以在面试中展示出对Android内存管理的专业认知和实践能力。