Android 性能优化 之 TraceView工具的使用

Traceview简介
TraceView 是 Android 平台特有的数据采集和分析工具,它主要用于分析 Android 中应用程序的 hotspot。TraceView 本身只是一个数据分析工具,而数据的采集则需要使用 Android SDK 中的 Debug 类或者利用 DDMS 工具。二者的用法如下:

开发者在一些关键代码段开始前调用 Android SDK 中 Debug 类的 startMethodTracing 函数,并在关键代码段结束前调用 stopMethodTracing 函数。这两个函数运行过程中将采集运行时间内该应用所有线程(注意,只能是 Java 线程)的函数执行情况,并将采集数据保存到 /mnt/sdcard/ 下的一个文件中。开发者然后需要利用 SDK 中的 TraceView 工具来分析这些数据。
借助 Android SDK 中的 DDMS 工具。DDMS 可采集系统中某个正在运行的进程的函数调用信息。对开发者而言,此方法适用于没有目标应用源代码的情况。
DDMS 中 TraceView 使用示意图如下,调试人员可以通过选择 Devices 中的这里写图片描述应用后点击 按钮 Start Method Profiling(开启方法分析)和点击这里写图片描述 Stop Method Profiling(停止方法分析)
这里写图片描述
我这边用一个简单的demo学习一下这个工具的使用。
代码如下所示:

package com.mingrisoft;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends Activity implements Runnable {
    private ImageView iv; // 声明一个显示广告图片的ImageView对象
    private Handler handler; // 声明一个Handler对象
    private List<Integer> list1 = new ArrayList<Integer>();
    private int[] path = new int[] { R.drawable.img01, R.drawable.img02,
            R.drawable.img03, R.drawable.img04, R.drawable.img05,
            R.drawable.img06 }; // 保存广告图片的数组
    private String[] title = new String[] { "编程词典系列产品", "高效开发", "快乐分享", "用户人群",
            "快速学习", "全方位查询" }; // 保存显示标题的数组
public void dynatest()
    {
        for(int i = 0; i < 500000; i++) {  
            //Log.i(TAG, "======== ");
            list1.add(i);  
        }  
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        iv = (ImageView) findViewById(R.id.imageView1); // 获取显示广告图片的ImageView
        Thread t = new Thread(this); // 创建新线程
        t.start(); // 开启线程
        // 实例化一个Handler对象
        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // 更新UI
                TextView tv = (TextView) findViewById(R.id.textView1); // 获取TextView组件
                if (msg.what == 0x101) {
                    dynatest();
                    tv.setText(msg.getData().getString("title")); // 设置标题
                    iv.setImageResource(path[msg.arg1]); // 设置要显示的图片
                }
                super.handleMessage(msg);
            }

        };

    }

    @Override
    public void run() {
        int index = 0;
        while (!Thread.currentThread().isInterrupted()) {
            index = new Random().nextInt(path.length); // 产生一个随机数
            Message m = handler.obtainMessage(); // 获取一个Message
            m.arg1 = index; // 保存要显示广告图片的索引值
            Bundle bundle = new Bundle(); // 获取Bundle对象
            m.what = 0x101; // 设置消息标识
            bundle.putString("title", title[index]); // 保存标题
            m.setData(bundle); // 将Bundle对象保存到Message中
            handler.sendMessage(m); // 发送消息

            try {
                Thread.sleep(2000); // 线程休眠2秒钟
            } catch (InterruptedException e) {
                e.printStackTrace(); // 输出异常信息
            }

        }
    }
}

这个demo主要功能是每两秒随机显示一张广告图片。首先将三张图片资源引用ID保存到 path 这个数组中。

private int[] path = new int[] { R.drawable.img01, R.drawable.img02,
            R.drawable.img03, R.drawable.img04, R.drawable.img05,
            R.drawable.img06 }; // 保存广告图片的数组

MainActivity 重写 Runnable的 Run函数,每隔两秒钟随机发送一个图片ID给hander然后显示。

    TextView tv = (TextView) findViewById(R.id.textView1); // 获取TextView组件
                if (msg.what == 0x101) {
                //故意增加延迟
                    for(int i = 0; i < 10000; i++) {  
                        list1.add(i);  
                    }  
                    tv.setText(msg.getData().getString("title")); // 设置标题
                    iv.setImageResource(path[msg.arg1]); // 设置要显示的图片
                }

我们在显示广告图片中故意加一个循环用于延迟广告图片显示。
其UI划分为上下两个面板,即Timeline Panel(时间线面板)和Profile Panel(分析面板)。
这里写图片描述
左边Pane显示的是测试数据中所采集的线程信息。由图1-4可知,本次测试数据采集了main线程,两个Binder线程和其它系统辅助线程(例如GC线程等)的信息。
右边Pane所示为时间线,时间线上是每个线程测试时间段内所涉及的函数调用信息。这些信息包括函数名、函数执行时间等。由图可知,main线程对应行的的内容非常丰富,而其他线程在这段时间内干得工作则要少得多。
另外,开发者可以在时间线Pane中移动时间线纵轴。纵轴上边将显示当前时间点中某线程正在执行的函数信息。
同意颜色在实际轴越长说明该颜色代表的函数花费时间越长

Profile Panel 是 TraceView 的核心界面,其内涵非常丰富。它主要展示了某个线程(先在 Timeline Panel 中选择线程)中各个函数调用的情况,包括 CPU 使用时间、调用次数等信息。而这些信息正是查找 hotspot 的关键依据。所以,对开发者而言,一定要了解 Profile Panel 中各列的含义。下表列出了 Profile Panel 中比较重要的列名及其描述。
这里写图片描述
另外,每一个Time列还对应有一个用时间百分比来统计的列(如Incl Cpu Time列对应还有一个列名为Incl Cpu Time %的列,表示以时间百分比来统计的Incl Cpu Time)。
了解完Traceview的UI后,现在介绍如何利用Traceview来查找hotspot。
一般而言,hotspot包括两种类型的函数:


一类是调用次数不多,但每次调用却需要花费很长时间的函数。在示例代码中,它就是hotspot 1。
一类是那些自身占用时间不长,但调用却非常频繁的函数。在示例代码中,它就是hotspot 2。
在我们代码中handleMessage 函数属于 每次调用花费很长时间的函数。因为里面有个耗时的dynatest循环在那一直处理。
这里写图片描述
从上图中我们可以发现
标号 12 的 com.mingrisoft.MainActivity.dynatest这一行很特别。它的Incl Cpu Time % 是 32.1 % ,Excl Cpu Time 也是 32.1% ,并且 它的 Calls+Recur Calls/Total列显示其调用次数为1,即它仅仅被调用一次了。这个函数是应用程序实现的,所以极有可能是一个潜在的Hotspot。在这个函数里面做了很多耗时的操作。
比如标号 11 的 handleMessage 虽然 它的Incl Cpu Time % 是 40.2 %,但是它的Excl Cpu Time 是 0 % 。

说明dynatest可能是手机发烫、卡顿、高 CPU 占用率的原因所在。
我们现在把函数 dynatest() 注释掉,再看看结果:
这是注释前handleMessage 各个函数时间比

这是注释后的结果
说明去掉dynatest这个函数后。handleMessage 主要花费时间都是在 setImageResource 函数上。而且这个函数是系统函数。

第二种情况
就是虽然每次调用花费时间不是很多。但是调用次数很多的那种情况。
源码中函数dynatest() 注释掉。添加一下代码

public void handleMessage(Message msg) {
                // 更新UI
                TextView tv = (TextView) findViewById(R.id.textView1); // 获取TextView组件
                if (msg.what == 0x101) {
                    //dynatest() ;
                    for(int i = 0; i < 500000; i++) {  
                        //Log.i(TAG, "======== ");
                        list1.add(i);  
                    }  
                    tv.setText(msg.getData().getString("title")); // 设置标题
                    iv.setImageResource(path[msg.arg1]); // 设置要显示的图片
                }

然后运行TraceView得到下面的图:
这里写图片描述

这里写图片描述
发现这三个类的Incl Cpu Time 很高分别是55.6% 10.6% 和7.6%,但是你有发现他们每次的Incl Real Time大约分部是
这里写图片描述
1535 282 和 198.每次占用cpu时间很短。但是占用整个cpu运行周期的比例确实很高。这说明只有一种情况。这几个函数执行频率非常高。通过查看Call+Recur Calls/Total 发现情侣确实如我所料。
这里写图片描述
这里发现执行次数达到49947.而且函数大部分都是一次。这说明这几个函数运行次数太多。可以在实际开发过程中优化

猜你喜欢

转载自blog.csdn.net/lb5761311/article/details/53171571