本文基本翻译自Facebook工程师的文章
Speed up your app,也加入了自己的一些内容。
会介绍以下几个主题
Systrace的功能可以在AS的DDMS中找到,但不太稳定,所以这里只介绍命令行模式。
另外Systrace只能分析概况,不能定位问题位置,不太感兴趣的朋友也可以直接跳到第二节Traceview。
在终端(我的环境是MAC)中先进入sdk/platform-tools/systrace目录
cd /Users/apple/Documents/Android/sdk/platform-tools/systrace
然后执行Systrace的命令是
python systrace.py --time=10 -a com.duotin.fm -o mynewtrace.html sched gfx view wm
–time=10 表示记录10秒
更多参数说明请查看官网
执行完打开生成的mynewtrace.html文件
呈现这样的界面
点击第一列的三角形警示图标或者第二列的圆形警示图标,都可以查看警示详情,点击效果分别如下
三角形警示图标,每个图标代表一个警示,点击后查看警示详情。
圆形警示图标,每个图标代表一个frame,显示红色或者黄色表示此frame的时间已经超过16.6 millisecond per frame的标准,会导致界面失帧。点击后查看这个frame所有的警示。
此时按”M”快捷键可以高亮当前选中的frame。“A”和”D”分别为左移和右移视图,”W”放大,”S”缩小。
此外,点击各颜色块,可以查看各颜色块的详情。
点击上图中的第一个Alert: Inflation during ListView recycling
显示详情
可以看出Inflation during ListView recycling的执行时间是32ms(远远超过了16ms的限制),共5个item,平均到每个item为6ms。通过点击该frame范围内的颜色块,可以查看各个方法的详情。
我们再点击一个圆形警示图标并高亮
顶部显示此frame耗时19ms, 点击右下方的Alert,显示有 Scheduled delay。
Scheduled delay 是指告诉CPU执行任务,但CPU太忙了,任务被延迟执行了。
点击下面的一个颜色块,显示如下
Wall duration 是指此颜色块代表的方法从开始到结束的耗时
CPU duration 是指 CPU的执行时间
可以发现CPU duration只有4ms,但Wall duration有18ms。
延迟这么严重,我们来看看原因。
在选中的颜色块上方,我们看到四个CPU都被颜色块填充,表示此时4个CPU都有活干,很忙。
我们选中一个CPU的颜色块
可以发现占用CPU的应用是com.udinic.keepbusyapp
恩,对Systrace的介绍到此结束了,虽然还有些没讲,但Systrace的确只能看个概览。
且慢,再加送一个tip, 点击右边栏的Alerts, 你能看到所有的Alerts.
通过这张图我分析出
inefficient view alpha usage数量最多。Inflation during ListView recycling影响时间最长,耗时长达52ms。
inefficient view alpha usage是因为调用具体View的setAlpha方法,而View的setAlpha在Android中是很昂贵的操作。解决方法是用ARGB设置color代替直接调用setAlpha;如果是ImageView,调用ImageView#setImageAlpha;如果是自定义View,覆盖hasOverlappingRendering()或者onSetAlpha()或者通过paint.setAlpha实现。详细请参考文章1,文章2, 文章3
Traceview能够在方法层面上分析APP的性能,非常强大。
可以通过,命令行或者GUI启动,我用的是GUI启动,点击AS的Android Device Monitor, 点击Devices栏目下面的 Start Method Profilling的图标 , 在对话框中选择,(我选的是trace Based Profilling,表示实时分析,会比较慢,但分析结果详细),操作APP, 分析结束的时候,点击同一个图标即可。更多操作请访问官网
先看下界面
列名 | 意义 |
---|---|
Name | 方法名,每个方法的颜色都不一样。 |
Inclusive CPU Time | 此方法占用CPU的时间,Inclusive指包括调用的方法所 |
Exclusive CPU Time | 此方法所占用CPU的时间,Exclusive 指不包括调用的方法 |
Inclusive / Exclusive Real Time | Real Time指方法从开始到结束消耗的时间,跟Systrace中的Wall duration一个意思。 |
Calls+Recursion | 此方法被调用了多少次+多少次是递归调用 |
Calls / Total | 子方法被此父方法调用的次数/子方法被调用的总次数 |
点击某条目下的parent 或者 child 方法时,会跳到该方法的条目。
想找出最影响性能的方法,可以点击Exclusive CPU time一栏,找出消耗时间最长的几个方法。如果是应用的方法,直接看可不可优此方法。如果是系统方法,通过查看其父方法,追溯至应用方法。
而查看子方法,可以看出此方法到底做了什么。
如果要找UI卡顿的原因,可以从 具体Adaper类#getView 具体View#ondraw, 具体View#onMeasure等方法入手。
方法执行时占用了CPU,所以执行时间过长会造成UI渲染被延迟,从而应用不流畅。而GC同样会占用CPU,AS也同样提供了查看GC的工具:
点击AS中的Android Monitor, 选中Memory | CPU 一栏, 界面如下
如图所示,小幅的内存下降一般就是发生了GC。
点击左侧的Heap dump,会生成内存中的所有对象的快照。
列名 | 意义 |
---|---|
Total Counts | 内存中该类的对象个数 |
Heap Count | 在该堆中该类的对象个数,左上角可以选择App heap或Zygote heap |
Sizeof | 单个对象占用的Shallow Size |
Shallow Size | 所有对象所占用的Shallow Size |
Retained Size | 所有对象所占用的Retained Size,即GC后会释放的内存 |
instance | 该类一个具体的对象 |
Reference Tree | 引用这个对象的父对象,点击父对象,展开这些父对象的父对象 |
什么是Shallow Size 和Retained Size
选中一行,点击右侧的一个instance,可以在下方看到Refrence Tree界面
在图中可以看出MemoryActivity的一个instance在ListenerManager中被引用了。如果MemoryActivity已经不在Activity栈中了,这样的引用就是内存泄漏。另外一个检查内存泄漏的工具是leakcanary
通过查看Retained Size和Reference Tree,我们可以知道哪些对象占用了较多的内存,对象间的引用关系,进而分析是否可以优化数据结构,减少引用关系,以减少内存占用和GC频率。
Memory | CPU一栏左侧的另一个按钮Allocation Tracker也是用于分析内存占用。点击一次表示开始记录,再次点击表示停止记录。
在结果页面的左上方点击饼状图。
可以选择 group by Allocator,即按对象划分。
或者 group by method
Allocator下面的饼状图最外围的是具体的类,内部的是包名。图中可以看出包或者对象占用的内存大小或者个数,面积越大,占用或者个数越多。选择size可以查看占用内存最多的对象,选择count可以查看以及个数最多的对象。前者我们可以试着优化类,后者我们可以尝试建立一个Object pool来复用对象。
从group by method可以看出,decode方法占用的总内存达10.91M, 就有可能是方法内新建了太多对象,可以往这方面优化。
内存方面的tips:
1. Enums Enums比int占的内存大得多,而且有替换方案@IntDef, 所以除了某些情况,比如你需要强制指定类型,不然的话int会更节省内存
2. 自动装箱 自动装箱指从基本类型自动转换到对象形式的(比如int到Integer),鉴于基本类型使用的场景和次数都较多,所以需要尽量避免使用其自动装箱的形式。
3. HashMap vs ArrayMap / Sparse*Array 如果我们需要使用int作为Map的value,可以使用SparseIntArray,比起使用HashMap对int自动装箱,要省内存的多。如果要使用Object作为Map的key,除了HashMap,你也可以考虑使用ArrayMap,功能和HashMap一样,但更省内存,点击了解原理。尽管时间性能上HashMap更胜一筹,但除非你要存储1000个以上对象,否则他们使用起来几乎一样快。
4. 注意Context对象 因为Context在开发中的使用场景较多,所以最容易造成内存泄漏。Activity本身是一个heavy的对象,为了避免内存泄漏,可以穿ApplicationContext的话,就不要传Activity了。
5. 避免非静态内部类 非静态内部类隐式持有外部类的引用,所以如果外部类不再被需要,但内部类仍在使用状态,就造成了内存泄漏。特别是Activity类,在定义内部类的时候尽量定义成static的。
首先在手机的开发者选项页面,点击GPU呈现模式分析(Profile GPU rendering),选中“In adb shell dumpsys gfxinfo” 然后在AS的Android Monitor 界面选中GPU一栏,确保左上方的暂停按钮没有选中,此时AS就开始按照选定的包名显示GPU情况了。每一个条直线表示UI渲染中的一帧,不同的颜色表示不同的绘制阶段。
在 Marshmallow 版本,增加了更多的颜色
根据谷歌工程师John Reck提供的信息,
图中的Animation 是指所有通过Choreographer 注册的CALLBACK_ANIMATION,包括
Choreographer#postFrameCallback View#postOnAnimation。这两个函数在 view.animate(), ObjectAnimator, Transitions等场景中有用到。systrace中的Animation也是这个意思。
misc指的是接收到的vsync的时间戳和当前时间的延迟。
很多人都看到过Choreographer的log “Missed vsync by。。。ms skipping 。。。 frames”,这就是misc。换句话说,就是在记录帧状态时INTENDED_VSYNC和VSYNC的差别
要使用这个功能,你需要在手机的开发者选项中开启GPU rendering
此工具原理是ADB命令
adb shell dumpsys gfxinfo <PACKAGE_NAME>
如果我们的项目有自动化UI测试工具,就可以在构建服务器上在一些UI交互后(列表滑动,复杂动画)运行此命令,查看“Janky Frames”等值是否有变化。这样做能够帮我们确定最近的几次提交(commite)是否影响了性能,在产品发布前发现和解决此问题。如果我们使用framestats作为关键字,还能获得更多详细的渲染信息。
我们还能以其他方式展示此图
在“Profile GPU Rendering”选项里,有“On screen as bars”这个选项,选中它,在手机屏幕上就会出现三个图像,分别代表StatusBar,NavBar和当前程序的Activity的GPU Rending信息,以绿线指示16ms的渲染阈值。
在右侧这张图,我们看到有些帧超过了绿线,即说明渲染时间超过了16ms。这些“越界”的帧大部分是蓝色,我们大概可以认为是因为绘制了太多或者太复杂的View。我滑动了一下此界面的信息流,的确有多种类型的View.有些会被重绘,有些比较复杂。所以那些“越界”的帧可能是因为正在绘制复杂的View.
注意:默认的主题设置了窗口的背景色,如果你的Activity包含的不透明的布局能覆盖全屏幕,你就可以通过移除窗口的背景色来减少过度绘制。可以在主题中设置,或者在onCreate方法中调用 getWindow().setBackgroundDrawable(null)
利用 Hierarchy Viewer, 你能导出所有层级到 PSD 文件。用Photoshop打开此文件,查看不同的层级,你能发现布局中所有的过度绘制. 请利用这些信息去掉冗余的过度绘制,不要调试GPU过度绘制时显示绿色就觉得可以了,争取蓝色。
// Using the Object animator
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 20f);
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
objectAnimator.start();
// Using the Property animator
view.animate().translationX(20f).withLayer().start();
很简单,是不是?
不过使用hardware layer时需要注意以下几点:
Hardware layer 占用了GPU有限的内存,所以请只在动画等需要的场合使用Hardware layer,使用完后及时清理。上例中,使用ObjectAnimator时,我设置了一个监听器,在动画结束时移除layer。使用Property animator时, 我使用了withLayers()方法, 此方法会在动画开始时自动创建layer并在动画结束时移除。
如果你在应用了hardware layer以后改变了View的属性, 就会invalidate hardware layer 并在离屏缓存重新渲染一遍View。这种情况在执行Hardware Layer未优化的操作时会发生 ( 到目前为止, 被优化的操作包括: 旋转, 伸缩, 坐标设置, 坐标偏移, pivot(枢轴) 和 透明度)。比如 , 你对使用了hardware layer的View执行动画 ,一边位移一边更新 View 的背景色,就会导致hardware layer不停的更新. 在这种情况下,更新hardware layer的开销会抵消掉使用它带来的好处。
在第二种情况下,我们可以查看hardware layer更新的情况。即在开发者选项中启用 “显示硬件层更新”
启用后,View在更新其hardware layer时会以绿光闪烁。不久前我的一个ViewPager滑动起来不流畅时我就启动了此选项. 下图是我当时所看到的:
在整个的滑动过程中,两个Page都显示绿色!
翻译了大半,待续