ConstraintLayout是2016年Google发布的,这种新的布局方式支持了编辑器的方式,从布局和性能上都做了一定的优化,同时也增加了一些新的概念,例如约束链和设置大小比例。本文从将会从性能上与传统的布局进行比较,并且给出使用在项目上的建议。
Android的绘制流程
首先回顾一下Android的绘制流程,方便我们更好的理解ConstraintLayout的性能。
Android绘制流程包括3个阶段:
1. 测量(Measure)
形同完成View数从上到下的遍历决定每个ViewGroup有多大和View元素是什么。当一个VIewGourp被测量,它也会测量它的子View。
2. 布局(Layout)
另一个从上到下遍历的发生,每个ViewGroup使用在测量阶段决定的大小确定子View的位置。
3. 绘制(Draw)
这个方法还是从上到下的遍历,对于View数中的每个对象,创建的Canvas对象用于发送绘制命令列表到GPU。这些命令包括系统在前两个阶段确定的ViewGroup和View对象的大小和位置。
绘制过程中的每个节点都需要从上到下遍历View树。因此,在View层级中互相嵌入的View越多,设备用于绘制View的所需时间和计算能力越多。通过在应用布局保持扁平的层级,你可以创建一个快速响应用户界面的应用。
层级消耗
绘制一个如下图所示的界面
使用传统布局LinearLayout和RelativeLayout来完成上面的这个图片,需要下面的代码
<RelativeLayout>
<ImageView />
<ImageView />
<RelativeLayout>
<TextView />
<LinearLayout>
<TextView />
<RelativeLayout>
<EditText />
</RelativeLayout>
</LinearLayout>
<LinearLayout>
<TextView />
<RelativeLayout>
<EditText />
</RelativeLayout>
</LinearLayout>
<TextView />
</RelativeLayout>
<LinearLayout >
<Button />
<Button />
</LinearLayout>
</RelativeLayout>
层级最深需要4层,结合上面讲的Android绘制流程,Measure、Layout、Draw三个步骤都需要绘制子View,层级越深,绘制的所要花费的时间就越长,Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染。理论上来说两次回调的时间周期应该在16ms,如果超过了16ms我们则认为发生了卡顿。
但是我们使用ConstraintLayout,不论什么样场景都只需要一个层级就可以搞定。
性能比较
口说无凭,我们需要用真实的数据进行对比,网上有很多测试绘制性能的工具,例如Systrace和TraceView,Systrace和TraceView使用方法。这些工具都可以帮助我们分析UI卡顿的原因,使用这两个工具让我们做个对比:
- Systrace
传统的方式绘制
ConstraintLayout绘制方式
Systrace会自动高亮这个布局的性能问题,以及一些修改的建议,通过点击“Alerts”标签,你会发现绘制这个View等级在测量和布局阶段需要80多次昂贵的传递,在测量和布局阶段触发很多昂贵的传递并不是我们想要的,大量的绘制活动会导致掉帧。而ConstraintLayout比传统的方式好了很多。
- TraceView
传统的方式绘制
ConstraintLayout绘制方式
两幅图是在创建Activity的时候调用的方法,可以看出,传统的绘制方式调用方法的层级明显比ConstrainLayout的多。
通过这两个性能的比较,在比较复杂的情况下,ConstraintLayout性能表现还是很好的。
在ConstraintLayout 1.0的时候,在简单布局的情况下,ConstraintLayout的绘制的低于LinearLayout和RelativeLayout布局的绘制速度的。
ConstraintLayout 1.0 优势:
1. 一层布局解决所有的情况
2. 按照比例布局
3. 可视化编辑操作
4. 在复杂的情况下性能优于传统的布局
ConstrainLayout 1.0 劣势:
1. 在复杂的布局时,阅读比较费劲
2. 在简单的布局时,性能消耗比传统的布局要大
3. 因为引入新的概念,上手需要一些时间。
4. 存在一些bug和坑
ConstrainLayout这里有一篇很好的使用介绍
ConstraintLayout 1.1
最近Google升级了约束布局,修改了bug,更新了一些新的特性
百分比
在约束布局 1.0 版本中,需要使用两条引导线才能让视图根据百分比来占据屏幕。而在约束布局 1.1 版本中,通过允许您轻松地将任何视图限制为百分比宽度或高度,一切将变得很简单。
使用百分比指定按钮的宽度,以便在保持设计效果的同时适应可用空间。
所有视图都支持 layout_constraintWidth_percent 和 layout_constraintHeight_percent 属性。这些将导致约束被固定在可用空间指定百分比位置。 因此,使用几行 XML 代码就可以使 Button 或 TextView 展开并以百分比填充屏幕。
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintWidth_percent="0.7" />
链条
链条不是很特性,1.1修复1.0的一些bug。
通过 spread,spread_inside 和 packed,链条能够让您配置如何布置多个相关的视图。
app:layout_constraintVertical_chainStyle 属性可以作用于链条中的任何视图。 您可以设置它的值为 spread,spread_inside 或者 packed。
- spread:均匀分配链中的所有视图
- spread_inside:将第一个元素和最后一个元素放置在边缘上,并均匀分布其余元素
- packed:将元素包裹在链条的中心
群组
在1.0的时候,如果你想隐藏一些view,只能调用每个view的setView方法,1.1新增群组的功能是非常好的。
一个群组并没有增加视图的层级——这实际上只是一种标记视图的方式。在下面的示例中,我们将标记 profile_name 和 profile_image 以供 id 配置文件引用。
当您有多个需要显示或陈列在一起的元素时,这将很有用。
<android.support.constraint.Group
android:id="@+id/profile"
app:constraint_referenced_ids="profile_name,profile_image" />
当定义名为 profile 的群组后,您可以为该群组设置可见性,并将其应用于 profile_name 和 profile_image。
prifile.setVisibility(GONE);
prifile.setVisibility(VISIABLE);
更多的新特性,可以看这篇文章
项目中检测绘制性能
- 利用UI线程Looper打印的日志
- 利用Choreographer
- OnFrameMetricsAvailableListener Android 7.0以后出来的新功能,用来检测每一帧中各个方法绘制所需要的时间
前两种方式具体使用可以看这篇文章
FrameMetrics
从 7.0(API 24)开始,安卓 SDK 新增 OnFrameMetricsAvailableListener 接口用于提供帧绘制各阶段的耗时,数据源与 GPU Profile 相同。
回调接口为 Window.FrameMetrics:
public interface OnFrameMetricsAvailableListener {
void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation);
}
FrameMetrics存储了如下的数据
阶段 | 含义(纳秒) | 备注 |
---|---|---|
ANIMATION_DURATION | 动画耗时 | |
DRAW_DURATION | 创建和更新DisplayList耗时 | |
FIRST_DRAW_FRAME | 布尔值,标志该帧是否为此 Window 绘制的第一帧 | |
INPUT_HANDLING_DURATION | 处理用户输入操作的耗时 | |
INTENDED_VSYNC_TIMESTAMP | 预期VSync到来的时间戳 | |
LAYOUT_MEASURE_DURATION | layout/measure耗时 | |
SWAP_BUFFERS_DURATION | GPU在等待GPU完成渲染的耗时 | |
SYNC_DURATION | 上传bitmap到GPU耗时 | |
TOTAL_DURATION | 整帧渲染耗时 | |
UNKNOWN_DELAY_DRRATION | 位置延迟 | |
VSYNC_TIMESTAMP | VSync时机到来的时间戳 |
使用ActivityFrameMetrics的效果:
看一下抖音项目中首页的帧绘制效率
可以看出来,第一帧总共花费时间是1.2s,这已经是很长的时间了,在网络请求后,又进行了一次绘制,花费了大概0.5s,如果是优化可以从这方面进行。MainActivity层级大搞是7-8层的样子,如果是用ConstraintLayout的话,至少可以减少一半的层级,可以在一定程度上减少绘制所花费的时间。