Android 内存泄漏详解

一、什么是内存泄漏?

内存泄漏(Memory Leak) 是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

在 Android 中,更具体的定义是:长生命周期对象持有短生命周期对象的引用,导致短生命周期对象无法被垃圾回收器(GC)回收


二、内存泄漏的危害

危害 说明
OOM 崩溃 内存持续增长,最终触发 OutOfMemoryError,应用崩溃
卡顿/ANR GC 频繁触发,主线程被阻塞,界面卡顿甚至 ANR
电量消耗 后台泄漏导致进程无法被回收,持续占用 CPU 和内存
用户体验差 应用越来越卡,需要频繁重启

三、导致内存泄漏的常见情况

1. 静态变量持有 Activity/Context 引用 ⭐最常见

java

复制代码
public class Singleton {
    private static Singleton instance;
    private Context context;
    
    private Singleton(Context context) {
        this.context = context; // ❌ 持有 Activity 引用
    }
    
    public static Singleton getInstance(Context context) {
        if (instance == null) {
            instance = new Singleton(context);
        }
        return instance;
    }
}

原因:静态变量生命周期 = 应用生命周期,如果传入的是 Activity Context,Activity 销毁后仍被静态引用持有,无法回收。

解决 :使用 Application Contextcontext.getApplicationContext())。


2. 非静态内部类 / 匿名内部类持有外部类引用

java

复制代码
public class MainActivity extends AppCompatActivity {
    
    // ❌ 非静态内部类隐式持有外部 Activity 引用
    private class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            // ...
        }
    }
    
    // ❌ 匿名内部类同样持有 Activity 引用
    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            // 延迟执行,Activity 可能已销毁
        }
    };
}

原因:非静态内部类会隐式持有外部类的引用。如果内部类有耗时操作(如 Handler 延迟消息、AsyncTask、Thread),Activity 销毁时无法被回收。

解决

  • 使用 静态内部类 + 弱引用(WeakReference)

  • onDestroy() 中移除消息/回调

java

复制代码
private static class MyHandler extends Handler {
    private WeakReference<MainActivity> weakRef;
    
    MyHandler(MainActivity activity) {
        weakRef = new WeakReference<>(activity);
    }
    
    @Override
    public void handleMessage(Message msg) {
        MainActivity activity = weakRef.get();
        if (activity != null) {
            // 安全使用
        }
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    handler.removeCallbacksAndMessages(null); // 移除所有消息
}

3. Handler 内存泄漏 ⭐高频考点

java

复制代码
public class MainActivity extends AppCompatActivity {
    private Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            // 隐式持有 MainActivity 引用
        }
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        handler.sendEmptyMessageDelayed(0, 10000); // 10秒后执行
    }
}

原因Message 会持有 Handler 引用,Handler 持有 Activity 引用,MessageQueue 持有 Message。如果 Activity 在消息执行前被销毁,整条引用链导致 Activity 泄漏。

解决 :静态内部类 + WeakReference + onDestroy() 移除消息。


4. 资源未关闭

java

复制代码
// ❌ 未关闭的资源
Cursor cursor = getContentResolver().query(...);
// 忘记 cursor.close();

Bitmap bitmap = BitmapFactory.decodeResource(...);
// 忘记 bitmap.recycle(); // Android 3.0+ 后 Bitmap 内存由 GC 管理,但大 Bitmap 仍建议及时释放

InputStream is = ...;
// 忘记 is.close();

解决 :使用 try-finally 或在 Kotlin 中使用 use 自动关闭。


5. 监听器未注销

java

复制代码
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ❌ 注册后未在 onDestroy 中注销
        getSystemService(LocationManager.class)
            .requestLocationUpdates(..., listener);
    }
}

常见场景

  • LocationManager.requestLocationUpdates()removeUpdates()

  • SensorManager.registerListener()unregisterListener()

  • BroadcastReceiver 动态注册后未 unregisterReceiver()

  • EventBus / RxJava 订阅后未取消订阅

解决 :在 onDestroy() / onStop() 中及时注销。


6. 单例模式持有回调/监听器

java

复制代码
public class NetworkManager {
    private static NetworkManager instance;
    private NetworkCallback callback;
    
    public void setCallback(NetworkCallback callback) {
        this.callback = callback; // ❌ 可能持有 Activity 引用
    }
}

解决:使用弱引用,或在 Activity 销毁时手动置空。


7. WebView 内存泄漏

java

复制代码
// ❌ 直接在 xml 中声明 WebView,Activity 销毁时 WebView 持有 Activity 引用
<WebView
    android:id="@+id/webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

原因:WebView 内部逻辑复杂,会持有 Activity 引用,且不易释放。

解决

  • 使用独立的进程加载 WebView(android:process=":web"

  • 动态添加/移除 WebView,销毁时从父布局移除并调用 destroy()

  • 使用腾讯 X5 内核等第三方方案

java

复制代码
@Override
protected void onDestroy() {
    if (webView != null) {
        webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
        webView.clearHistory();
        ((ViewGroup) webView.getParent()).removeView(webView);
        webView.destroy();
        webView = null;
    }
    super.onDestroy();
}

8. 集合类泄漏

java

复制代码
public class CacheManager {
    // ❌ 静态集合一直增长,从未清理
    private static List<Object> cache = new ArrayList<>();
    
    public void add(Object obj) {
        cache.add(obj);
    }
    // 没有 remove() 或 clear() 机制
}

解决 :使用 LruCache 等有限容量的缓存,或设置过期清理机制。


9. 属性动画(Animator)未取消

java

复制代码
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 0, 1);
animator.setDuration(3000);
animator.start();
// ❌ Activity 销毁时未取消动画

原因:动画会持有 target View 的引用,View 持有 Activity 引用。

解决 :在 onDestroy()animator.cancel()


10. 系统服务持有 Context

java

复制代码
// ❌ 错误
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);

某些系统服务(如 InputMethodManager)内部存在已知的内存泄漏 Bug(Android 底层问题)。

解决:使用 Application Context 获取系统服务。


11. 第三方库使用不当

  • Glide:传入 Activity Context 时,Glide 会绑定生命周期;但如果传入 Application Context 且未手动管理,可能泄漏

  • RxJava :订阅后未在 onDestroy()dispose()

  • EventBus :注册后未 unregister()

  • LeakCanary:虽然它是检测工具,但本身也可能有轻微影响


四、内存泄漏检测工具

表格

工具 用途
LeakCanary Square 出品,自动检测并展示泄漏引用链,开发必备
Android Studio Profiler 实时查看内存分配,Heap Dump 分析
MAT (Memory Analyzer Tool) 分析 hprof 文件,查找支配树和引用链
adb shell dumpsys meminfo 查看应用内存占用情况

五、预防内存泄漏的黄金法则

  1. 能用 Application Context 就不用 Activity Context

  2. 静态变量不持有短生命周期对象

  3. 内部类尽量声明为 static,配合 WeakReference

  4. 有注册就有注销,有开启就有关闭

  5. 耗时操作/延迟任务要考虑生命周期

  6. 使用 Jetpack 组件(ViewModel、Lifecycle、LiveData)自动管理生命周期

  7. 接入 LeakCanary 在开发阶段及时发现


六、总结图

plain

复制代码
长生命周期对象 ──持有引用──► 短生命周期对象
     ↑                           │
     └───────────────────────────┘
           无法被 GC 回收
           = 内存泄漏
相关推荐
雨声不在2 小时前
docker-android 重启后镜像起不来
android·docker·容器
阿pin2 小时前
Android随笔-Framework
android·framework
达达尼昂3 小时前
Flutter AI Harness 如何让 Agent 参与软件开发全流程
android·人工智能·后端
sRect3 小时前
用ESP32-S3开发板开发一个接收消息的BB机
android·serverless·嵌入式
AFinalStone4 小时前
Android 7系统无障碍服务(二)AccessibilityManagerService 启动与初始化
android·无障碍服务
小孔龙4 小时前
BufferQueue 多缓冲与背压
android
阿pin4 小时前
Android随笔-kotlin Flow
android·kotlin·flow
阳光予你4 小时前
App 启动流程:从 Launcher 点击到 Activity 创建
android
松仔log4 小时前
JetPack——Hilt
android·java·开发语言·kotlin