Android 内存泄漏的检测和解决

Android 内存泄漏的检测和解决

    • 前言
    • MAT工具下载地址
    • Android Profiler的使用
      • 1、Run菜单下的profile
      • 2、导出hprof文件
      • 3、文件格式转换
    • MAT工具使用
      • 1、导入文件
      • 2、分析开始
    • 总结
        • 典型处理方案
        • 优化内存的良好编码习惯

前言

Android内存泄漏的产生的原因:一个长生命周期的对象持有一个短生命周期对象的引用
通俗讲就是该回收的对象,因为引用问题没有被回收,最终会产生OOM

.分析内存的常用工具有:

  • top/procrank
  • meinfo
  • Procstats
  • DDMS
  • MAT
  • Finder-Activity
  • LeakCanary
  • LeakInspector

MAT工具下载地址

MAT工具下载地址: https://www.eclipse.org/mat/downloads.php
自己下载自己对应版本

Android Profiler的使用

Android Profiler是Android Studio编译器自带的工具;

1、Run菜单下的profile

Android 内存泄漏的检测和解决_第1张图片

2、导出hprof文件

在图型用户界面上选择要分析的一段内存,右键export出来 ,导入到一个文件夹中,最好新建一个文件夹,因为在使用MAT时候,会产生很多的临时文件。
profile拍快照时,下方分析数据中列代表的含义:

  • Allocations: 动态分配对象个数
  • Total count :对象的总数
  • Shallow Size:对象本身占用的内存大小
  • Retained Size:GC回收能收走的内存大小
  • Deallocation:解除分配的对象个数

注意:我们先把需要检测的界面,打开关闭循环几次,然后再使用profile内存分析这一块的gc工具,就是下方图片中小红方框左边那个垃圾桶,多点几次,让gc来次高潮,然后我们在点击下方途中的小红方框,进行拍快照
Android 内存泄漏的检测和解决_第2张图片
Android 内存泄漏的检测和解决_第3张图片
Android 内存泄漏的检测和解决_第4张图片

3、文件格式转换

使用hprof-conv命令进行文件格式转换
注意:hprof-conv命令是SDK中自带的,如果在cmd中找不到该命令,请先查看是否配置了环境变量;
转换命令:hprof-conv -z src dst
Android 内存泄漏的检测和解决_第5张图片
Android 内存泄漏的检测和解决_第6张图片

MAT工具使用

1、导入文件

一顿导入文件操作
选择带_mat的文件
Android 内存泄漏的检测和解决_第7张图片
Android 内存泄漏的检测和解决_第8张图片
Android 内存泄漏的检测和解决_第9张图片
Android 内存泄漏的检测和解决_第10张图片

2、分析开始

在这个Class Name这一块输入要分析的Activity或者Fragment名字;
我这里是分析WelcomeActivity,所以我输入了WelcomeActivity然后点击回车;

这个时候重点来了,看看Objects 这一栏,1,居然有一个地方还持有我们已经销毁的WelcomeActivity的引用
Android 内存泄漏的检测和解决_第11张图片
这是我们就要看到底哪里还持有WelcomeActivity的引用了~!我们要干掉他!!
右击选择
我们这里只过滤出来强引用
Android 内存泄漏的检测和解决_第12张图片
这时候我们能看到持有引用的最顶层是哪个地方,我们就需要一层一层的展开,看看这条引用链,看看到底是从哪里开始的
Android 内存泄漏的检测和解决_第13张图片
一层一层的展开,分析要从下往上去查看,最下方是我自己的类,从下往上第二个发现不是我自己的类,OK,我们就从这类下手,我们解决掉这个地方,顶层的引用就不存在了,这条引用链就能解除掉了;
从下面这张图可以看得出是ConnectivityManager类中的sInstance变量持有了我的WelcomeActivity的context;
Android 内存泄漏的检测和解决_第14张图片
进入自己的项目,看了下使用ConnectivityManager类的地方只有这个自己写的这个工具类了,上面很清楚了是context泄漏了,仔细一看我居然没注意直接传入了当前Activity的上下文;
Android 内存泄漏的检测和解决_第15张图片
我们来修改下,获取application的上下文(非业务需要不要把activity的上下文做参数传递,可以传递application的上下文
Android 内存泄漏的检测和解决_第16张图片
然后我们再来试下,看看是否还有泄漏?
再来一把上面的流程~~~~~~Objects 0 内存泄漏的引用没了!!!
Android 内存泄漏的检测和解决_第17张图片

总结

典型处理方案

1、第三方SDK引发的问题处理,我们可以看看第三方SDK有没有提供类似解绑的方法,我们在页面结束的时候,顺手将其解绑;如果没有提供该方法,我们就需要暴力手段,反射引用的类置空自己的引用;
2、Android源码引发的问题,我们就需要暴力手段,反射引用的类置空自己的引用;

优化内存的良好编码习惯

很多时候我们需要注意的是自己良好的编码习惯,这样才能减少问题的出现

  • 数据类型:不要使用比需求更占空间的基本数据类型,例如能使用int的就不要去使用float等等;
  • 循环尽量用foreach,少用iterator, 自动装箱尽量少用
  • 数据结构与算法的解度处理:例如:数据量千级以内可以使用
    Sparse数组(key为整数),ArrayMap(key为对象)
    性能不如HashMap但节约内存
  • 枚举问题:
    1.每一个枚举值都是一个单例对象,在使用它时会增加额外的内存消耗,所以枚举相比与 Integer 和 String 会占用更多的内存;
    2.较多的使用 Enum 会增加 DEX 文件的大小,会造成运行时更多的IO开销,使我们的应用需要更多的空间;
    3.特别是分dex多的大型APP,枚举的初始化很容易导致ANR;
  • static staticfinal的问题:
    1.static会由编译器调用clinit方法进行初始化
    2.static final不需要进行初始化工作,打包在dex文件中可以直接调用,并不会在类初始化申请内存
    3.所以基本数据类型的成员,可以全写成static final
  • 字符串的连接尽量少用加号(+),可以用StringBuffer 和 StringBuilder来做字符串的拼接
  • .重复申请内存的问题
    1.不要在同一个方法多次调用,如递归函数 ,回调函数中new对象;
    2.不要在读流直接在循环中new对象等;
    3.不要在onMeause() onLayout() onDraw() 中去刷新UI(requestLayout)
  • Activity组件泄漏;
    1.非业务需要不要把activity的上下文做参数传递,可以传递application的上下文;
    2.和Activity有关联的对象千万不要写static 如private static Button btn; private static Drawable drawable,因为加上这个static,会在gc引用链顶层的gc roots里面,所以会得不到回收;
    3.非静态内部类和匿名内部类会持有activity引用,开发时尽量不要在activity里面写非静态内部类和匿名内部类,建议单独写个文件;
    4.单例模式不需要使用activity的上下文,单例模式里面如有需要用到activity里面的控件等等,尽可能的去用弱引用去接收;
    5.handler.postDelayed()问题,如果开启的线程需要传入参数,用弱引接收可解决问题,handler记得清除removeCallbacksAndMessages(null),Handler的handleMessage方法中(或者是run方法)处理消息,如果这个是一个延时消息,会一直保存在主线程的消息队列里,并且会影响系统对Activity的回收,造成内存泄露;
  • 尽量使用IntentService,而不是Service;

你可能感兴趣的:(Android,性能优化)