欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 性能检测工具

程序员文章站 2022-04-07 12:36:54
...

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 监控的那栏会有一个闹钟似的的按钮,未启动应用时是灰色:
Android 性能检测工具
启动应用后,这个按钮会变亮,点击后开始追踪,相当于代码调用 startMethodTracing:
Android 性能检测工具
当要结束追踪时再次点击这个按钮,就会生成 trace 文件了。
生成 trace 后 Android Studio 自动加载的 traceview 图形如下:
Android 性能检测工具
从这个图可以大概了解一些方法的执行时间、次数以及调用关系,也可以搜索过滤特定的内容。

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

鼠标悬浮到黄色的矩形上,会显示对应方法的开始、结束时间,以及自己占用和调用其他方法占用的时间比例:
Android 性能检测工具

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 profilingAndroid 性能检测工具—>选择sample base profiling—>…—>stop mothod profilingAndroid 性能检测工具

Android 性能检测工具

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

TraceView界面

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

Android 性能检测工具

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 中比较重要的列名及其描述。

Android 性能检测工具

关键指标有三个:

  • 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大小排序:
Android 性能检测工具

按照Calls + Recur Calls/Total大小排序:
Android 性能检测工具

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

相关标签: 性能