Android系统每隔16ms发出VSYNC(Vertical Synchronization(垂直同步))信号,触发对UI进行渲染,也就是我们的应用必须在16ms内完成屏幕刷新的全部逻辑。
为什么是16ms呢,这是因为一般来说人眼分辨的最大帧数是每秒60帧。一帧看做一张图片的话,可以算出1000/60 =16.6ms。这样才能达到每秒60帧,然而,这个每秒帧数的参数是由手机硬件所决定的,现在大部分手机屏幕的刷新率是60赫兹,也就是我们必须在16ms(1000/60 =16.6ms)内完成一帧的刷新。不然,就会出现丢帧,卡顿。
下面,看下对UI布局的优化方式,对应的优化分析工具,性能测试等。
这个标签的目的是为了布局的重用。
include标签允许在一个布局里面引入另外的布局。
如果我们有一些公共样式的布局,我们最好的做法就是提取出来,放到单独的布局文件(xml)里面;然后,让有需要的布局直接引入。
这样,我们就达到了布局重用的目的。
比如,我们的应用大部分的标题栏都是(返回按钮+标题+功能按钮)。我们就可以单出的写道一个布局里面,在需要的view里面引入,不如
......
这个时候,要注意base_titlebar.xml最外层布局的高度,如果是*“match_parent”*的话就会影响其它的布局(显示不出来)。如果,我们使用的是android:layout_height="wrap_content"的话。可能会满足不了其它的布局要求(标题栏一般不会,但是,其它的公共view会出现这种情况)。
这种情况下,我们就需要再引入此布局的view中,重写layout_width或者layout_height属性了;除了layout的height、width属性,我们还可以复写其它的layout属性,比如layout_margin等。
......
这里引入的话,其实还涉及了一个问题,布局嵌套。讲merge标签的时候会提到。
merge标签是为了减少布局的嵌套。
下面看个自定义view的例子
自定义view
public class OptSelectLineEditText extends AutoRelativeLayout implements IDynamicEditListener {
...
public OptSelectLineEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
View view = LayoutInflater.from(context).inflate(R.layout.view_select_edit_opt, this, true);
...
}
mer
}
...
优化前
看下布局view_select_edit_opt.xml
优化后
布局文件view_select_edit_opt.xml
懒加载,惰性加载
有时候,我们的view需要在某些特定的条件下才需要加载出来,这样的情况很多。比如,普通用户没有表示,VIP用户有个金光闪闪的LOGO(ImageView)。
一般在做这样功能的时候,我们都是先把这个ImageView设置成GONE隐藏掉,当VIP用户进来的时候,把它设置成VISIBLE显示出来。这样就实现方式来说,肯定是可以的。但是,它其实还是在布局上的,在解析的布局文件的时候,还是会把这个元素解析出来的。
这个时候,我们就需要用到了ViewStub这个View了。它为什么可以作为一种优化呢,因为它没有大小,没有绘制,资源消耗非常少。
下面看下ViewStub的使用
布局引用
代码使用(伪代码)
ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub);
if (viewStub != null) {
//加载懒加载的view
View inflatedView = viewStub.inflate();
imgVIP= (ImageView) inflatedView.findViewById(R.id.iv_vip_logo);
}
这样就把金光灿灿的VIP标识渲染出来了。
最后,为什么说ViewStub,没有大小,没有绘制,资源消耗非常少?
我们知道View的绘制都会经过onMeasure,onLayout,onDraw 3个阶段。我们来看下ViewStub是怎么经历的呢,并且inflate()方法,又是怎么把view绘制出来的呢?
public final class ViewStub extends View {
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context);
...
//不可见
setVisibility(GONE);
// 设置不绘制
setWillNotDraw(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 宽高都为0 onMeasure的时候 宽高都为0
setMeasuredDimension(0, 0);
}
//什么也不绘制
@Override
public void draw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
}
public View inflate() {
// 1、获取ViewStub的父view
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
// 2、获取ViewStub里面需要懒加载的view(布局里layout指定的布局view_vip_logo.xml)
final View view = inflateViewNoAdd(parent);
// 3、将ViewStub自身从parent中移除,并且把身上的param放到懒加载的身上
replaceSelfWithView(view, parent);
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
//mLayoutResource是需要懒加载的文件,如果没有,直接挂了
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
// 如果父view不是viewGroup的话,直接挂了。
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
}
到这里,我们就基本看完了ViewStub的源码
到这里,我们也知道了为什么ViewStub会消耗资源比较少了。
1,在布局层次一样的情况下,使用LinearLayout替代RelativeLayout。因为默认情况下LinearLayout只会测量一次,而RelativeLayout会测量2次;但是,当LinearLayout的view有weight属性的时候,它也会测量2次,这个时候,就建议使用RelativeLayout了。
层级深度一样的情况下,优先级:FrameLayout、LinearLayout(不带weight)、RelativeLayout。
2,如果布局层数较深,考虑使用RelativeLayout来较少布局层次。
ConstraintLayout 布局是一个支持库,你可以在API9以上的系统上使用它。
具体使用:Android ConstraintLayout 约束布局的使用介绍
它主要就是为了减少布局嵌套过多的问题,来优化布局。
官方文档
通过手机的开发者选项-调试GPU过度绘制,来打开此功能。再下面讲过度绘制工具使用的时候,我们再看下,怎么查看过度绘制。
我们通过查看过度绘制,一般,我们通过
消除过度绘制,达到优化布局的目的。
参考文档
调试UI布局优化的工具主要有:
Hierarchy Viewer 是 Android Device Monitor 中内置的一种工具,可让您测量布局层次结构中每个视图的布局速度。它可以帮助您查找由视图层次结构导致的性能瓶颈。已经弃用
Layout Inspector 主要是看布局的层级结构及布局属性。它中的大部分功能以前是由 Hierarchy Viewer 和 Pixel Perfect 工具提供的,但这些工具现已弃用。
过度绘制 -开发者选项查看
虽然,Hierarchy Viewer 已经弃用了。但是,Layout Inspector 查看的功能还是比较简单。我们想要看看布局速度,哪里拖慢了速度,还是需要用Hierarchy Viewer 的。
官方文档
使用它来分析您的应用:
1,真机的话,你需要通过ViewServer设置到你的代码里,重新运行应用。
2,打开sdk–>tools–>monitor.bat ,打开了DDMS。(它在AS3.1以上里面的快捷键已经被移除)
打开后的初始界面如下图
3,启动 Hierarchy Viewer
连接设备,启动应用。然后,在菜单栏中,依次选择 Window > Open Perspective,然后点击 Hierarchy View
你就会看到下图这样
如果没有看到,请依次选择 Window > Reset Perspective 以返回默认布局。
功能介绍:
视图层次结构是布局的快照,因此它不会自动更新。要更新层次结构视图,请点击 Reload the view hierarchy 图标 。
4,分析布局
4-1,在 Tree View 或 Layout View 中,点击要分析其子级的视图节点(小灰方块)。
4-2, 要开始分析,请点击 Tree View 顶部的 Obtain layout times 图标
点完后,如下图,就会显示出view的measure,layout,draw的时间了。
所选节点的每个子级都有三个圆点,可以是绿色、黄色或红色。
这些圆点大致对应于处理View的测量、布局和绘制阶段。圆点的颜色表示该节点相对于本地系列中所有其他已分析节点的相对性能。
解读结果
Hierarchy Viewer 可测量每个节点相对于同级视图的性能,因此分析结果中总是有红色节点(除非所有视图以完全相同的方式执行),并且这并不一定意味着红色节点就是表现不佳(只不过它是本地视图组中最慢的视图而已)
Hierarchy Viewer 会栅格化您的布局以获取时间信息。栅格化是获取高级基元(如圆形或矢量字体)并将其转换为屏幕上的像素的过程。栅格化通常由设备上的 GPU 完成,但对于软件栅格化,渲染是使用普通软件在 CPU 上完成的。这意味着绝对报告时间相对于彼此是正确的,但会随着设备和开发计算机上的总体和不断变化的 CPU 工作负载而变化。因此,它不能反映设备上的实际性能速度,您应该进行多次分析以了解平均测量结果。
如果应用的运行速度出乎意料地慢,则红色节点可能有问题。在相对设置中,总有一个最慢的节点;只需确保它是您预期的节点即可。以下示例说明了如何解读红色圆点。
使用 Android Studio 中的 Layout Inspector,您可以将应用布局与设计模型进行比较、显示应用的放大视图,并在运行时检查其布局的细节。如果您的布局是在运行时(而不是完全在 XML 中)构建的并且布局表现出意外行为,这会很有用。
官方文档
要打开 Layout Inspector,请执行以下操作:
1,在连接的设备或模拟器上运行您的应用。
2,依次点击 Tools > Layout Inspector。
3,在显示的 Choose Process 对话框中,选择要检查的应用进程,然后点击 OK。
图 1. Choose Process 对话框
默认情况下,Choose Process 对话框仅列出当前在 Android Studio 中打开且正在设备或模拟器上运行的项目的进程。如果您要检查设备上的其他应用,请勾选 Show all processes。如果您使用的是没有 Google Play 商店的已取得 root 权限的设备或模拟器,那么您会看到所有正在运行的应用。否则,您只会看到正在运行的可调试的应用。
Layout Inspector 会拍摄快照,将其另存为 .li 文件并打开。
拍摄快照(.li)文件打开后,如下图
官方文档
打开开发者选项–GPU过度渲染-选择过度绘制区域,如下图
打开后,再进入我们的APP,就会显示过度绘制的区域,下面的图,看出左边的是正常的,右边的是有过度绘制的。
过度绘制的颜色说明:
上图,说明了高度绘制的次数及其实现颜色
根据显示出来的过度绘制区域,我们就可以很愉快的去想办法优化拉。