Android内存泄漏深度解析

一、内存泄漏的本质

定义:当对象不再被使用时,由于被其他对象直接或间接持有引用,导致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);
    }
}

原理

  1. Handler作为非静态内部类持有Activity引用

  2. Message持有Handler引用

  3. MessageQueue持有Message引用

  4. 主线程的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引用。

三、检测内存泄漏的工具

  1. LeakCanary

    • 自动检测Activity/Fragment泄漏

    • 提供泄漏引用链

    • 集成简单,适合开发阶段使用

  2. Android Profiler

    • 实时内存监控

    • 堆转储(Heap Dump)分析

    • 分配追踪(Allocation Tracking)

  3. MAT(Memory Analyzer Tool)

    • 强大的堆转储分析

    • 查找Dominator Tree

    • 计算对象保留大小

  4. adb shell dumpsys meminfo

    • 查看进程内存概况

    • 分析Activity实例数量

四、内存泄漏的预防与解决

通用解决方案

  1. 使用弱引用(WeakReference)

    private static WeakReference<Context> sContextRef;
  2. 及时释放资源

    • 在onDestroy中取消注册、停止动画、关闭资源等

  3. 使用Application Context

    • 对于生命周期与应用一致的对象,使用getApplicationContext()

特定场景解决方案

  1. 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);
    }
  2. 单例模式改进

    public class AppManager {
        private static AppManager sInstance;
        private Context mContext; // Application Context
        
        private AppManager(Context context) {
            this.mContext = context.getApplicationContext();
        }
    }
  3. WebView解决方案

    @Override
    protected void onDestroy() {
        if (mWebView != null) {
            mWebView.stopLoading();
            mWebView.getSettings().setJavaScriptEnabled(false);
            mWebView.clearHistory();
            mWebView.removeAllViews();
            mWebView.destroy();
            mWebView = null;
        }
        super.onDestroy();
    }

五、内存泄漏分析技巧

  1. 分析堆转储

    • 查找重复的Activity实例

    • 检查Bitmap是否过大

    • 查看集合类是否异常增长

  2. 跟踪引用链

    • 从GC Roots到泄漏对象的路径

    • 重点关注静态变量、单例、线程等

  3. 比较堆转储

    • 操作前后对比内存变化

    • 识别异常增长的对象

  4. 监控关键指标

    • Activity实例数量

    • Fragment实例数量

    • Bitmap内存占用

通过深入理解这些内存泄漏场景和解决方案,你可以在面试中展示出对Android内存管理的专业认知和实践能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值