作者:个推安卓开发工程师 一七

随着科技的发展,各种移动端早已成为人们日常生活中不可或缺的部分,人们使用移动端产品工作、社交、娱乐……移动端界面的流畅性已经成为影响用户体验的重要因素之一。那么你是否思考过移动端所展现的流畅画面是如何实现的呢?

本文通过对移动端View显示过程的简略分析,帮助开发者了解View渲染的逻辑,更好地优化自己的APP。

上图展示的是一个完整的页面渲染过程。通过上图,我们可以初步了解每一帧页面从代码布局的编写到展示给使用者,其背后的逻辑是如何一步一步执行的。


屏幕如何呈像

像素点

在电子屏幕中显示的图片,其实都是由一个个“小点”所组成的,这些“小点”被称为“像素点”。每一个像素点都有自己的颜色,每一张完整的图片都是由它们相连拼接形成的。

每个像素点一般都有 3 个子像素:红、绿、蓝,根据这三种原色,我们能够调制出各种各样的颜色。

大电视机

与现在的平板电视不同的是,以前的黑白电视机或者大背投彩电,总是带着大大的“后背”。“大后背”电视其实就是阴极射线管电视机,俗称显像管电视。其成像原理是电子枪发射出的电子束(阴极射线)通过聚焦系统和偏转系统,射向屏幕上涂有荧光层的指定位置。被电子束轰击的每个位置,荧光层都会产生一个小亮点,最终小亮点们将会组成一幅幅影像,显示在电视屏幕上。

这也是以前大电视机的屏幕都呈圆弧形的原因。因为越接近圆形,边长到中心的距离越相近,呈像越均匀。那为什么当磁铁贴近电视机时,会让电视机的成像出现问题呢?那是因为磁铁会干扰电子束的正常轨迹,并且在贴近屏幕的时候,也可能使得屏幕的荧光层磁化,出现一个个不正常的光斑。

下图展示的是摄像机慢放后,电子束的绘制过程。

LCD 和 OLED

随着科技的不断进步,电视、手机、电脑的体积越来越薄,射线管显像方式也逐渐被淘汰。目前在手机市场上占据主流地位的是 LCD 和 OLED 两种屏幕。

LCD 全称为 Liquid Crystal Display ,即液晶显示器。OLED 全称为 Organic Light-Emitting Diode ,即有机发光二极管。这两者之间存在显著的差别:

1. 两者成像原理不同:LCD 是靠白色的背光穿透彩色薄膜显色的,而 OLED 则是靠每个像素点自行发光。

2. 在耗电量方面:LCD的耗电量较高,即使只显示一个亮点,LCD 的背光源也需要一直发光,而且容易出现漏光现象。而OLED的每个像素都能独立工作,而且 可以自行发光,因此采用OLED的设备可以制作得更薄,甚至可以弯曲。

3.在制作方面: LCD使用的是无机材料, OLED 则需要使用有机材料,因此 OLED的制作费用更高,并且使用寿命不如 LCD 。


图形显示核心 GPU

与CPU相对比,GPU的计算单元更多,更擅长大规模并发计算,例如密码破解、图像处理等。CPU 则是遵循冯诺依曼架构存储程序顺序执行,在大规模并行计算能力上,受到的限制更大,因此更擅长逻辑控制。


应用程序编程接口 API (OpenGL)

在没有统一的 API 之前,开发者需要在各式各样的图形硬件上编写各种自定义接口和驱动程序,工作量极大。

1990 年 SGI(硅谷图形公司)成为了工作站 3D 图形领域的领导者,并将其 API 转变为一项开放标准,即 OpenGL。后来,SGI还促成了 OpenGL 架构审查委员会(OpenGL ARB)的创建。

垂直同步 Vertical Synchronization

当我们在使用手机 APP 的过程中,发现页面出现卡顿现象,那么极有可能是页面没有在 16ms 内更新导致的。实际上,人眼与大脑之间的协作无法感知超过 60fps 的画面更新。60fps 相当于是每秒 60 帧,那么每个页面需要在 1000/60 = 16ms 内更新为其他页面,才不会让我们感受到页面的卡顿。

而在没有 VSync 的情况下可能会出现以下情况:

如上图所示,在没有 VSync 的情况下,会出现需要显示第二帧时,其尚未处理完成的情况,因此Display 中显示的仍是第一帧。这会造成该帧显示时长超过16ms,从而导致页面卡顿的现象。

为了使 CPU、GPU 生成帧的速度与 Display 保持一致,Android 系统每 16ms 就会发出一次 VSYNC 信号,触发 UI 渲染更新。

从上图中我们可以看出,每隔 16ms ,安卓会发出一个 VSync 信号,收到信号后 CPU 开始处理下一帧的的内容,GPU 在 CPU 处理结束之后,将会进行光栅化,此时屏幕上显示的是上一帧已经处理完成的页面。如此反复,就可以在页面中展示一幅幅的指定画面。而确保画面流畅的前提是CPU 和 GPU 处理一帧所花费的时间不能超过 16 ms,否则就会出现以下情况:

当CPU 和 GPU 处理一帧的时间超过了16 ms时,在第一个 Display 中,由于 GPU 处理 B 画面的时间过长,导致系统发出 VSync 信号时, Display不能及时地显示出 B 画面,而重复显示A页面,造成卡顿。

此外,在第二个 Display 中,由于 A Buffer 还在被 Display 所使用,不能在收到 VSync 信号后开始处理下一帧的页面,导致该时间段内 CPU 的闲置。为了避免这种时间的浪费,三缓存机制由此出现:

如上图所示,在三缓存机制中,当 A 缓存被 Display 使用、B 缓存被 GPU 处理时,系统会发出 Vsync 信号,并加入新的缓存 C ,用来缓存下一帧的内容。这种方式虽然不能完全避免 A页面的重复显示,但是能够让后面页面的显示更加平滑。


View 的绘制流程

View 的绘制是从 ViewRootImpl 的 performTraversals() 方法开始的,其整体流程大致分为三步,如下图所示:

measure

控件测量过程从 performMeasure() 方法开始。在该方法中childWidthMeasureSpec 和 childHeightMeasureSpec,分别是用来确定宽度和高度的。

MeasureSpec 是一个 int 值,它存储着两个信息:低 30 位是 View 的 specSize,高 2 位是 View 的 specMode。

specMode 有三种类型:

1.UNSPECIFIED

父视图对子视图没有任何限制,可以将视图按照开发者的意愿设置成任意的大小,在一般开发过程中不会用到。

2.EXACTLY

父视图为子视图指定一个确切的尺寸,该尺寸由 specSize 的值来决定。

3.AT_MOST

父视图为子视图指定一个最大的尺寸,该尺寸的最大值是 specSize。

观察 View 的 measure() 方法,可以发现该方法是被 final 修饰的,因此 View 的子类只能够通过重载 onMeasure() 方法来完成自己的测量逻辑。

在 onMeasure() 方法中:

调用 getDefaultSize() 方法来获取视图的大小:

该方法中的第二个参数 measureSpec 是从 measure() 方法中传递过来的:通过 getMode() 和 getSize() 解析获取其中对应的值,再根据 specMode 给最终的 size 赋值。

不过以上只是一个简单控件的一次 measure 过程,在真正测量的过程中,由于一个页面往往包含多个子 View ,所以需要循环遍历测量,在 ViewGroup 中有一个 measureChildren() 方法,就是用来测量子视图的:

measure 整体流程的方法调用链如下:

layout

在performTraversals() 方法的测量过程结束后,进入 layout 布局过程:

performLayout(lp,desiredWindowWidth,desiredWindowHeight);

该过程的主要作用即根据子视图的大小以及布局参数,将相应的 View 放到合适的位置上。

host.layout(0,0,host.getMeasuredWidth(),host.getMeasuredHeight());

如上,layout() 方法接收了四个参数,按照顺时针,分别是左上右下。该坐标针对的是父视图,以左上为起始点,传入了之前测量出的宽度和高度。之后,让我们进入到 layout() 方法中观察:

我们通过 setFrame() 方法给四个变量赋值,判断 View 的位置是否变化以及是否需要重新进行 layout,而且其中还调用了 onLayout() 方法。

在进入该方法后,我们可以发现里面是空的,这是因为子视图的具体位置是相对于父视图而言的,所以 View 的 onLayout 为空实现。

再进入 ViewGroup 类中查看,我们可以发现,这其实是一个抽象的方法,在这样的情况下, ViewGroup 的子类便需要重写该方法:

draw

绘制的流程主要如下图所示,该流程也是存在遍历子 View 绘制的过程:

需要注意的是,View 的 onDraw() 方法是空的,这是因为每个视图的内容都不相同,这个部分交由子类根据自身的需要来处理,才更加合理:


安卓渲染机制的整体流程

1. APP 在 UI 线程构建 OpenGL 渲染需要的命令及数据;

2. CPU 将数据上传(共享或者拷贝)给 GPU 。(PC 上一般有显存,但是 ARM 这种嵌入式设备内存一般是 GPU 、 CPU 共享内存);

3. 通知 GPU 渲染。一般而言,真机不会阻塞等待 GPU 渲染结束,通知结束后就返回执行其他任务;

4. 通知 SurfaceFlinger 图层合成;

5. SurfaceFlinger 开始合成图层。


总结

移动端技术发展很快,而画面显示优化是一个持续发展的实践课题,贯穿于每个开发者的日常工作中。未来,个推技术团队将继续关注移动端的性能优化,为大家分享相关的技术干货。

浅谈移动端 View 的显示过程的更多相关文章

  1. 浅谈flask源码之请求过程

    更新时间:2018年07月26日 09:51:36   作者:Dear.   我要评论   这篇文章主要介绍了浅谈flask源码之请求过程,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随 ...

  2. Activtiy完全解析(三、View的显示过程measure、layout、draw)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/52840065 本文出自:[openXu的博客]   在Activity完全解析的第一篇文章A ...

  3. Ajax概述及浅谈其与服务器的交互过程

    概念: 首先AJAX不只是一个特定的客户端技术,更应算是一种技巧.Ajax技术的核心操作是用XmlHttpRequest(下称XHR)对象进行异步数据处理. 所谓异步,即通过 AJAX,JavaScr ...

  4. 浅谈移动端之touch事件--手指的滑动事件

    今天台风‘海马’袭击深圳,全市停工.现分享一篇关于touch的文章,望指教! 原理: 当开始一个touchstart事件的时候,获取此刻手指的横坐标startX和纵坐标startY: 当触发touch ...

  5. 浅谈移动端适配-rem

    对于移动端开发来说,无可避免的就是直面各种设备不同分辨率和不同DPR(设备像素比)的问题,在此忽略其他兼容性问题的探讨. 一. 移动端开发有关于像素的概念: 1.设备像素(dp),也叫物理像素.指设备 ...

  6. 浅谈移动端设备标识码:DeviceID、IMEI、IDFA、UDID和UUID

    ---恢复内容开始--- 转:https://www.jianshu.com/p/38f4d1a4763b [心路历程] 最近刚好在思考工作中统计数据所用的标识码产生的数据误差到底有多大,借此机会几番 ...

  7. 浅谈移动端中的视口(viewport)

    在 PC 端,视口指的是浏览器的可视区域,其宽度和浏览器窗口的宽度保持一致.在 CSS 标准文档中,视口也被称为初始包含块,它是所有 CSS 百分比宽度推算的根源,给 CSS 布局限制了一个最大宽度. ...

  8. 浅谈移动端三大viewport

    我们通常在写移动端页面时,往往都会在html页面中加入这样一段话 <meta name="viewport" content="width=device-width ...

  9. 浅谈移动端设备标识码:DeviceID、IMEI、IDFA、UDID和UUID -费元星

    在公司做数据分析的时候,发现NA端有很多ID,所有来系统的理解一下,有问题大家多指出   [心路历程] 最近刚好在思考工作中统计数据所用的标识码产生的数据误差到底有多大,借此机会几番搜索资料+请教大神 ...

随机推荐

  1. 微信小程序 Echarts 异步数据更新

    微信小程序 Echarts 异步数据更新的练习,被坑了很多次,特作记录. 作者:罗兵 地址:https://www.cnblogs.com/hhh5460/p/9989805.html 0.效果图   ...

  2. Hadoop日记Day16---命令行运行MapReduce程序

    一.代码编写 1.1 单词统计 回顾我们以前单词统计的例子,如代码1.1所示. package counter; import java.net.URI; import org.apache.hado ...

  3. springboot项目生成jar包(带静态资源)方法

    [Maven]在pom.xml文件中使用resources插件的小作用 不过war包比较实用,毕竟独立的tomcat比较好控制

  4. 老项目迁移到 springboot 过程

    打算把detectx迁移,毕竟springboot更适合它, 首先我是用的快速建立的项目,springboot版本为 1.5.19.RELEASE ,官网查了下,这个是GA稳定生产环境版本 然后如果要 ...

  5. service手动实例化(new)导致类中的spring对象无法注入的问题解决

    下面说的这个画横线的可能是错误的,因为我之前用controller继承父类的注解对象的时候成功了,所以可能这次的唯一原因就是 不该把本该从ioc容器中拿出的对象通过new的方式实例化,至于继承注解对象 ...

  6. 使用 Vue.js 2.0+ Vue-resource 模仿百度搜索框

    使用 Vue.js 2.0 模仿百度搜索框 <!DOCTYPE html> <html> <head> <meta charset="utf-8&q ...

  7. 开源微信Http协议Sdk【实现登录/获取好友列表/修改备注/发送消息】

    基于微信Http协议封装的一个Sdk,目前实现了以下功能:. 1:扫码登录(检测二维码扫描状态) 2:获取最近联系人.群组.所有联系人 3:修改好友备注 4:给好友发送消息 暂且这么多,也没多余的时间 ...

  8. SSM整合配置(Spring+Spring MVC+Mybatis)

    一.配置准备   通过Maven工程,在eclipse中整合SSM,并在Tomcat服务器上运行 在进行配置前,先理清楚要配置哪些文件,如图,除web.xml外,其余三个配置文件名称均可自定义: 如图 ...

  9. 一次VB汇编中看-溢出计算

    图文记录 一.观察程序特点和运行逻辑 带弹窗 是VB开发的 需要用户名和注册码 有弹框 具备了很简单的特点…… 错误弹框,如图 二.定位 弹窗内容入手,搜索关键字定位到关键跳,nop掉或者je改jne ...

  10. docker-compose编排

    创建并启动容器 docker-compose up -d 备注: -d 后台启动并运行容器 前提是你在执行该命令的时候已经编写好了docker-compose.yml文件,在这个文件的当前目录执行上述 ...