Android进阶——性能优化之Android Monitor、TraceView、Allocation Tracking、Lint的使用

前言

一个好的性能优化,可以让你的软件运行速度上比别人快,出现的卡顿现象少,而且一个好的性能软件,会在系统内存中生存的更久。性能优化最主要的就是对Java内存的管理,即堆内存中的管理,对于Java内存分配的讲解,详细可见我的博客文章

概念介绍

内存泄漏和内存溢出的区别

  • 内存泄漏:指程序分配出去的内存不再使用,无法进行回收
  • 内存溢出:指程序在申请内存时,没有足够的空间供其使用

成员变量和局部变量内存分配

  • 成员变量中的引用和引用对象存在堆中
  • 局部变量中的引用存在栈中,引用对象存在堆中

工具的说明

  • Android Monitor:用来查看当前设备堆内存的情况,可以通过该工具找到内存泄漏的原因
  • Android Monitor之Memory Usage:经过手动的GC之后,可以查看当前Activity的数量,来判断是否泄漏
  • TraceView:查看某个方法的CPU执行时间和方法运行时间
  • Allocation Tracking:查看内存的饼状图
  • Lint:系统自带的检测工具,可以检测内存泄漏的疑点和书写规范等问题

Android Monitor

我们通过一个单例模式的例子产生的内存泄漏来使用Android Monitor

/**
 * 单例模式
 */
public class CommUtil {
    private static CommUtil instance;
    private Context context;
    
    private CommUtil(Context context) {
        this.context=context;
    }
    public static CommUtil getInstance(Context context){
        if(null==instance){
            instance=new CommUtil(context);
        }
        return instance;
    }
}

/**
 * 使用单例
 */
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        CommUtil instance = CommUtil.getInstance(this);
    }
}

接着我们旋转三次屏幕,让Activity不断调用onCreate方法,从而导致CommUtil不断被实例化,在Android Monitor的内存监测中也能看得出来内存在增加

这里写图片描述

1、打开Android Monitor

这里写图片描述

2、点击Dump java Heap让其产生一份快照

这里写图片描述

3、找到MainActivity类,查看其实例情况

这里写图片描述

我们可以看到MainActvity有三个实例对象被引用

  • 0号位属于内存泄漏的实例
  • 1号位属于垃圾内存,会被GC回收
  • 2号位属于当前界面引用的实例

这里需要注意,旋转屏幕3次以上都只会有2个MainActivity。当GC回收的时候会将第0个和最后一个留着,其他的都会被回收

4、点击对象实例,找到引用的对象

这里写图片描述

5、解决方法

/**
 * 使用单例
 */
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        CommUtil instance = CommUtil.getInstance(getApplicationContext());
    }
}

这个时候,我们再按照前面的方法查看MainActivity的实例

这里写图片描述

Android Monitor之Memory Usage

正常的情况下,应用程序按返回键退出程序,并且经过多次手动GC,理论上所有的Activity都会被回收,我们可以通过Memory Usage的信息,查看退出程序后的Activity和View是否依然存在内存中,从此可以判断是否发生内存泄漏。我们主要是通过下面的入口,查看到最后程序退出并经过GC后的剩余情况,这里很明显可以看到内存泄漏了Activity和View,正常的值应该是0

这里写图片描述

TraceView

我们通过计算斐波拉契列数的例子,学习TraceView的使用

//调用斐波拉契列数
computeFibonacci(40);

public int computeFibonacci(int positionInFibSequence) {
    if (positionInFibSequence <= 2) {
        return 1;
    } else {
        return computeFibonacci(positionInFibSequence - 1)
                + computeFibonacci(positionInFibSequence - 2);
    }
}

1、打开Android Device Monitor (DDMS),按步骤启动TraceView

这里写图片描述

2、开始start Method Profiling之后,在主程序中执行斐波拉契列数,等待大概5秒就stop Method Profiling,系统会生成一份分析文件

这里写图片描述

3、鼠标移动到某一处黑色的地方

这里写图片描述

4、滑动鼠轮进行放大

这里写图片描述

5、产看详细信息面板

这里写图片描述

列名作用
Name该进程中所调用的函数名称
Incl Cpu Time函数占用的CPU时间,包含内部调用其它函数的CPU时间
Excl Cpu Time函数占用的CPU时间,但不包含内部调用其它函数所占用的CPU时间
Incl Real Time函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间
Excl Real Time函数运行的真实时间(以毫秒为单位),不包含调用其它函数所占用的真实时间
Calls+Recur Calls/Total函数被调用次数以及递归调用占总调用次数的百分比
Cpu Time/Call函数调用CPU时间与调用次数的比(该函数平均执行时间)
Real Time/Call同CPU Time/Call类似,只不过统计单位换成了真实时间

6、解决方法

//将递归换为for循环 同时使用缓存 先将结果缓存起来
public int computeFibonacci(int positionInFibSequence) {
    int prev=0;
    int current=1;
    int values;
    for(int i=0;i<positionInFibSequence;i++){
            values=prev+current;
            prev=current;
            current=values;
    }
    return current;
}

Allocation Tracking

1、打开Android Monitor,找到下面按钮,点击start Allocation Tracking,然后执行我们的程序进行分配内存,最后stop Allocation Tracking,可以看到图片上有一段矩形,就是我们追踪的内存分配部分,等一会会生成一份分析报告

这里写图片描述

2、在详细报告中打开图形图,可以看到每一层的内存分布情况,后面的内存分配都可以在图形中结合右边介绍找到

这里写图片描述

Lint

1、采用Lint工具系统会检测出一些简单的内存泄漏,或者是书写规范等等问题,使用Analyze里面的Inspect Code

这里写图片描述

2、选择整个目录结构即可得到分析报告

这里写图片描述

常见的内存泄漏例子

1、静态变量引起的内存泄露情况

当调用getInstance时,如果传入的context是Activity的context,只要这个单例没有被释放,那么这个Activity也不会被释放,一直到进程退出才会释放。解决方法就是将传入的context设置为getApplicationContext()即可

public class CommUtil {
    private static CommUtil instance;
    private Context context;

    private CommUtil(Context context) {
        this.context = context;
    }

    public static CommUtil getInstance(Context mcontext) {
        if (instance == null) {
            instance = new CommUtil(mcontext);
        }
        return instance;
    }
}

2、非静态内部类引起的内存泄露情况

由于非静态内部类对外部类持有应用,且非静态内部类的生命周期和外部类生命周期不一样,就会导致内存泄漏。解决方法就是将非静态内部类设置为静态内部类即可

3、注册的监听未移除引起的内存泄露情况

最常见的是 registerReceiver()、订阅-发布模式

4、资源未关闭引起的内存泄露情况

BroadCastReceiver、Cursor、Bitmap、IO流、自定义属性attribute、attr.recycle()的回收

5、无限循环动画引起的内存泄露情况

没有在onDestroy中停止动画,否则Activity就会变成泄露对象

处理过的内存泄露

1、Handler和Callback内存泄露

在Activity内部创建Handler,由于Activity的生命周期和Handler不一样,所以Handler会内存泄露,解决方法就是将Handler设置为静态类,有时Handler里面会存储Callback等变量,请务必将这些变量设置为弱引用进行存储

2、DCL(双检查锁机制)

DCL的单例需要对单例变量进行volatile修饰,否则JVM会存在指令的重排序优化,导致内存泄露

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

许英俊潇洒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值