关于Android中的内存泄漏问题,你需要了解这些

一、Android中内存泄露的经典场景

1. Activity中的Handler长期持有activity引用导致activity泄漏。

原因:

(1). 在Activity内new一个Handler时,该handler实例会对Activity持有一个隐式的引用。
(2). Looper通过loop方法不断循环消息队列,而消息队列中的Message又会持有handler的引用。
(3). 当Activity被finish掉后,消息队列未处理完,而handler持有对activity引用,因此即使activity被finish掉,也不会被gc掉。

解决方法:

(1) 将内部类handler创建到外部类,handler仅持有activity的弱引用。
(2) 将内部类handler设为静态,因为静态内部类不持有对外部类的引用。
(3) 或者在activity的onDestory方法中干掉handler的所有callback和message,mHandler.removeCallbacksAndMessages(null);
2. 非静态匿名内部类、匿名内部类造成内存泄露

原因:

(1)当我们在Activity中直接new一个Thread,但当我们的Activity被finish时,Thread仍在运行,就造成了内存泄露

解决方式:

(1)同样把Thread定义为静态的内部类,这样就不会持有外部类的引用。
3. 单例+依赖注入

原因:

(1)当我们在单例类中(静态实体,生命周期和App一样,非常长)持有Activity/context的对象引用,造成内存泄露

解决方法:

(1)单例的类尽量不要持有Activity/context对象,非要持有的话,可以选择ApplicationContext。
(2)可以持有Activity/context的弱引用,在它们被内存回收的时候避免强引用而造成无法回收的问题。
4. 资源对象没关闭造成的内存泄漏

原因:

(1)Cursor、InputStream、OutputStream、Socket等在使用后,没有进行关闭,然后长期堆积就会导致内存泄漏.

解决方案:

(1)在使用完毕后进行关闭。例如使用完cursor,可以在finally进行cursor.close()。其他同理。

5. webview导致的内存泄漏

原因:

(1) 在销毁webview前没有把webview从父view中移除,会导致内存泄漏。

解决:

(1) 在销毁webview前一定要onDetachedFromWindow,我们先将webview从它的父view中移除再调用destroy方法。

    @Override
    public void destroy() {
        try {
            ViewParent parent = getParent();
            if (parent != null) {
                ((ViewGroup) parent).removeView(this);
            }
            clearView();
            removeAllViews();
            super.destroy();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

二、提前发现

1.通过工具

(1)通过Android Studio的Monitor工具,打开Memory标签查看内存变化情况,通过dump java head采集数据,生成hprof文件
(2)通过MAT工具,导入hprof文件,它会帮我们分析内存泄露的原因
(3)通过LeakCanary插件,LeakCanary会在app运行时分析当前的内存快照,找到对象的引用链,并显示到页面上,好处是可以直接在手机上查看(如果检测到泄漏会发送到通知栏,点击通知栏就可以跳转到具体的泄漏分析页面。)
2.通过代码

(1)debug版本可以起一个长期工作的线程LeakThread在后台专门做泄漏检测
(2)向Application注册一个页面生命周期的监听:application.registerActivityLifecycleCallbacks
(3)在监听类中对 onActivityDestoryed(Activity activity) 的事件回调做处理:
         如果一个Activity走到onDestroy,那么这个Activity对象就是需要被回收的目标。
         我们声明一个检测对象的弱引用ref = new WeakReference(activity)
(4)在LeakThread中我们每隔一段时间检测一下ref.get() 是否为空,为空说明activity已被释放。不为空可以手动触一次发gc;如果超过一段时间,比如50s,页面对象还未被清理,我们可以推断内存泄漏的发生。
(5)当内存泄漏发生时,提示给开发者,并自动dump出.prof文件。

三、常见的内存泄漏检测工具

1.MAT

MAT出自于Eclipse公司,可以利用Android Studio的Profile工具里面的内存工具,导出.hprof文件,然后通过Android SDK的hprof-conv命令把.hprof文件转为MAT工具支持的标准.hrpof格式文件,再用MAT打开标准格式的.hrpof文件进行分析,通过分析怀疑对象是否存在,以及排除软、弱、虚引用后查看其引用路径,从而确定泄漏途径。

hprof-conv命令如下:

// mac系统切换到~/Library/Android/sdk/tools/目录执行以下命令
hprof-conv android.hprof mat.hprof

⚠️MacOS打开MAT会报错,正确打开姿势是右键选择“显示包内容”,命令行进入MacOS目录,执行命令打开:

./MemoryAnalyzer -data ./workspace

2.LeakCanary

LeakCanary是Square公司开源的一个内存泄漏检测工具。

  • 官方文档:https://square.github.io/leakcanary/
  • 使用方法:《性能优化工具 - LeakCanary》
  • 触发检测时机:每当Activity/Fragment执行完onDestory的时候,会触发LeakCanary初始化RefWatcher检测该Activity/Fragment是否还在内存中,从而判断是否内存泄漏。
  • 触发dump时机:当Activity/Fragment被销毁时,ObjectWatcher会对它们持有弱引用,并且会进行gc,gc完毕5s钟后查看Activity/Fragment是否还存在,如果存在的话,就会认为存在潜在的内存泄漏。LeakCanary并不会在发现潜在内存泄漏时就进行dump内存堆栈,而是等到泄漏对象超过一定阈值(APP在前台的话默认5个,如果APP在后台的话默认1个)才会进行dump内存堆栈。
  • 如何分析内存堆栈:LeakCanary会使用Shark工具进行内存分析,对于每一个泄漏的对象,它都会找到一个阻止泄漏对象被gc的路径,这个路径就是从垃圾回收器里面找到的泄漏对象的强引用路径。

四、帮助定位线上OOM的方法

1.通过lifecycler监听每一个Activity的创建和销毁

对于一些显式的OOM,如针对一些颗粒度是Activity的泄露,可以在每个Activity的创建和销毁打印Xlog日志,在进行定位线上OOM的时候,可以通过捞取用户日志,过滤相关日志,最终定位是哪一个Activity销毁的,从而解决一些颗粒度为Activity的内存泄露,如退出Activity没有移除延时任务等。

利用这种方法,除了发现一些比较明显的OOM之外,还有作为线索定位其他问题。之前通过这个日志定位到一个由于加载大图导致撑爆内存的问题,当时查了很多用户的日志都没有线索,直到后来捞取到一个用户的日志从启动到OOM崩溃只花了两分钟,于是就跟着用户的操作,进到某一个用户的个人页,发现本地也能复现问题,这才定位出来是由于该用户的个人页里面比较多九宫格图片,在一些比较差的设备由于系统分给App的内存比较小,所以就很容易造成OOM,最终得以解决问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值