Systrace是Android 4.1引入的基于ftrace的性能分析工具,通过在系统各个关键调用位置添加trace埋点 来分析系统调用耗时等问题
在右侧选择需要同时监听的进程
这个选项可以开启指定包名App中自定义Trace Label的Trace功能。也就是说,如果你在代码中使用了Trace.beginSection(“tag”); Trace.endSection; 默认情况下,你的这些代码是不会生效的,因此,这个选项一定要开启,开启之后,就能在生成的Systrace文件中看到自己在当前进程添加的自定义trace。
然后点击左上角右侧的按钮并进行相关配置
点击左上角按钮,出来右边选项框,上面配置文件名、持续时长、buffer大小、调试的应用程序等
具体如何配置我们会在之后细讲
然后点击ok就开始抓trace了,这时候你就可以在这段时间进行操作,以便相关进程能走到你需要测试的逻辑,抓到正确的trace
option(参数) | value(值) |
---|---|
-o < FILE > | output 指定trace数据文件的输出路径,如果不指定就是当前目录的trace.html |
-t N, –time=N | 执行时间,默认5s。绝对不要把时间设的太短导致你操作没完Trace就跑完了,分析数据就基本无效了 |
-b N, –buf-size=N< /td > | buffer大小(单位kB),用于限制trace总大小,默认无上限 |
-a < APP_NAME>,–app=< APP_NAME> | 这个选项可以开启指定包名App中自定义Trace Label的Trace功能。也就是说,如果你在代码中使用了Trace.beginSection(“tag”), Trace.endSection;默认情况下,你的这些代码是不会生效的,因此,这个选项一定要开启 |
-l, –list-categories | 这个用来列出你分析的那个手机系统支持的Trace模块,一般来说,高版本的支持的模块更多 |
-e < DEVICE_SERIAL>,–serial=< DEVICE_SERIAL> | 指定设备,在特定连接设备上进行跟踪,由设备序列号标识 。 |
–from-file=< FROM_FILE> | 从Trace文件中加载 |
-k < KFUNCS>,–ktrace=< KFUNCS> | 追踪kernel函数,用逗号分隔 |
除了这些参数之外,还可以添加自己想要监听的类别或者模块(以下列出常用的模块):
模块 | 含义 |
---|---|
gfx | Graphic系统的相关信息 |
input | input |
view | View绘制相关信息 |
wm | Window Manager |
am | Activity Manager |
sm | Sync Manager |
app | Application |
res | Resource Loading |
sched | CPU调度的信息 |
sync | Synchronization |
命令行启动trace示例:
python systrace.py gfx input view wm am app res sched sync -b 10240 -t 5 -a com.android.recentspsp -o /media/caiqiwei/internal/mytrace.html
需要注意的点是,在命令行启动时,会有几秒钟的延时,当终端出现Starting tracing时,才表示开始正式抓取trace了
当出现Tracing completed. Collecting output...
时,表示trace已经抓取完毕了,这时cd到我们之前的output目录下,通过chrome浏览器(目前不支持firefox等其它浏览器)打开生成的html文件就可以查看我们抓取的trace文件了
操作方式是:
w:放大
s:缩小
a:左移
d:右移
1:箭头工具:用于选定目标:选择后会在底部显示当前所选目标的详细信息
4:范围选择工具:用于选取某一范围,可在顶部显示当前范围的时间(ms),并高亮显示
f:快捷选取当前选定目标并居中
m:快速选取当前选定目标并框选范围(个人觉得在有些情况下可以代替 4(范围选择工具)并更加精确)
顶部右侧有工具框及搜索定位框:
如何查看trace文件:
左侧框为进程名或模块名,点击可展开,查看进程或模块所包含的各个线程调度信息
点击空白区域之后发现当前帧确实是处于一个红色的警示显示状态:
Running为总共运行的时间
runnable为可运行但未拿到cpu时间片的等待运行时间
sleep为睡眠时间
正常来说:
当running时间过长的话,我们需要考虑当前帧是否在主线程做了太多的耗时操作
当runnable时间过长的话,需要考虑是否是当前进程内进行了一些跨进程调用之类的行为
当sleep时间过长的话,需要考虑是否是当前进程的调度优先级不够,拿不到cpu时间片
我们点击当前问题帧的F标志,可以直接高亮显示应用在当前帧所做所有行为
systrace中会显示三种颜色的帧,绿色代表正常的帧,(即在60hz屏幕上运行时间小于16.6ms,在90hz屏幕上运行时间小于11.1ms,以此类推),黄色和红色的帧均超过规定一帧的时间,红色比黄色更加严重。
如何查看当前设备每一个VSync帧到来的时间?
其实在systrace中已经有显示,可以看到上图中有一个一个的斑马纹,可以通过顶部右侧工具箱中View Options的Highlight VSync开启关闭
每一个斑马纹其实就代表一个VSync帧(灰色和白色都代表一个VSync帧),我们也可以通过SurfaceFlinger模块的VSync-app来查看
可以看到VSYNC-app在每一次波峰(数值1)和波谷(数值0)到来时,即是VSync信号到来时,但我们还看到这里还有一个VSync-sf且相对于VSYNC-app有一个offset偏移。
简单解释一下,这个也是VSync信号,不过是基于SurfaceFlinger的VSync信号。
我们知道,应用在每一帧做的事有如下:
而SurfaceFlinger在VSync信号到来之后一帧的行为如下:
3. 从每一个应用的buffer队列中取出上一帧(正常来说为上一帧,但其实可能为上上帧 等)渲染好的buffer,然后根据layer大小将所有buffer进行合成
其实每一次VSync信号到来时,会同时通知app和SurfaceFlinger,app端接收到VSync信号时会进行当前帧的buffer渲染,而SurfaceFlinger收到VSync信号时,会从各个app的buffer队列中取出渲染完成待合成的buffer进行合成。
而引入offset机制的意义是,如果在app收到VSync信号进行渲染和SurfaceFlinger收到VSync信号进行合成的offset间隔中,app完成了buffer的渲染,那么SurfaceFlinger就可以在当前帧拿到已经渲染好的buffer,保证用户可以更快看到当前buffer
可以看到,我的机器其实就有一个VSync Offset
(6fps校准)
可以看到当前在每个核上的进程调度信息
点开之后可以看到底部的详细信息,包括:在当前cpu核的当前时间片上运行的进程、线程、运行时间等
Perfetto 是 Android 10 中引入的全新平台级跟踪工具。这是适用于 Android、Linux 和 Chrome 的更加通用和复杂的开源跟踪项目。与 Systrace 不同,它提供数据源超集,可让您以 protobuf 编码的二进制流形式记录任意长度的跟踪记录。您可以在 Perfetto 界面中打开这些跟踪记录。
traceconv
工具将 Perfetto 跟踪记录转换为旧版 Systrace 文本格式。启动方式:
Perfetto是基于Android 9(P)起可用的平台级跟踪工具,但仅从Android 11(R)起默认启用。在Android 9(P)和10(Q)上,我们首先需要开启traced和traced_probes这两个进程,才能使用Perfetto来抓取数据,可通过命令行来开启手机的这两个进程
# Needed only on Android 9 (P) and 10 (Q) on non-Pixel phones.
adb shell setprop persist.traced.enable 1
开启之后我们就可以通过perfetto来抓取相关的信息了
目前建议通过chrome浏览器在Perfetto 界面进行操作,方便快捷且可操作性强
可以看到:
Open trace file是直接打开一个pftrace文件
Open with legacy UI是打开一个pftrace文件的同时将其转换为旧版Systrace
Record new trace则是在线录制一个trace文件
选择在线录制,则跳转到配置界面
顶部可以选择当前想要抓取trace的设备
Recording settings用于配置录制模式、缓存大小、录制时间
Trace command用于同步生成当前配置的命令行,我们可以直接将其粘贴到终端,通过命令行启动
probes栏则是其它的一些配置(CPU、GPU、Power等等)
而我们之前systrace里配置的那些模块,已经移到了Android apps & svcs中
相较于systrace,perfetto不会直接把VSync帧在图形界面标注出来,所以我们第一步最好是直接找到SurfaceFlinger进程的VSYNC-app数据,并钉在顶部,方便我们能分析出app每一帧的运行时间
而且同样的,perfetto也不会直接高亮显示出应用的问题帧,而需要你通过应用主线程和RenderThread线程配合SurfaceFlinger的VSYNC-app数据配合分析出当前应用的问题帧
这里有一个比较取巧的小方法,我们可以将trace文件在新版的perfetto打开的同时,通过perfetto提供的legace UI,转换成老版的systrace
通过在systrace中先找到问题帧,再在perfetto中进行细致分析
perfetto还提供基于SQLite的查询和分析数据的能力,具体可查看这篇文档:
https://perfetto.dev/docs/quickstart/trace-analysis
TraceView是Android平台配备一个很好的性能分析工具,它可以通过图形化的方式让我们了解我们要跟踪的程序的性能,并且能具体到方法。
WinScope 提供了用于在窗口转换期间和转换后记录和分析 WindowManager 状态和 SurfaceFlinger 状态的基础架构和工具。WinScope 将所有相关的系统服务状态记录在一个跟踪文件中,您可以使用该文件重现并逐步查看转换。
相关详细介绍可查看Google官方网站: Tracing Window Transitions
通过在Android Studio -> Tools -> Layout Inspector打开:
勾选Show all processes可显示所有进程
这里有一个问题是在有些时候会出现无法找到adb或adb被占用的问题
adb disconnect
一下并在开发者选项中关闭打开一下usb调试就好了
但是Android Studio的Layout Inspector有一个问题是,对于视图树较为复杂的Window,抓取时间超时(默认20s),导致无法抓取当前的视图树,以及Android Studio 4.0引入的实时Layout Inspector导致的部分问题。
对此,网上也有相关解决办法,也就是将超时的默认20s时长修改更长,保证能够在限定时间内解析较为复杂的视图树。具体可查看这篇博客:
Android Studio LayoutInspector 超时错误解决
命令行启动:
通过命令行打开位于Sdk/tools/bin/
下的uiautomatorviewer.sh
文件
点击左上工具栏的第二个按钮可以截取当前手机或虚拟机的界面进行分析
但相对于上文中 “加强版” 的Layout Inspector来说,还是八行:
首先,只能解析出一些原生基础View/ViewGroup,无法像Layout Inspector一样拿到精确的类名,其次解析出的属性太少,唯一的优势就是快,但没啥用
命令行启动
直接命令行输入:
hierarchyviewer
以横向树状图的形式在左侧展示当前window所有的View视图结构
每一个节点显示当前View的类名、View Id(View.getId())
点击其中一个View会直接显示其本身和子View的视图缩略图以及当前View的measure、layout、draw所耗时间
右侧分别显示:
hierarchyviewer相对于新版的Layout Inspector还有一个比较有用的属性是:统计并比较当前View各个子View的测量、布局、绘制时长
通过顶部工具栏的Profile Node打开:
圆点有三种颜色(绿、黄、红),分别对应视图的measure、layout、draw的时长
此为官方给出的对于颜色的解释(说实话,绿色和黄色的含义比较模糊)
Green means the view renders faster than at least half of the other views.
Yellow means the view renders faster than the bottom half of the other views.
Red means the view is among the slowest half of views.
如下是官方给出的分析建议:
Hierarchy Viewer 可测量每个节点相对于同级视图的性能,因此分析结果中总是有红色节点(除非所有视图以完全相同的方式执行),并且这并不一定意味着红色节点就是表现不佳(只不过它是本地视图组中最慢的视图而已)。
Hierarchy Viewer 会栅格化您的布局以获取时间信息。栅格化是获取高级基元(如圆形或矢量字体)并将其转换为屏幕上的像素的过程。栅格化通常由设备上的 GPU 完成,但对于软件栅格化,渲染是使用普通软件在 CPU 上完成的。这意味着绝对报告时间相对于彼此是正确的,但会随着设备和开发计算机上的不断变化的总体 CPU 工作负载而变化。因此,它不能反映设备上的实际性能速度,您应该进行多次分析以了解平均测量结果。
如果应用的运行速度出乎意料地慢,则红色节点可能有问题。在相对设置中,总有一个最慢的节点;只需确保它是您预期的节点即可。以下示例说明了如何解读红色圆点。
Android Lint 是 SDK Tools 16(ADT 16)开始引入的一个代码扫描工具,通过对代码进行静态分析,可以帮助开发者发现代码质量问题和提出一些改进建议。除了检查 Android 项目源码中潜在的错误,对于代码的正确性、安全性、性能、易用性、便利性和国际化方面也会作出检查。
Android Lint 作为项目的代码检测工具,是因为它具有以下几个特性:
启动:
工具栏 -> Analyze -> Inspect Code
这里可以选择需要扫描的路径,然后进行扫描
扫描完成后生成扫描结果
可以看到,有很多条目,但其实不是所有的条目都是有用的
我们主要看Android Lint下的相关条目
如上图所展示的,Android Lint 对检查的结果进行了分类,同一个规则(issue)下的问题会聚合,其中针对 Android 的规则类别会在分类前说明是 Android 相关的,主要是六类:
后面其他的结果条目则是针对 Java 语法的问题
里面也是很多条目
例如:
用< merge >标签替代相关FrameLayout
SparseArray替代HashMap 等
我们可以以此作为参考,根据实际情况进行修改
详细的介绍及自定义Lint相关操作可以查看这篇文章,很细致
Matrix 是一款微信研发并日常使用的应用性能接入框架,支持iOS, macOS和Android。 Matrix 通过接入各种性能监控方案,对性能监控项的异常数据进行采集和分析,输出相应的问题分析、定位与优化建议,从而帮助开发者开发出更高质量的应用。
matrix
可以有效查看到操作时界面的surface更新区域
不支持在 Docs 外粘贴 block
eg:之前敏感权限和时间胶囊的需求开发的时候,发现手机systemui的耗电剧增,打开Surface更新发现,systemui在每一帧都有surface更新,查找原因是因为敏感权限的需求导致状态栏左侧的时间区域由于呼吸动画的缘故不停做动画,导致整个状态栏每一帧都在更新,严重增加耗电,最后取消了呼吸动画
动画相关调试,但目前不支持物理动画的时长缩放(Fling Spring)
可以粗略查看View的layout情况,但细节方面的数值还需要配合Layout相关工具查看
何为过度绘制?
过度绘制是指系统在渲染单个帧的过程中多次在屏幕上绘制某一个像素。例如,如果我们有若干界面卡片堆叠在一起,每张卡片都会遮盖其下面一张卡片的部分内容。
但是,系统仍然需要绘制堆叠中的卡片被遮盖的部分。这是因为堆叠的卡片是根据 Painter 算法(也就是按从后到前的顺序)来渲染的。按照这种渲染顺序,系统可以将适当的透明度混合应用于阴影之类的半透明对象。
过度绘制通常是不必要的,最好避免。它会浪费 GPU 时间来渲染与用户在屏幕上所见内容无关的像素,进而导致性能问题。
overlap
Android 将按如下方式为界面元素着色,以确定过度绘制的次数:
请注意,这些颜色是半透明的,因此在屏幕上看到的确切颜色取决于界面内容。
事实上,有些过度绘制是不可避免的。在优化应用的界面时,应尽量尝试达到大部分显示真彩色或仅有 1 次过度绘制(蓝色)的视觉效果。
默认情况下,布局没有背景,这表示布局本身不会直接渲染任何内容。但是,当布局具有背景时,其有可能会导致过度绘制。 移除不必要的背景可以快速提高渲染性能。不必要的背景可能永远不可见,因为它会被应用在该视图上绘制的任何其他内容完全覆盖。例如,当系统在父视图上绘制子视图时,可能会完全覆盖父视图的背景。
布局优化:
ViewStub、merge、include等
图为某手机任务管理器返回动画渲染条形图
下面是有关输出的几点注意事项:
下表介绍了使用运行 Android 6.0 及更高版本的设备时分析器输出中竖条的每个区段:
在Android6.0之前,柱状图主要为黄色、红色、蓝色(Swap Buffers,Command Issue,Draw)三类。自从安卓6.0之后,玄学曲线进行了改版,增加至8条数据。新版本GPU呈现分析曲线新增加了(Sync&Upload,Measure&LayoutAnimation,Input Handling,Misc/Vsync Delay)五大步骤数据。
(1)Command Issue(红色):表示执行任务的时间,是Android进行2D渲染显示列表的时间,为了将内容绘制到屏幕上,Android需要使用Open GL ES的API接口来绘制显示列表,红色线条越高表示需要绘制的视图更多;比如我们在遇到多张图加载的时候,红色会突然跳的很高,此时滑动页面也就不流畅了,要等几秒图片才能加载出来,并不是卡住。
(2)Swap Buffers(黄色):表示处理任务的时间,即CPU等待GPU完成任务的时间,线条越高,表示GPU做的事情越多。若橙色部分过高,说明GPU目前过于忙碌。
(3)Draw(蓝色):表示测量和绘制视图列表所需要的时间,蓝色线条越高表示每一帧需要更新很多视图,或者View的onDraw方法中做了耗时操作。它越长说明当前视图比较复杂或者无效需要重绘,表现为卡顿。
理想的流畅状态是三色都低于绿线以下。
(4)Sync & Upload(浅蓝色):表示的是准备当前界面上有待绘制的图片所耗费的时间,为了减少该段区域的执行时间,我们可以减少屏幕上的图片数量或者是缩小图片的大小。
下面这几种统称为绿色,随着后面标注的数字颜色逐渐加深。
(5) Measure/Layout(绿色1)表示布局的onMeasure与onLayout所花费的时间,一旦时间过长,就需要仔细检查自己的布局是不是存在严重的性能问题。
(6)Animation(绿色2):表示计算执行动画所需要花费的时间,包含的动画有ObjectAnimator,ViewPropertyAnimator,Transition等等。一旦这里的执行时间过长,就需要检查是不是使用了非官方的动画工具或者是检查动画执行的过程中是不是触发了读写操作等等。
(7)Input Handling(绿色3):表示系统处理输入事件所耗费的时间,粗略等于对事件处理方法所执行的时间。一旦执行时间过长,意味着在处理用户的输入事件的地方执行了复杂的操作。
(8) Misc Time/Vsync Delay(绿色4):表示在主线程执行了太多的任务,导致UI渲染跟不上vSync的信号而出现掉帧的情况。
总结一下:
红色/黄色:从布局构建角度去考虑。优化:否减少视图层级、减少无用的背景图、减轻自定义控件复杂度等。
蓝色/浅蓝/各种绿色:从耗时操作角度去考虑。
Android Developer关于严格模式的介绍:StrictMode
StrictMode是一个开发人员工具,可以检测到您可能偶然执行的操作并将其引起您的注意,以便您可以对其进行修复。
StrictMode最常用于在应用程序的主线程上捕获意外的磁盘或网络访问,在该主线程上接收UI操作并进行动画处理。使磁盘和网络操作脱离主线程(位于异步线程)可以使应用程序更加顺畅,响应更快。通过使应用程序的主线程保持响应状态,还可以防止向用户显示ANR对话框。
我们一般将StrictMode相关的检测代码放入应用程序组件的Application::onCreate
方法:
public void onCreate() {
if (DEVELOPER_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate();
}
然后在开发者选项中打开严格模式
eg:
android.os.strictmode.LeakedClosableViolation: A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.
at android.os.StrictMode$AndroidCloseGuardReporter.report(StrictMode.java:1877)
at dalvik.system.CloseGuard.warnIfOpen(CloseGuard.java:286)
at android.view.SurfaceControl.finalize(SurfaceControl.java:980)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:289)
at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:276)
at java.lang.Daemons$Daemon.run(Daemons.java:137)
at java.lang.Thread.run(Thread.java:919)
Caused by: java.lang.Throwable: Explicit termination method 'release' not called
at dalvik.system.CloseGuard.open(CloseGuard.java:237)
at android.view.SurfaceControl.<init>(SurfaceControl.java:899)
at android.view.SurfaceControl.<init>(SurfaceControl.java:79)
at android.view.SurfaceControl$1.createFromParcel(SurfaceControl.java:965)
at android.view.SurfaceControl$1.createFromParcel(SurfaceControl.java:963)
at android.os.Parcel.readParcelable(Parcel.java:2973)
at android.view.RemoteAnimationTarget.<init>(RemoteAnimationTarget.java:188)
at android.view.RemoteAnimationTarget$1.createFromParcel(RemoteAnimationTarget.java:261)
at android.view.RemoteAnimationTarget$1.createFromParcel(RemoteAnimationTarget.java:259)
at android.os.Parcel.readTypedObject(Parcel.java:2811)
at android.os.Parcel.createTypedArray(Parcel.java:2772)
at android.view.IRemoteAnimationRunner$Stub.onTransact(IRemoteAnimationRunner.java:103)
at android.os.Binder.execTransactInternal(Binder.java:1132)
at android.os.Binder.execTransact(Binder.java:1014)