Android 性能检测工具

trace文件

trace.txt是系统用于保存ANR Log的文件,通过这个文件可以找到系统检测到的ANR的应用。

ANR的定义

在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择“等待”而让程序继续运行,也可以选择“强制关闭”。所以一个流畅的合理的应用程序中不能出现anr,而让用户每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样系统不会显示ANR给用户。

ANR的类型

  • KeyDispatchTimeout(5 seconds) —-主要类型
    按键或触摸事件在特定时间内无响应
  • BroadcastTimeout(10 seconds)
    BroadcastReceiver在特定时间内无法处理完成
  • ServiceTimeout(20 seconds) —-小概率类型
    Service在特定的时间内无法处理完成

KeyDispatchTimeout超时原因

(1)当前的事件没有机会得到处理(即UI线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)
(2)当前的事件正在处理,但没有及时完成

如何避免KeyDispatchTimeout

(1)UI线程尽量只做跟UI相关的工作
(2)耗时的工作(比如数据库操作,I/O,连接网络或者别的有可能阻碍UI线程的操作)把它放入单独的线程处理
(3)尽量用Handler来处理UIthread和别的thread之间的交互

UI线程

首先要知道事件发生的线程,一般来说大多数可能是UI线程操作超时,那么UI线程都有哪些呢:
(1). Activity 生命周期
(2). View post 的runnable方法 、 handler(MainLooper) 的 handleMessage()
(3). Asycktask 的 onPreExecute(), onPostExecute() , onProgressUpdate()方法
(4). Broadcast 的onReceive()
(5). Service

其他原因造成的anr

(1). 线程死锁
(2). cpu 饥饿

如何去分析ANR

我们写一个主线程阻塞的demo:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        doANR();
    }

    private void doANR() {
        try {
            Thread.sleep(60000); //阻塞
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

出现Application Not Responding的提示后,系统会将日志LOG写到到data\anr\traces.txt文件,将手机上的traces.txt导出到电脑的d目录下:

adb pull /data/anr/traces.txt d:/

Log中定位ANR信息:

"main" prio=5 tid=1 Sleeping
  | group="main" sCount=1 dsCount=0 obj=0x7523f450 self=0x55a8df6b20
  | sysTid=10299 nice=-1 cgrp=top_visible sched=0/0 handle=0x7f97788fd0
  | state=S schedstat=( 958207562 23237861 227 ) utm=65 stm=30 core=0 HZ=100
  | stack=0x7fef1d1000-0x7fef1d3000 stackSize=8MB
  | held mutexes=
  at java.lang.Thread.sleep!(Native method)
  - sleeping on <0x04746b7d> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:1046)
  - locked <0x04746b7d> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:1000)
  at com.hx.tools.MainActivity.doANR(MainActivity.java:17) /**就是这里**/
  at com.hx.tools.MainActivity.onCreate(MainActivity.java:12)
  at android.app.Activity.performCreate(Activity.java:6367)
  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1110)
  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2404)
  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2511)
  at android.app.ActivityThread.access$900(ActivityThread.java:165)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1375)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:150)
  at android.app.ActivityThread.main(ActivityThread.java:5621)
  at java.lang.reflect.Method.invoke!(Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684)

其他关于死锁以及cpu饥饿的Log分析可以参考Android ANR 分析学习总结

TraceView

TraceView 是 Android SDK 中内置的一个工具,它可以加载 trace 文件,用图形的形式展示代码的执行时间、次数及调用栈,便于我们分析。
通过TraceView,可以得到以下两种数据:

  • 单次执行最耗时的方法
  • 执行次数最多的方法

trace 文件是 log 信息文件的一种,生成 trace 文件有三种方法:

  • 使用代码
  • 使用 Android Studio
  • 使用 DDMS

代码生成 trace文件

/**开始 trace,保存文件到 "/sdcard/mytrace.trace" */
Debug.startMethodTracing("mytrace"); 
... 
//结束
Debug.stopMethodTracing(); 

代码很简单,当你调用开始代码的时候,系统会生产 trace 文件,并且产生追踪数据,当你调用结束代码时,会将追踪数据写入到 trace 文件中,6.0以上系统请自己完成运行时权限。使用代码生成 trace 方式的好处是容易控制追踪的开始和结束,缺点就是步骤稍微多了一点。

本地的trace文件我们就可以使用SDK下的TraceView或DDMS来直接打开分析了。

Android Studio 生成 trace 文件

Android Studio 内置的 Android Monitor 可以很方便的生成 trace 文件到电脑。
在 CPU 监控的那栏会有一个闹钟似的的按钮,未启动应用时是灰色:
这里写图片描述
启动应用后,这个按钮会变亮,点击后开始追踪,相当于代码调用 startMethodTracing:
这里写图片描述
当要结束追踪时再次点击这个按钮,就会生成 trace 文件了。
生成 trace 后 Android Studio 自动加载的 traceview 图形如下:
这里写图片描述
从这个图可以大概了解一些方法的执行时间、次数以及调用关系,也可以搜索过滤特定的内容。

左上角可以切换不同的线程,这其实也是直接用 Android Studio 查看 trace 文件的缺点:无法直观地对比不同线程的执行时间

鼠标悬浮到黄色的矩形上,会显示对应方法的开始、结束时间,以及自己占用和调用其他方法占用的时间比例:
这里写图片描述

DDMS 生成 trace 文件

DDMS 即 Dalvik Debug Monitor Server ,是 Android 调试监控工具,它为我们提供了截图,查看 log,查看视图层级,查看内存使用等功能,可以说是如今 Android Studio 中内置的 Android Monitor 的前身。

从Android Studio中启动android Device Monitor: Tools -> Android -> Android Device Monitor.
选择你要调试的进程—>点击start mothod profiling这里写图片描述—>选择sample base profiling—>…—>stop mothod profiling这里写图片描述

这里写图片描述

开启方法分析后对应用的目标页面进行测试操作,测试完毕后停止方法分析,界面会跳转到 DDMS 的 trace 分析界面,下面会分析。操作最好不要超过5s,因为最好是进行小范围的性能测试。

TraceView界面

下面我的分析是针对DDMS抓取的trace文件。

这里写图片描述

TraceView 界面比较复杂,其 UI 划分为上下两个面板,即 Timeline Panel(时间线面板)和 Profile Panel(分析面板)。上图中的上半部分为 Timeline Panel(时间线面板),Timeline Panel 又可细分为左右两个 Pane:

  • 左边 Pane 显示的是测试数据中所采集的线程信息。由图可知,本次测试数据采集了 main 线程,监视线程和其它网络操作线程的信息。
  • 右边 Pane 所示为时间线,时间线上是每个线程测试时间段内所涉及的函数调用信息。这些信息包括函数名、函数执行时间等。由图可知,main 线程对应行的的内容非常丰富,而其他线程在这段时间内干得工作则要少得多。
  • 另外,开发者可以在时间线 Pane 中移动时间线纵轴。纵轴上边将显示当前时间点中某线程正在执行的函数信息。

上图中的下半部分为 Profile Panel(分析面板),Profile Panel 是 TraceView 的核心界面,其内容非常丰富。它主要展示了某个线程(先在 Timeline Panel 中选择线程)中各个函数调用的情况,包括 CPU 使用时间、调用次数等信息。而这些信息正是查找 hotspot 的关键依据。所以,对开发者而言,一定要了解 Profile Panel 中各列的含义。下表列出了 Profile Panel 中比较重要的列名及其描述。

这里写图片描述

关键指标有三个:

  • Cpu Time/Call :该方法平均占用 CPU 的时间
  • Real Time/Call :平均执行时间,包括切换、阻塞的时间,>= Cpu Time
  • Calls + Recur Calls/Total :调用、递归次数

Profile Panel界面下方表格中纵轴就是每个方法,包括了JDK的,Android SDK的,也有native方法的,当然最重要的就是app中你自己写的方法,有些Android系统的方法执行时间很长,那么有很大的可能就是你app中调用这些方法过多导致的。

每个方法前面都有一个数字,是全部方法按照Incl CPU Time 时间的排序序号。点一个方法后可以看到有两部分,一个是Parents,另一个是Children。

  • Parent表示调用这个方法的方法,可以叫做父方法
  • Children表示这个方法中调用的其他方法,可以叫做子方法

根据 TraceView 定位问题

定位问题时 TraceView 的使用方式:

  • 从上半部分查看哪些线程执行时间长?什么时候开始执行?与主线程交错时间?
  • 哪些方法的执行需要花费很长时间
    点击 TraceView 中的 Cpu Time/Call,按照占用 CPU 时间从高到低排序
  • 哪些方法调用次数非常频繁
    点击 TraceView 中的 Calls + Recur Calls/Total ,按照调用次数从高到底排序,排序后,然后逐个排查是否有项目代码或者依赖库代码,有的话点击查看详情,查看是这个方法还是调用的子方法的问题,进一步定位问题。

我这里写了一个循环打印的例子:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        doLoop();
    }

    private void doLoop() {
        for (int i = 0; i < 100000; i++) {
            showLog(i);
        }
    }

    private void showLog(int i) {
        Log.v("hx", "i="+i);
    }
}

按照Cpu Time/Call大小排序:
这里写图片描述

按照Calls + Recur Calls/Total大小排序:
这里写图片描述

都能很快的定位到出错函数。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值