在Android开发中,内存泄漏(Memory Leak)指的是对象无法被垃圾回收器(GC)回收,导致内存持续占用,最终可能引发OOM(OutOfMemoryError)。以下是常见的内存泄漏类型及对应的解决方案:
一、常见内存泄漏类型
1. 静态变量持有Activity/Fragment引用
- 原因:静态变量生命周期与应用进程一致,若持有Activity引用会导致Activity无法被回收。
- 示例:
public class MainActivity extends Activity { private static Context context; // 静态变量持有Activity引用 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); context = this; // 内存泄漏 } }
- 解决方案:
- 使用
Application Context
替代Activity Context。 - 避免在静态变量中存储Activity/Fragment的非静态内部类。
- 使用
2. 非静态内部类/匿名类持有外部类引用
- 原因:非静态内部类(如Handler、Runnable、AsyncTask)会隐式持有外部Activity的引用,若生命周期长于Activity(如异步任务未完成),会导致Activity无法回收。
- 示例:
public class MainActivity extends Activity { private Handler handler = new Handler() { // 非静态内部类 @Override public void handleMessage(Message msg) { // 更新UI } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); handler.postDelayed(() -> { /* 长时间运行的任务 */ }, 10000); } }
- 解决方案:
- 使用静态内部类 + 弱引用(WeakReference):
private static class MyHandler extends Handler { private final WeakReference<MainActivity> activityRef; public MyHandler(MainActivity activity) { this.activityRef = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { MainActivity activity = activityRef.get(); if (activity != null) { // 更新UI } } }
- 在Activity销毁时移除所有未处理的消息:
@Override protected void onDestroy() { super.onDestroy(); handler.removeCallbacksAndMessages(null); }
- 使用静态内部类 + 弱引用(WeakReference):
3. 资源未关闭导致泄漏
- 原因:未正确关闭Cursor、File、InputStream/OutputStream、SQLiteDatabase等资源,导致GC无法回收。
- 示例:
public void readFile() { FileInputStream fis = null; try { fis = new FileInputStream("file.txt"); // 读取文件 } catch (IOException e) { e.printStackTrace(); } // 未关闭fis,导致泄漏 }
- 解决方案:
- 使用
try-with-resources
语句(Java 7+):public void readFile() { try (FileInputStream fis = new FileInputStream("file.txt")) { // 自动关闭资源 } catch (IOException e) { e.printStackTrace(); } }
- 在
finally
块中手动关闭资源:finally { if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } }
- 使用
4. 注册/监听未注销导致泄漏
- 原因:注册广播接收器(BroadcastReceiver)、传感器监听(SensorListener)、数据库监听(ContentObserver)等后,未在Activity销毁时注销。
- 示例:
public class MainActivity extends Activity { private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // 处理广播 } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); registerReceiver(receiver, new IntentFilter("ACTION")); // 注册 } // 未在onDestroy()中调用unregisterReceiver(receiver) }
- 解决方案:
- 在
onDestroy()
中注销监听:@Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(receiver); }
- 在
5. 动画未停止导致泄漏
- 原因:属性动画(ObjectAnimator)或无限循环动画未在Activity销毁时停止,导致View无法被回收。
- 示例:
public class MainActivity extends Activity { private ObjectAnimator animator; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); animator = ObjectAnimator.ofFloat(view, "alpha", 0, 1); animator.setDuration(1000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.start(); } // 未在onDestroy()中停止动画 }
- 解决方案:
- 在
onDestroy()
中停止动画:@Override protected void onDestroy() { super.onDestroy(); if (animator != null && animator.isRunning()) { animator.cancel(); } }
- 在
6. 单例持有Activity引用
- 原因:单例模式的生命周期是全局的,若持有Activity引用会导致Activity无法回收。
- 示例:
public class Singleton { private static Singleton instance; private Context context; private Singleton(Context context) { this.context = context; // 传入Activity Context导致泄漏 } public static Singleton getInstance(Context context) { if (instance == null) { instance = new Singleton(context); } return instance; } }
- 解决方案:
- 使用
Application Context
:public static Singleton getInstance(Context context) { if (instance == null) { instance = new Singleton(context.getApplicationContext()); // 使用App Context } return instance; }
- 使用
7. WebView内存泄漏
- 原因:WebView持有Activity引用,且其内部线程生命周期长于Activity,导致Activity无法回收。
- 解决方案:
- 单独创建进程加载WebView,通过AIDL通信。
- 在
onDestroy()
中销毁WebView:@Override protected void onDestroy() { super.onDestroy(); if (webView != null) { webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); webView.clearHistory(); ((ViewGroup) webView.getParent()).removeView(webView); webView.destroy(); webView = null; } }
二、内存泄漏检测工具
- Android Profiler:查看内存分配和对象存活情况。
- LeakCanary:自动检测内存泄漏并生成报告。
- MAT(Memory Analyzer Tool):分析堆转储文件(Heap Dump),定位大对象和引用链。
- Lint:静态代码分析工具,检查潜在的内存泄漏。
三、最佳实践总结
-
避免Activity/Fragment被长生命周期对象持有:
- 使用静态内部类 + 弱引用。
- 使用
Application Context
替代Activity Context。
-
及时释放资源:
- 使用
try-with-resources
关闭流和数据库。 - 在
onDestroy()
中注销广播接收器、停止动画、取消异步任务。
- 使用
-
谨慎使用单例和静态变量:
- 确保单例只持有
Application Context
。 - 避免静态集合存储Activity/Fragment。
- 确保单例只持有
-
使用弱引用(WeakReference):
- 适用于缓存、监听器等场景。
-
使用生命周期感知组件:
- 如ViewModel、LiveData自动管理生命周期。
-
定期进行内存分析:
- 使用工具检测和修复泄漏,特别是在性能敏感的应用中。
通过以上方法,可以有效减少Android应用中的内存泄漏,提高应用稳定性和性能。