一、内存泄漏的根本原因
内存泄漏的本质是:对象不再被使用时,由于被其他对象错误持有,导致无法被垃圾回收(GC)。在 Android 中,常见于以下场景:
二、常见内存泄漏场景与解决方案
1. 静态变量持有 Activity 或 View 的引用
原因:静态变量生命周期与 Application 一致,若持有 Activity 或 View 的强引用,会导致 Activity 无法被回收。
public class LeakUtils {
// 错误示例:静态变量持有 Activity
public static Activity sLeakedActivity;
}
// 在某个 Activity 中赋值
LeakUtils.sLeakedActivity = this; // Activity 销毁后仍被持有
解决方案:
避免使用静态变量直接引用 Activity/View。
若必须持有 Context,使用 WeakReference(弱引用):
public class SafeUtils {
private static WeakReference<Context> sWeakContext;
public static void setContext(Context context) {
sWeakContext = new WeakReference<>(context.getApplicationContext());
}
}
2. Handler 导致的内存泄漏
原因:非静态内部类 Handler 隐式持有外部类(如 Activity)的引用。若未处理完的消息仍在 MessageQueue 中,Activity 无法释放。
public class MainActivity extends Activity {
private Handler mHandler = new Handler() { // 非静态内部类隐式持有 Activity
@Override
public void handleMessage(Message msg) {
// 处理消息
}
};
}
解决方案:使用 静态内部类 + WeakReference:
private static class SafeHandler extends Handler {
private WeakReference<Activity> mWeakActivity;
SafeHandler(Activity activity) {
mWeakActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = mWeakActivity.get();
if (activity != null) {
// 处理消息
}
}
}
在 Activity 销毁时移除所有消息:
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null); // 清空消息队列
}
3. 单例模式持有 Context
原因:单例类持有 Activity 的 Context,而非 Application Context。
public class SingletonManager {
private static SingletonManager instance;
private Context mContext;
private SingletonManager(Context context) {
mContext = context; // 错误:若传入 Activity Context,会导致泄漏
}
public static SingletonManager getInstance(Context context) {
if (instance == null) {
instance = new SingletonManager(context);
}
return instance;
}
}
解决方案:始终使用 Application Context:
private SingletonManager(Context context) {
mContext = context.getApplicationContext(); // 正确:使用 Application Context
}
4. 匿名内部类(如 Runnable、监听器)
原因:匿名内部类隐式持有外部类的引用。例如,子线程中执行耗时任务时持有 Activity 的引用。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new Thread(new Runnable() { // 匿名内部类隐式持有 Activity
@Override
public void run() {
// 耗时操作
}
}).start();
}
}
解决方案:使用静态内部类 + WeakReference:
private static class MyRunnable implements Runnable {
private WeakReference<MainActivity> mWeakActivity;
MyRunnable(MainActivity activity) {
mWeakActivity = new WeakReference<>(activity);
}
@Override
public void run() {
MainActivity activity = mWeakActivity.get();
if (activity != null) {
// 处理逻辑
}
}
}
5. 未取消注册的监听器或回调
原因:注册了广播、事件总线(如 EventBus)或系统服务后未及时注销。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 注册广播接收器
registerReceiver(mReceiver, new IntentFilter("CUSTOM_ACTION"));
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 处理广播
}
};
// 未在 onDestroy() 中注销,导致 Activity 泄漏
}
解决方案:在 onDestroy() 中取消注册:
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver); // 注销广播
// 其他监听器同理(如 EventBus.getDefault().unregister(this))
}
6. 资源未释放(如文件流、数据库连接)
原因:未及时关闭文件、数据库或网络连接,导致资源占用。
public void readFile() {
try {
FileInputStream fis = new FileInputStream("file.txt");
// 读取文件但未关闭流
} catch (IOException e) {
e.printStackTrace();
}
}
解决方案:使用 try-with-resources(Java 7+)或在 finally 块中关闭资源:
try (FileInputStream fis = new FileInputStream("file.txt")) {
// 读取文件
} catch (IOException e) {
e.printStackTrace();
}
三、检测内存泄漏的工具
Android Profiler
内置工具,可实时监控内存使用,查看对象分配和堆转储(Heap Dump)。
LeakCanary
自动检测内存泄漏的开源库,集成后会在发生泄漏时弹出通知并生成泄漏链。
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}
MAT(Memory Analyzer Tool)
分析 Heap Dump 文件,查找可疑引用链。
四、最佳实践
1.遵循生命周期规则:在 onDestroy() 中释放资源、取消注册和移除回调。
2.优先使用弱引用:在需要跨组件持有对象时,使用 WeakReference 或 SoftReference。
3.避免长生命周期的对象持有短生命周期对象的引用(如 Activity 被静态集合持有)。
4.使用 Application Context:在单例或工具类中,优先使用 getApplicationContext()。
5.代码审查与自动化测试:结合工具定期检查潜在内存问题。
五、总结
内存泄漏是 Android 开发中的常见问题,根源在于对象引用未被正确释放。通过合理使用弱引用、及时注销监听器、避免静态变量持有短生命周期对象,以及结合工具检测,可以有效减少泄漏风险。