Android 性能优化之UI布局优化

前言

Android系统每隔16ms发出VSYNC(Vertical Synchronization(垂直同步))信号,触发对UI进行渲染,也就是我们的应用必须在16ms内完成屏幕刷新的全部逻辑。

为什么是16ms呢,这是因为一般来说人眼分辨的最大帧数是每秒60帧。一帧看做一张图片的话,可以算出1000/60 =16.6ms。这样才能达到每秒60帧,然而,这个每秒帧数的参数是由手机硬件所决定的,现在大部分手机屏幕的刷新率是60赫兹,也就是我们必须在16ms(1000/60 =16.6ms)内完成一帧的刷新。不然,就会出现丢帧,卡顿。

下面,看下对UI布局的优化方式,对应的优化分析工具,性能测试等。

一、Android UI布局优化的几个方面

  • include标签使用
  • merge标签使用
  • ViewStub的使用
  • 布局的选择(RelativeLayout、LinearLayout)
  • ConstraintLayout约束布局
  • 过度绘制优化
  • 控件的优化

1、include 标签

这个标签的目的是为了布局的重用。

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标签的时候会提到。

2、merge 标签

merge标签是为了减少布局的嵌套。

  • merge最常见的是和include标签一块使用。这样在复用布局的时候,也就不会增加多余的布局嵌套了,解决了只有include标签带来的问题。
  • 在自定义组合view的时候(比如继承FrameLayout,LinearLayout等)。我们会自定义一个布局(xml)来引入到自定义view中,如果不用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




    

    

    

    


使用Hierarchy Viewer查看下布局
Android 性能优化之UI布局优化_第1张图片

  • 优化后

    布局文件view_select_edit_opt.xml




    

    

    

    

使用Hierarchy Viewer查看下布局
Android 性能优化之UI布局优化_第2张图片

3、ViewStub标签

懒加载,惰性加载

有时候,我们的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的源码

  • 它设置了自身不可见GONE
  • 它设置了不绘制setWillNotDraw(true)
  • 它的onMeasure()方式的宽高都强制设置成了0
  • 它的onDraw()方法什么也没有
  • inflate的时候,会把自身的param设置给懒加载的view身上,并且把自己从view树里移除。

到这里,我们也知道了为什么ViewStub会消耗资源比较少了。

4、布局的选择

1,在布局层次一样的情况下,使用LinearLayout替代RelativeLayout。因为默认情况下LinearLayout只会测量一次,而RelativeLayout会测量2次;但是,当LinearLayout的view有weight属性的时候,它也会测量2次,这个时候,就建议使用RelativeLayout了。
层级深度一样的情况下,优先级:FrameLayout、LinearLayout(不带weight)、RelativeLayout。

2,如果布局层数较深,考虑使用RelativeLayout来较少布局层次。

5、ConstraintLayout约束布局

ConstraintLayout 布局是一个支持库,你可以在API9以上的系统上使用它。
具体使用:Android ConstraintLayout 约束布局的使用介绍

它主要就是为了减少布局嵌套过多的问题,来优化布局。
官方文档

6、优化过度绘制

通过手机的开发者选项-调试GPU过度绘制,来打开此功能。再下面讲过度绘制工具使用的时候,我们再看下,怎么查看过度绘制。

我们通过查看过度绘制,一般,我们通过

  • 移除不必要的背景
  • 如果是自定义view的话,我们也可以通过clipRect和clipPath方法来减少绘制的层数
  • android使用主题的时候,window会自带一个背景(通过getWindow().setBackgroundDrawable(null)或theme中使用 android:windowbackground=“null” 来移除)

消除过度绘制,达到优化布局的目的。

7、控件的优化

参考文档

二、Android UI布局优化及分析的工具

调试UI布局优化的工具主要有:

  • 手机自带的GPU过度绘制查看
  • 使用Hierarchy Viewer
  • Layout Inspector 。

Hierarchy Viewer 是 Android Device Monitor 中内置的一种工具,可让您测量布局层次结构中每个视图的布局速度。它可以帮助您查找由视图层次结构导致的性能瓶颈。已经弃用

Layout Inspector 主要是看布局的层级结构及布局属性。它中的大部分功能以前是由 Hierarchy Viewer 和 Pixel Perfect 工具提供的,但这些工具现已弃用。

过度绘制 -开发者选项查看


虽然,Hierarchy Viewer 已经弃用了。但是,Layout Inspector 查看的功能还是比较简单。我们想要看看布局速度,哪里拖慢了速度,还是需要用Hierarchy Viewer 的。


使用 Hierarchy Viewer 分析您的布局

官方文档

使用它来分析您的应用:
1,真机的话,你需要通过ViewServer设置到你的代码里,重新运行应用。
2,打开sdk–>tools–>monitor.bat ,打开了DDMS。(它在AS3.1以上里面的快捷键已经被移除)
打开后的初始界面如下图
Android 性能优化之UI布局优化_第3张图片
3,启动 Hierarchy Viewer

连接设备,启动应用。然后,在菜单栏中,依次选择 Window > Open Perspective,然后点击 Hierarchy View

你就会看到下图这样
Android 性能优化之UI布局优化_第4张图片
如果没有看到,请依次选择 Window > Reset Perspective 以返回默认布局。
功能介绍:

  • Tree View(中央):显示视图层次结构的树状视图。您可以使用鼠标和底部的缩放控件来拖动和缩放树。每个节点都指示它的 View 类名和 ID 名称。
  • Tree Overview(右上方):可让您一览应用的完整视图层次结构。移动灰色矩形可更改 Tree View 中可见的视口。
  • Layout View(右下方):显示布局的线框视图。当前所选视图的轮廓为红色,其父视图为浅红色。
    在此处点击某个视图也会在 Tree View 中选择该视图,反之亦然。

视图层次结构是布局的快照,因此它不会自动更新。要更新层次结构视图,请点击 Reload the view hierarchy 图标 在这里插入图片描述
4,分析布局

4-1,在 Tree View 或 Layout View 中,点击要分析其子级的视图节点(小灰方块)。

4-2, 要开始分析,请点击 Tree View 顶部的 Obtain layout times 图标 在这里插入图片描述
点完后,如下图,就会显示出view的measure,layout,draw的时间了。
Android 性能优化之UI布局优化_第5张图片
所选节点的每个子级都有三个圆点,可以是绿色、黄色或红色。

  • 左侧的是Measure测量阶段
  • 中间圆点表示布局阶段
  • 右点圆点表示执行阶段。

这些圆点大致对应于处理View的测量、布局和绘制阶段。圆点的颜色表示该节点相对于本地系列中所有其他已分析节点的相对性能

  • 绿色表示视图的渲染速度至少比一半的其他视图快。
  • 黄色表示视图渲染速度比其他视图中渲染速度最慢的那一半快。
  • 红色表示视图是渲染速度最慢的那一半视图之一。

解读结果

Hierarchy Viewer 可测量每个节点相对于同级视图的性能,因此分析结果中总是有红色节点(除非所有视图以完全相同的方式执行),并且这并不一定意味着红色节点就是表现不佳(只不过它是本地视图组中最慢的视图而已)

Hierarchy Viewer 会栅格化您的布局以获取时间信息。栅格化是获取高级基元(如圆形或矢量字体)并将其转换为屏幕上的像素的过程。栅格化通常由设备上的 GPU 完成,但对于软件栅格化,渲染是使用普通软件在 CPU 上完成的。这意味着绝对报告时间相对于彼此是正确的,但会随着设备和开发计算机上的总体和不断变化的 CPU 工作负载而变化。因此,它不能反映设备上的实际性能速度,您应该进行多次分析以了解平均测量结果

如果应用的运行速度出乎意料地慢,则红色节点可能有问题。在相对设置中,总有一个最慢的节点;只需确保它是您预期的节点即可。以下示例说明了如何解读红色圆点

  • 查找叶节点中的红色圆点或仅包含少数子级的视图组。(应用的运行速度可能并不缓慢,或者在设备上的运行速度并不慢,但您需要注意这个圆点为何显示为红色。 Systrace 或 Traceview 可以为您提供更多信息。)
  • 如果您有一个包含许多子级的视图组和一个红色测量阶段,请查看相应子级以了解它们的执行情况。
    (带有黄色甚或红色圆点的视图在设备上的执行速度可能并不慢。这正是实际数字的用武之地。 Systrace 或 TraceView 可为您提供更多信息。)
  • 如果层次结构的根视图具有红色测量阶段、红色布局阶段和黄色绘制阶段,那这就是一个比较典型的情况,因为它是所有其他视图的父级,并且只有在子级完成之后其布局才会完成。
  • 如果具有 20 个以上视图的树中的某个叶节点包含红色绘制阶段,则表示存在问题。请检查 onDraw() 方法中是否有不应存在的代码。

使用 Layout Inspector 调试布局

使用 Android Studio 中的 Layout Inspector,您可以将应用布局与设计模型进行比较、显示应用的放大视图,并在运行时检查其布局的细节。如果您的布局是在运行时(而不是完全在 XML 中)构建的并且布局表现出意外行为,这会很有用。

官方文档

要打开 Layout Inspector,请执行以下操作:

1,在连接的设备或模拟器上运行您的应用。
2,依次点击 Tools > Layout Inspector。
3,在显示的 Choose Process 对话框中,选择要检查的应用进程,然后点击 OK。
Android 性能优化之UI布局优化_第6张图片
图 1. Choose Process 对话框

默认情况下,Choose Process 对话框仅列出当前在 Android Studio 中打开且正在设备或模拟器上运行的项目的进程。如果您要检查设备上的其他应用,请勾选 Show all processes。如果您使用的是没有 Google Play 商店的已取得 root 权限的设备或模拟器,那么您会看到所有正在运行的应用。否则,您只会看到正在运行的可调试的应用。

Layout Inspector 会拍摄快照,将其另存为 .li 文件并打开。

拍摄快照(.li)文件打开后,如下图

Android 性能优化之UI布局优化_第7张图片

  • ① View Tree:布局中视图的层次结构。
  • ② Layout Inspector 工具栏:Layout Inspector 的工具。
  • ③ 屏幕截图:设备上显示的应用布局的屏幕截图,其中显示了每个视图的布局边界。
  • ④ Properties Table:选定视图的布局属性。

GPU

官方文档
打开开发者选项–GPU过度渲染-选择过度绘制区域,如下图
Android 性能优化之UI布局优化_第8张图片

打开后,再进入我们的APP,就会显示过度绘制的区域,下面的图,看出左边的是正常的,右边的是有过度绘制的。
Android 性能优化之UI布局优化_第9张图片

过度绘制的颜色说明:
Android 性能优化之UI布局优化_第10张图片
上图,说明了高度绘制的次数及其实现颜色

根据显示出来的过度绘制区域,我们就可以很愉快的去想办法优化拉。

你可能感兴趣的:(Android,UI优化,布局优化,性能优化)