Handler 持有 Activity 或 Fragment 的强引用
Handler 通常用于在特定线程(如后台线程)上安排代码在 UI 线程上执行。如果 Handler 在其内部类中持有对 Activity 或 Fragment 的强引用,并且这些组件在 Handler 的消息被处理之前被销毁,那么就会导致内存泄漏。
public class LeakyActivity extends AppCompatActivity {
private final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 尝试更新 UI,但此时 Activity 可能已经被销毁了
updateUI();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leaky);
// 发送一个延时的消息到 Handler
handler.postDelayed(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain();
handler.sendMessage(msg);
}
}, 10000); // 假设 10 秒后处理消息
// 假设用户在这里关闭了 Activity,但 Handler 的消息还在队列中
}
// 假设的更新 UI 方法
private void updateUI() {
// 更新 UI 逻辑
}
@Override
protected void onDestroy() {
super.onDestroy();
// 应该在这里移除所有挂起的消息和回调,但在这个例子中我们故意不这样做
}
}
为了解决这个问题,可以使用静态的 Handler,并通过弱引用来持有 Activity 或 Fragment 的引用,或者在 Activity/Fragment 销毁时移除所有挂起的消息和回调。但是,更简单的解决方案是使用 Handler 的 removeCallbacksAndMessages(null) 方法在 onDestroy() 中清除所有消息。然而,这种方法可能不够精确,因为它会移除所有消息,而不仅仅是与特定任务相关的消息。更好的做法是使用弱引用和适当的生命周期管理。
当然,除了 Handler
持有 Activity 或 Fragment 的强引用导致的内存泄漏之外,还有其他几个常见的内存泄漏场景:
1. 静态集合类
如果静态集合类(如 HashMap
、ArrayList
等)持有了对 Activity、Fragment 或其他 Context 的引用,并且这些组件被销毁后,静态集合类中的引用没有被清除,那么就会导致内存泄漏。因为静态集合的生命周期与应用程序的生命周期相同,它们持有的对象不会被垃圾回收器回收。
解决方案:
- 避免在静态集合中存储对 Activity、Fragment 或 Context 的直接引用。
- 如果必须存储,考虑使用弱引用(
WeakReference
)或软引用(SoftReference
)。 - 在 Activity 或 Fragment 销毁时,从静态集合中移除对应的引用。
2. 线程或定时器(如 Timer
和 TimerTask
)
当使用线程或定时器执行后台任务时,如果它们持有了对 Activity、Fragment 或其他生命周期感知组件的强引用,并且这些组件在任务完成之前被销毁,那么就会导致内存泄漏。因为线程或定时器的生命周期可能超出组件的生命周期。
解决方案:
- 使用弱引用或观察者模式来避免直接持有对组件的强引用。
- 在组件销毁时取消线程或定时器任务。
3. 监听器未移除
在 Android 中,很多组件(如按钮、列表视图等)都支持添加监听器(如点击监听器、滚动监听器等)。如果在 Activity 或 Fragment 的 onCreate()
或其他生命周期方法中添加了监听器,但在对应的 onDestroy()
或其他适当的生命周期方法中未移除这些监听器,那么当组件被销毁时,这些监听器仍然持有对组件的引用,从而导致内存泄漏。
解决方案:
- 在组件销毁时移除所有添加的监听器。
- 使用生命周期感知的组件或库(如 LiveData、RxJava 等),它们可以自动处理生命周期事件。
4. 非静态内部类
非静态内部类默认持有对其外部类的引用。如果非静态内部类被用作长时间运行的操作(如异步任务、后台服务等)的一部分,并且这些操作在外部类(如 Activity、Fragment)被销毁后仍然继续执行,那么就会导致外部类的内存泄漏。
解决方案:
- 将内部类声明为静态,并通过构造函数传递必要的 Context 或其他依赖项。
- 使用弱引用来持有对外部类的引用(尽管这通常不是必要的,因为静态内部类不默认持有外部类的引用)。
5. 资源未关闭
在 Android 开发中,打开的资源(如文件流、数据库连接、Cursor 等)必须在不再需要时正确关闭。如果这些资源没有被关闭,它们将占用系统资源,并在极端情况下导致内存泄漏。
解决方案:
- 使用 try-with-resources 语句(在 Java 7 及更高版本中可用)自动管理资源。
- 在 finally 块中确保关闭所有资源。
- 使用资源感知的库或框架来简化资源管理。
通过注意这些常见的内存泄漏场景并采取适当的预防措施,可以显著提高 Android 应用程序的稳定性和性能。