关于性能监控和测试是安卓开发中技术进阶的重要内容,对于基建开发更是必备技能。所谓工欲善其事必先利其器,首先应该掌握安卓官方的提供的各种工具的使用,进一步的,通过探究其原理来开发我们自己的性能检测工具。

测试指标和测试方法

指标 工具或方法 备注
启动时间 adb am -
内存占用 Memory Profiler
卡顿分析 SysTrace,Looper监听,dumpsys
UI布局 LayoutInspector
ANR ANR-WatchDog
网速检测
耗电情况 Battery Historian

另外也有一些线上/线下的性能检测工具:

  • AndroidGodEye

UI性能

当一个xml布局首次加载到activity/fragment中时,它需要经历:

#mermaid-svg-q2RX7GdbxAgT1zoC .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .label text{fill:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .node rect,#mermaid-svg-q2RX7GdbxAgT1zoC .node circle,#mermaid-svg-q2RX7GdbxAgT1zoC .node ellipse,#mermaid-svg-q2RX7GdbxAgT1zoC .node polygon,#mermaid-svg-q2RX7GdbxAgT1zoC .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-q2RX7GdbxAgT1zoC .node .label{text-align:center;fill:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .node.clickable{cursor:pointer}#mermaid-svg-q2RX7GdbxAgT1zoC .arrowheadPath{fill:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-q2RX7GdbxAgT1zoC .flowchart-link{stroke:#333;fill:none}#mermaid-svg-q2RX7GdbxAgT1zoC .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-q2RX7GdbxAgT1zoC .edgeLabel rect{opacity:0.9}#mermaid-svg-q2RX7GdbxAgT1zoC .edgeLabel span{color:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-q2RX7GdbxAgT1zoC .cluster text{fill:#333}#mermaid-svg-q2RX7GdbxAgT1zoC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-q2RX7GdbxAgT1zoC .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-q2RX7GdbxAgT1zoC text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-q2RX7GdbxAgT1zoC .actor-line{stroke:grey}#mermaid-svg-q2RX7GdbxAgT1zoC .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-q2RX7GdbxAgT1zoC #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .sequenceNumber{fill:#fff}#mermaid-svg-q2RX7GdbxAgT1zoC #sequencenumber{fill:#333}#mermaid-svg-q2RX7GdbxAgT1zoC #crosshead path{fill:#333;stroke:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .messageText{fill:#333;stroke:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-q2RX7GdbxAgT1zoC .labelText,#mermaid-svg-q2RX7GdbxAgT1zoC .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-q2RX7GdbxAgT1zoC .loopText,#mermaid-svg-q2RX7GdbxAgT1zoC .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-q2RX7GdbxAgT1zoC .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-q2RX7GdbxAgT1zoC .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-q2RX7GdbxAgT1zoC .noteText,#mermaid-svg-q2RX7GdbxAgT1zoC .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-q2RX7GdbxAgT1zoC .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-q2RX7GdbxAgT1zoC .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-q2RX7GdbxAgT1zoC .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-q2RX7GdbxAgT1zoC .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-q2RX7GdbxAgT1zoC .section{stroke:none;opacity:0.2}#mermaid-svg-q2RX7GdbxAgT1zoC .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-q2RX7GdbxAgT1zoC .section2{fill:#fff400}#mermaid-svg-q2RX7GdbxAgT1zoC .section1,#mermaid-svg-q2RX7GdbxAgT1zoC .section3{fill:#fff;opacity:0.2}#mermaid-svg-q2RX7GdbxAgT1zoC .sectionTitle0{fill:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .sectionTitle1{fill:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .sectionTitle2{fill:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .sectionTitle3{fill:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-q2RX7GdbxAgT1zoC .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-q2RX7GdbxAgT1zoC .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-q2RX7GdbxAgT1zoC .grid path{stroke-width:0}#mermaid-svg-q2RX7GdbxAgT1zoC .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-q2RX7GdbxAgT1zoC .task{stroke-width:2}#mermaid-svg-q2RX7GdbxAgT1zoC .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-q2RX7GdbxAgT1zoC .taskText:not([font-size]){font-size:11px}#mermaid-svg-q2RX7GdbxAgT1zoC .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-q2RX7GdbxAgT1zoC .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-q2RX7GdbxAgT1zoC .task.clickable{cursor:pointer}#mermaid-svg-q2RX7GdbxAgT1zoC .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-q2RX7GdbxAgT1zoC .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-q2RX7GdbxAgT1zoC .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-q2RX7GdbxAgT1zoC .taskText0,#mermaid-svg-q2RX7GdbxAgT1zoC .taskText1,#mermaid-svg-q2RX7GdbxAgT1zoC .taskText2,#mermaid-svg-q2RX7GdbxAgT1zoC .taskText3{fill:#fff}#mermaid-svg-q2RX7GdbxAgT1zoC .task0,#mermaid-svg-q2RX7GdbxAgT1zoC .task1,#mermaid-svg-q2RX7GdbxAgT1zoC .task2,#mermaid-svg-q2RX7GdbxAgT1zoC .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-q2RX7GdbxAgT1zoC .taskTextOutside0,#mermaid-svg-q2RX7GdbxAgT1zoC .taskTextOutside2{fill:#000}#mermaid-svg-q2RX7GdbxAgT1zoC .taskTextOutside1,#mermaid-svg-q2RX7GdbxAgT1zoC .taskTextOutside3{fill:#000}#mermaid-svg-q2RX7GdbxAgT1zoC .active0,#mermaid-svg-q2RX7GdbxAgT1zoC .active1,#mermaid-svg-q2RX7GdbxAgT1zoC .active2,#mermaid-svg-q2RX7GdbxAgT1zoC .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-q2RX7GdbxAgT1zoC .activeText0,#mermaid-svg-q2RX7GdbxAgT1zoC .activeText1,#mermaid-svg-q2RX7GdbxAgT1zoC .activeText2,#mermaid-svg-q2RX7GdbxAgT1zoC .activeText3{fill:#000 !important}#mermaid-svg-q2RX7GdbxAgT1zoC .done0,#mermaid-svg-q2RX7GdbxAgT1zoC .done1,#mermaid-svg-q2RX7GdbxAgT1zoC .done2,#mermaid-svg-q2RX7GdbxAgT1zoC .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-q2RX7GdbxAgT1zoC .doneText0,#mermaid-svg-q2RX7GdbxAgT1zoC .doneText1,#mermaid-svg-q2RX7GdbxAgT1zoC .doneText2,#mermaid-svg-q2RX7GdbxAgT1zoC .doneText3{fill:#000 !important}#mermaid-svg-q2RX7GdbxAgT1zoC .crit0,#mermaid-svg-q2RX7GdbxAgT1zoC .crit1,#mermaid-svg-q2RX7GdbxAgT1zoC .crit2,#mermaid-svg-q2RX7GdbxAgT1zoC .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-q2RX7GdbxAgT1zoC .activeCrit0,#mermaid-svg-q2RX7GdbxAgT1zoC .activeCrit1,#mermaid-svg-q2RX7GdbxAgT1zoC .activeCrit2,#mermaid-svg-q2RX7GdbxAgT1zoC .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-q2RX7GdbxAgT1zoC .doneCrit0,#mermaid-svg-q2RX7GdbxAgT1zoC .doneCrit1,#mermaid-svg-q2RX7GdbxAgT1zoC .doneCrit2,#mermaid-svg-q2RX7GdbxAgT1zoC .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-q2RX7GdbxAgT1zoC .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-q2RX7GdbxAgT1zoC .milestoneText{font-style:italic}#mermaid-svg-q2RX7GdbxAgT1zoC .doneCritText0,#mermaid-svg-q2RX7GdbxAgT1zoC .doneCritText1,#mermaid-svg-q2RX7GdbxAgT1zoC .doneCritText2,#mermaid-svg-q2RX7GdbxAgT1zoC .doneCritText3{fill:#000 !important}#mermaid-svg-q2RX7GdbxAgT1zoC .activeCritText0,#mermaid-svg-q2RX7GdbxAgT1zoC .activeCritText1,#mermaid-svg-q2RX7GdbxAgT1zoC .activeCritText2,#mermaid-svg-q2RX7GdbxAgT1zoC .activeCritText3{fill:#000 !important}#mermaid-svg-q2RX7GdbxAgT1zoC .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-q2RX7GdbxAgT1zoC g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-q2RX7GdbxAgT1zoC g.classGroup text .title{font-weight:bolder}#mermaid-svg-q2RX7GdbxAgT1zoC g.clickable{cursor:pointer}#mermaid-svg-q2RX7GdbxAgT1zoC g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-q2RX7GdbxAgT1zoC g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-q2RX7GdbxAgT1zoC .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-q2RX7GdbxAgT1zoC .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-q2RX7GdbxAgT1zoC .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-q2RX7GdbxAgT1zoC .dashed-line{stroke-dasharray:3}#mermaid-svg-q2RX7GdbxAgT1zoC #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-q2RX7GdbxAgT1zoC #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-q2RX7GdbxAgT1zoC #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-q2RX7GdbxAgT1zoC #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-q2RX7GdbxAgT1zoC #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-q2RX7GdbxAgT1zoC #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-q2RX7GdbxAgT1zoC #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-q2RX7GdbxAgT1zoC #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-q2RX7GdbxAgT1zoC .commit-id,#mermaid-svg-q2RX7GdbxAgT1zoC .commit-msg,#mermaid-svg-q2RX7GdbxAgT1zoC .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-q2RX7GdbxAgT1zoC .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-q2RX7GdbxAgT1zoC .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-q2RX7GdbxAgT1zoC g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-q2RX7GdbxAgT1zoC g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-q2RX7GdbxAgT1zoC g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-q2RX7GdbxAgT1zoC g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-q2RX7GdbxAgT1zoC g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-q2RX7GdbxAgT1zoC g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-q2RX7GdbxAgT1zoC .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-q2RX7GdbxAgT1zoC .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-q2RX7GdbxAgT1zoC .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-q2RX7GdbxAgT1zoC .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-q2RX7GdbxAgT1zoC .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-q2RX7GdbxAgT1zoC .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-q2RX7GdbxAgT1zoC .edgeLabel text{fill:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-q2RX7GdbxAgT1zoC .node circle.state-start{fill:black;stroke:black}#mermaid-svg-q2RX7GdbxAgT1zoC .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-q2RX7GdbxAgT1zoC #statediagram-barbEnd{fill:#9370db}#mermaid-svg-q2RX7GdbxAgT1zoC .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-q2RX7GdbxAgT1zoC .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-q2RX7GdbxAgT1zoC .statediagram-state .divider{stroke:#9370db}#mermaid-svg-q2RX7GdbxAgT1zoC .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-q2RX7GdbxAgT1zoC .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-q2RX7GdbxAgT1zoC .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-q2RX7GdbxAgT1zoC .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-q2RX7GdbxAgT1zoC .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-q2RX7GdbxAgT1zoC .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-q2RX7GdbxAgT1zoC .note-edge{stroke-dasharray:5}#mermaid-svg-q2RX7GdbxAgT1zoC .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-q2RX7GdbxAgT1zoC .error-icon{fill:#522}#mermaid-svg-q2RX7GdbxAgT1zoC .error-text{fill:#522;stroke:#522}#mermaid-svg-q2RX7GdbxAgT1zoC .edge-thickness-normal{stroke-width:2px}#mermaid-svg-q2RX7GdbxAgT1zoC .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-q2RX7GdbxAgT1zoC .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-q2RX7GdbxAgT1zoC .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-q2RX7GdbxAgT1zoC .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-q2RX7GdbxAgT1zoC .marker{fill:#333}#mermaid-svg-q2RX7GdbxAgT1zoC .marker.cross{stroke:#333}:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}#mermaid-svg-q2RX7GdbxAgT1zoC {color: rgba(0, 0, 0, 0.75);font: ;}

IO读取
xml解析
反射生成View对象
绘制渲染

由于class对象的缓存机制,性能瓶颈一般出现在首次加载的时候。我们应当通过重构xml的手段尽量减少xml布局的嵌套深度【1】,但是这种手段的优化效果是非常有限的,此时如果需要彻底优化,就要考虑使用AsyncLayoutInflator【2】来异步加载布局,真正彻底解决主线程卡顿问题。

使用异步加载布局并没有真正解决布局加载耗时的问题,我们回顾这个卡顿的根源还是在与这个xml加载的机制必然涉及到IO和反射,如果使用纯代码编写布局就没有这个问题。目前就有极客想到利用注解处理器将xml直接在编译器转为Java代码,X2C【3】就是这样开源项目。不过这也不是一个完美的解决方案,比如当xml属性没有对应的setXXX方法时(ProgressBar、SeekBar等)生成的Java代码就会有问题。

网络优化

  • 优化DNS:使用HttpDNS
  • 连接复用:开启keep-alive,默认开启
  • 数据压缩:开启gzip压缩
  • 资源分级:根据网络状况下载不同清晰度图片

网络请求策略:

  1. 无网络则不发起网络请求;
  2. 优先使用WiFi;
  3. 统计日志等非重要非重要网络请求等到WiFi环境再发送;

电量优化

  • 定位服务、消息推送服务等共性服务复用;
  • 前台不申请WakeLock或使用带超时的方法申请,使用FLAG_KEEP_SCREEN_ON保持屏幕常亮;

SysTrace

sysTrace是老版的用于系统跟踪的CLI工具,基于python 2.7实现,。其生成的报告是个HTML文件,用浏览器打开其界面大致如下:

Systrace HTML报告截图,其中显示了与某个应用之间5秒的交互情况

所谓“系统跟踪”就是抓取和记录某个时间段内设备的活动情况(包括但不限于CPU使用率、线程活动、磁盘活动等),并生成跟踪文件(Perfetto或Systrace格式,根据具体的工具),用这些文件可以生成系统报告(html或其他展示形式);

生成报告

CLI命令生成

python systrace.py -a com.example.myapp -b 16384 \
-o my_systrace_report.html sched freq idle am wm gfx view binder_driver hal \
dalvik camera input res
  • -a:指定进程名称,通常就是包名;
  • -b:指定缓冲区大小;
  • -o:指定输出报告的文件路径;
  • -t:跟踪设备活动N秒。如果您未指定此选项,systrace 会提示您在命令行中按 Enter 键结束跟踪;
  • -e:指定设备序列号;
  • categories:指定要跟踪的信息,包括schedfreqidleamwmgfx渲染图形的系统进程、viewbinder_driverhaldalvikcamerainputres等,可通过systrace -l查看已连接设备可用的服务列表;
  1. 经实际测试systrace.py仅支持python 2.7无法在python 3.x中使用,这点需要注意;
  2. 出了实时捕获还支持通过指定trace文件生成HTML报告;

android.os.Debug生成

可用于生成.trace文件,用于在SystraceCPU Profiler中导入分析。使用示例如下:

public class XXXActivity extends BaseActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {// 生成的.trace文件位于:sdcard/Android/data/package_name/files/XXXActivity_1111.traceDebug.startMethodTracing("XXXActivity_"+ System.currentTimeMillis());...}@Overrideprotected void onDestroy() {super.onDestroy();Debug.stopMethodTracing();}
}

然后用adb pull到开发机器导入到Profiler或则直接用AS的Device File Explorer双击打开:

需要注意的是:

  1. 启用剖析功能后,应用的运行速度会减慢;
  2. 同名trace文件会相互覆盖,因此文件名要唯一;

读懂报告

从Systrace报告的官网介绍中可以了解到我们通常关注Frames中为黄色的帧,通过点击对应的黄色圆点可以查看调用栈,从而定位问题点。

自定义事件

android.os.Trace作为SDK提供的辅助API工具,可以帮助我们实现自定义事件的统计。根据官网介绍,使用示例如下:

  • 演示Java层新增定义自定义事件
list_item.setOnItemClickListener((parent, view, position, id) -> {// 追踪列表项点击事件的耗时情况Trace.beginSection("OnListItemClick");try {......} finally {// 为确保Trace.endSection执行,建议放到finally块中Trace.endSection();}
});
  • 演示native层新增定义自定义事件
//1. 定义在游戏内捕获自定义事件所用 ATrace 函数的函数指针
#include <android/trace.h>
#include <dlfcn.h>void *(*ATrace_beginSection) (const char* sectionName);
void *(*ATrace_endSection) (void);typedef void *(*fp_ATrace_beginSection) (const char* sectionName);
typedef void *(*fp_ATrace_endSection) (void);//2. 在运行时加载 ATrace 符号,如以下代码段所示。通常在对象构造函数中执行此过程
// Retrieve a handle to libandroid.
void *lib = dlopen("libandroid.so", RTLD_NOW || RTLD_LOCAL);// Access the native tracing functions.
if (lib != NULL) {// Use dlsym() to prevent crashes on devices running Android 5.1// (API level 22) or lower.ATrace_beginSection = reinterpret_cast<fp_ATrace_beginSection>(dlsym(lib, "ATrace_beginSection"));ATrace_endSEction = reinterpret_cast<fp_ATrace_endSection>(dlsym(lib, "ATrace_endSection"));
}//3. 在自定义事件的开头和结尾分别调用 ATrace_beginSection() 和 ATrace_endSection()
#include <android/trace.h>char *customEventName = new char[32];
sprintf(customEventName, "User tapped %s button", buttonName);ATrace_beginSection(customEventName);
// Your app or game's response to the button being pressed.
ATrace_endSection();

然后运行APP,点击操作列表项,同事启用AS的CPU Profiler对过程进行录制,结束录制后将在分析报告中看到对应的自定义事件:

System Tracing

一款运行在Android 9(API 级别 28)或更高版本的终端工具,其功能与systrace类似,用于直接在手机上获取跟踪数据,生成.perfetto-trace[新系统,用Perfetto打开]/.ctrace[老系统,用Systrace生成报告]文件。
具体用法参考:捕获设备上的系统跟踪记录

TraceView

一款搭配Android Device Monitor的图形化日志查看工具,可以打开Systrace生成的.trace。截图如下:

LayoutInspector

AS自带的布局检测工具,可以查看当前运行的activity布局的详细树状结构。

Profiler

CPU Profiler

AS CPU Profiler官方使用说明

Memory Profiler

未完待续…

Network Profiler

未完待续…

Energy Profiler

未完待续…

代码检测

Looper监听

为什么主线程的代码最终都会在Looper.loop()中执行?

当APP启动后,其实主线程ActivityThread多数时间是阻塞在loop的queue.next()中的nativePollOnce()方法里,得益于Linux pipe/epoll机制,主线不会像我们日常多线程开发时使用独占锁导致的线程阻塞那样占用CPU资源,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。
主线程休眠后被谁唤醒呢?答案是同个进程中的其他线程发过来的消息。比如:当我们启动一个activity时,实际是执行了如下的IPC通讯过程:

而线程4(ApplcaitionThread)接收到消息后,便通过ActivityThread.H实例向主线程发送消息,进而在主线程(ActivityThread)中触发ActivityThread.H.handleMessage()方法,从执行AMS的对应三大组件生命周期方法。

至此可以已经了解,我们编写的代码逻辑,最终都被转化为MessageQueue中一个个的Message,并在msg.target.dispatchMessage(msg)中被执行。所以要检测卡顿就很明显了,只要根据此方法执行的耗时时长就可以判断是否发生了卡顿。而正好,Looper#mLogging在dispatchMessage执行前后打印了日志消息,这便允许我们通过检测日志前后打印的内容来判断方法执行完毕与否。

  1. 上图中线程1、2、3、4是指Binder线程池中的线程,因为Binder方法都是运行在这个线程池中的,所以尽管ApplcaitionThreadAcitvityThread运行都运行在APP进程,却是不同的线程,所以还是需要借助Handler进行通讯;
  2. Handler是同一进程内不同线程间通讯的桥梁,这一点虽然是常识,还是要重点强调下。
  3. Looper.loop()方法中,同样执行于dispatchMessage前后的还有Trace#traceBegin/Trace#traceEnd方法,这也正是Systrace这个工具的实现原理。

代码实现示例

private void check() {Looper.getMainLooper().setMessageLogging(new Printer() {private final String START = ">>>>> Dispatching to";private final String END = "<<<<< Finished to";@Overridepublic void println(String s) {if (s.startsWith(START)) {mCheckTask.start();} else if (s.startsWith(END)) {mCheckTask.end();}}});
}private class CheckTask {private HandlerThread mHandlerThread = new HandlerThread("卡顿检测");private Handler mHandler;private final int THREAD_HOLD = 1000;public CheckTask() {mHandlerThread.start();mHandler = new Handler(mHandlerThread.getLooper());}private Runnable mRunnable = new Runnable() {@Overridepublic void run() {log();}};public void start() {mHandler.postDelayed(mRunnable, THREAD_HOLD);}public void end() {mHandler.removeCallbacks(mRunnable);}
}/**
* 输出当前异常或及错误堆栈信息。
*/
private void log() {StringBuilder sb = new StringBuilder();StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();for (StackTraceElement s : stackTrace) {sb.append(s + "\n");}Log.w(TAG, sb.toString());
}

相关开源库

  • AndroidPerformanceMonitor
  • BlockCanaryEx

Choreographer

Android SDK提供的工具类,用于实时获取当前屏幕绘制的FPS数据。

相关开源库

  • Takt
  • TinyDancer

其实谷歌官网对于UI绘制帧率的检测也有相关方法的介绍:测试界面性能,使用的是adb dumpsys工具,感兴趣的可以看看。

LayoutInflaterCompat.setFactory2

LayoutInflater提供的hook入口,该方法可用于统计每个View的加载耗时。

Debug

android.os.Debug这个工具类提供了很多调试和性能检测相关的功能,比如:

  • startMethodTracing/stopMethodTracing
    在用户方法前后分别调用这两个方法,用于生成.trace文件。

  • dumpHprofData(String fileName)
    保存内存快照到本地文件,此文件可能体积非常大,不可在生产环境使用此方法。

Android性能测试手段和工具相关推荐

  1. Android 性能测试——Memory Monitor 工具

    Android 性能测试--Memory Monitor 工具 Memory Monitor能做什么? 实时查看App的内存分配情况 快速判断App是否由于GC操作造成卡顿 快速判断App的Crash ...

  2. 阿里巴巴Android性能测试工具mobileperf正式开源 (天猫精灵Android性能测试-线下篇)

    官方首发 阿里巴巴技术质量:Android 性能测试工具 mobileperf 开源 (天猫精灵 Android 性能测试-线下篇) Android性能测试工具mobileperf github 如果 ...

  3. Emmagee—开源Android性能测试工具

    下载:https://github.com/NetEase/Emmagee/releases/download/V2.5/Emmagee.apk 1.Emmagee--Android性能测试工具 Em ...

  4. Android 性能测试小工具 Emmagee

    Emmagee 是一个性能测试小工具 用来监控指定被测应用在使用过程中占用机器的CPU, 内存,流量资源的性能小工具 Emmagee 介绍 Emmagee是网易杭州研究院QA团队开发的一个简单易上手的 ...

  5. android 性能测试工具下载,Android性能测试 一些适用于Android Studio的代码审查和性能测试工具...

    导言: Android应用在CPU占用,内存消耗方面的性能指标是影响产品质量的重要因素,由于QQ管家,360手机助手等应用都提供直观的内存消耗,流量监控功能,致使用户比以往更加关注软件的性能,并以此进 ...

  6. Android软件测试Monkey测试工具

    前言: 最近开始研究Android自动化测试方法,对其中的一些工具.方法和框架做了一些简单的整理,其中包括android测试框架.CTS.Monkey.Monkeyrunner.benchmark.其 ...

  7. Android 性能测试初探(一)

    Android 性能测试,跟 pc 性能测试一样分为客户端及服务器,但在客户端上的性能测试分为 2 类: 一类为 rom 版本的性能测试 一类为应用的性能测试 对于应用性能测试,包括很多测试项,如启动 ...

  8. Android性能测试-内存

    前言: 近阶段都在探索android性能测试方面的东西,其中一个很重要的指标就是内存.对于内存,主要是一些gc是不是及时,或者说一些引用有没有及时释放,有没有导致oom或者内存持续增加导致卡顿,有没有 ...

  9. Android系统性能调优工具介绍

    经作者授权,发表Tieto某青年牛的一篇<程序员>大作. Android系统性能调优工具介绍 在软件开发过程中,想必很多读者都遇到过系统性能问题.而解决系统性能问题的几个主要步骤是: 测评 ...

最新文章

  1. Ext2.0框架的Grid使用介绍(转)
  2. 熟悉java环境实验报告_2018-2019-2 20175324实验二面向对象程序设计《Java开发环境的熟悉》实验报告...
  3. 如何获取枚举字符串,值及遍历枚举(转)
  4. VBS基础篇 - Dictionary对象
  5. vue-router linkActiveClass问题
  6. java关键字值transient
  7. java初级开发一系列的工具安装配置
  8. Map.Entry的使用(转载)
  9. 水泥行业超低排放政策频发,企业如何完成超低排放改造?
  10. 关于固定总价合同的建设工程项目审计的几点思考
  11. HDU 6441Find Integer
  12. python循环语句if语句的题目_Python if语句 for语句 while循环的练习
  13. matlab画直线段,如果要在MATLAB中绘制上题中的直线段,要求 ,则对应的MATLAB语句为____________...
  14. Vue和React的区别到底是什么?
  15. Unity Shader 颜色混合
  16. 2021全国人工智能师资培训正式“开课”,百度飞桨助力高校教师提升AI“超能力”...
  17. Mysql学习之优化篇
  18. Java-斐波拉契数列
  19. 信息定义、信息熵、信息处理及计算机科学定义
  20. 《嵌入式 - STM32开发指南》手把手教你搭建STM32开发环境 [Windows版 - 2]

热门文章

  1. 彻彻底底删除卸载docker
  2. ML:基于葡萄牙银行机构营销活动数据集(年龄/职业等)利用Pipeline框架(两种类型特征并行处理)+多种模型预测(分层抽样+调参交叉验证评估+网格/随机搜索+推理)客户是否购买该银行的产品二分类案
  3. 软件架构--入门导读--欲穷千里目,更上一层楼
  4. forEach、map、for..of、for..in、for循环实现异步变同步的问题
  5. SIFT与SURF算法比较
  6. Eu.org免费域名已经通过了,并转到CloudFlare解析
  7. 华为荣耀v20是android10,华为良心?8款新老机型升级Android10?荣耀V20也在列
  8. vue 使用Export2Excel导出Excel表格
  9. 2020年江苏中考数学能用计算机吗,重磅!江苏中考改革来了!改什么,看这里...
  10. 数学建模:异常检测算法