AndroidAndroid性能优化与实践

「深入理解Android布局优化 1」-布局的加载流程与绘制原理

2019-06-28  本文已影响0人  林栩link

前言

本篇文章是《深入理解Android布局优化》系列文章的第一篇。系列的主要目的是希望将Android开发中涉及布局优化的部分做一次系统的归纳、总结和学习。本系列文章包含理论基础常见工具项目实践三个部分。

理论基础:「深入理解Android布局优化 1」-布局的加载流程与绘制原理,主要讲解布局的加载流程与绘制原理,从源码上发现布局的性能瓶颈。

常见工具:「深入理解Android布局优化 2」-常见工具的使用,主要讲解Android布局优化时各种常见工具的使用。

项目实践:以一个实际的APP为例,将学习到的理论和工具,实际运用到Android开发中。

本文中实践时使用的项目地址:https://github.com/linux-link/Fan,可以先阅读这篇文章了解这个项目一次组件化与Android Jetpack的实践

本篇属于三个部分中的理论基础部分。

目录

正文

一、Android系统的绘图机制

Android系统每隔16ms就重新绘制一次Activity,这就要求UI界面必须在16ms内完成屏幕刷新的全部逻辑操作,这样才能达到每秒60fps,然而这个fps是由手机硬件所决定,现在大多数手机屏幕刷新率是60Hz(赫兹是国际单位制中频率的单位,它是每秒中的周期性变动重复次数的计量),也就是说我们有16ms(1000ms/60fps=16.66ms)的时间去完成每帧的绘制逻辑操作,如果超过了就会出现所谓的丢帧。实际开发中复杂的界面往往在16ms内完成全部绘制,但是尽量降级UI的绘制时间,总是可以有效的降低卡顿感。

对于Android系统的硬件绘图机制,并非布局优化的重点,有兴趣的可以翻看文末的参考资料。

二、Activity的组成

一个Activity层级结构图,如下所示


它有点像洋葱圈一层包裹着一层,下面我们就来逐个介绍一下。

通过这张层级关系图,我们就大致明白了Activity层级结构,理解Activity的页面层级结构非常的重要,它不仅与性能优化息息相关,而且也可以帮助我们理解Android触摸事件的分发机制。

触摸事件的分发机制,经常涉及到自定义的View,自定义View其实也是我们在布局优化时常用的手段之一。

这里重新画了一张“洋葱圈”一样的层级结构图,来帮助你理解触摸事件的向上传递机制。这张图很形象的解释了触摸事件是如何从Activity中开始传递,又是如何回到Activity中的。关于触摸事件的分发具体的分发机制,请参阅其他文章,这里就不再细说了。

洋葱圈结构图

三、布局文件的加载流程

在Android开发中setContentView是我们最常用的将xml格式的布局文件绘制到activity中的方法。那么布局文件是如何绘制到Activity当中的呢?通过阅读setcontentView的源代码,可以发现布局文件的加载大致分为,读取xml创建View对象两个流程。

image

1.读取xml布局文件

2.根据xml布局文件,创建对应的View或ViewGroup

简单梳理一遍View的加载流程,你会发现,到这里Android系统就完成了把xml布局文件转换成具体的View对象,在这其中我们可以看到至少两个会影响性能地方,一个是loadXmlResourceParser(),把xml读取到内存中这样的IO操作会影响性能,另一个则是createView(),通过反射创建对象会影响性能。这两个地方将是我们日后进行布局优化的重点。

目前为止Activity还是看不任何东西的,因为创建的View还没有开始绘制。接下来我们就来看看View的绘制流程。

四、View的绘制流程

View绘制流程主要分为三个部分:measure、layout、draw,分别对应测量、布局和绘制,其中measure确定View的测量宽高,layout确定View最终宽高和四个顶点的位置,draw负责将view最终绘制到屏幕上。

ViewGroup的绘制流程与View大体相同,唯一的区别就是,View只需要绘制它自己,而ViewGroup不仅要绘制它自己还要绘制它的子View。下面我们就以ViewGroup为例,简单从源代码的角度来看一下这三个流程:

1.Measure与MeasureSpec

测量过程通过measure()来实现,是View树自顶向下的遍历,每个View在循环过程中将尺寸细节向下传递,当测量过程完成之后,所有的View也就都存储了自己尺寸。

ViewGroup是一个抽象类,它并没有重写View的measure()方法,它在内部会调用measureChildren(),然后再去循环调用View的measure()方法。

measure()方法需要传入两个参数widthMeasureSpec和heightMeasureSpec。

protected void measure(int widthMeasureSpec, int heightMeasureSpec)

表面上看widthMeasureSpec和heightMeasureSpec是int的数字,它们是父类传过来的给当前View的一个建议值(这个建议值是我们在XML中设定的),实际上是由mode+size组成的。将widthMeasureSpec转换为二进制后,它是一个32位的数字,前两位表示模式(mode),后30位表示数值(size)。

mode共有三种模式,分别是

上述3中模式在自定义view时非常有用,当模式是EXACTLY时,我们是直接使用父类的建议值,当模式是AT_MOST时,我们则需要自己设定View的大小,因为用户没有规定这个View有多大。

2.layout

Layout的作用是ViewGroup用来确定子View的位置。在ViewGroup中调用layout方法确定位置确定后,它会在onLayout中遍历所有子View的layout方法,子View的layout又会调用onLayout方法,确定自己的位置。

layout的大致流程如下:

首先通过setFrame设定View的四个顶点位置;

然后调用onLayout方法,在这里面调用每个子View的layout

3.draw

draw的过程是最简单的,它的作用就是把View绘制到屏幕上,

 public void draw(Canvas canvas) {}

在draw方法中主要完成了一下几个任务:

在Android中draw方法会被频繁的调用,例如:按home键app进入后台,当我们在回到APP时,即使APP没有被销毁,当前界面下View组件的draw方法也会被调用。

简单了解了View的绘制流程后,不难看出这里面也存在至少两个性能瓶颈,一个是measure和layout过程中会循环调用子View的方法,其实这就决定了布局文件不能嵌套过深,否则循环的时间复杂度会很高。另一个是View的draw方法会被频繁的调用,对于这类频繁调用的方法,我们不能在其中创建对象或执行耗时操作,否则会产生剧烈的内存抖动和页面卡顿。

五、布局优化的简单建议

通过上面的分析,我们对布局的组成,加载以及绘制有了一定的了解,现在再来看看常见的布局优化建议,相信你一定对这些建议有了进一步的认识。

六、总结

本篇文章梳理了一下Activity的组成,一个xml的布局是如何加载到界面中,以及是如果绘制出来的,最后总结了一下目前的布局优化建议。但是在实际的开发中往往很难让所有人完全遵守布局优化的建议,下一篇我们来讲讲布局优化时常用的工具「深入理解Android布局优化 2」-常见工具的使用,通过工具来帮助我们发现UI的性能问题。

参考资料

Android进阶——性能优化之布局渲染原理和底层机制详解(四)

《Android开发艺术探索》 任玉刚著

上一篇下一篇

猜你喜欢

热点阅读