做者:个推安卓开发工程师 一七编程
随着科技的发展,各类移动端早已成为人们平常生活中不可或缺的部分,人们使用移动端产品工做、社交、娱乐……移动端界面的流畅性已经成为影响用户体验的重要因素之一。那么你是否思考过移动端所展示的流畅画面是如何实现的呢?缓存
本文经过对移动端View显示过程的简略分析,帮助开发者了解View渲染的逻辑,更好地优化本身的APP。性能优化
上图展现的是一个完整的页面渲染过程。经过上图,咱们能够初步了解每一帧页面从代码布局的编写到展现给使用者,其背后的逻辑是如何一步一步执行的。架构
在电子屏幕中显示的图片,其实都是由一个个“小点”所组成的,这些“小点”被称为“像素点”。每个像素点都有本身的颜色,每一张完整的图片都是由它们相连拼接造成的。并发
每一个像素点通常都有 3 个子像素:红、绿、蓝,根据这三种原色,咱们可以调制出各类各样的颜色。布局
与如今的平板电视不一样的是,之前的黑白电视机或者大背投彩电,老是带着大大的“后背”。“大后背”电视其实就是阴极射线管电视机,俗称显像管电视。其成像原理是电子枪发射出的电子束(阴极射线)经过聚焦系统和偏转系统,射向屏幕上涂有荧光层的指定位置。被电子束轰击的每一个位置,荧光层都会产生一个小亮点,最终小亮点们将会组成一幅幅影像,显示在电视屏幕上。性能
这也是之前大电视机的屏幕都呈圆弧形的缘由。由于越接近圆形,边长到中心的距离越相近,呈像越均匀。那为何当磁铁贴近电视机时,会让电视机的成像出现问题呢?那是由于磁铁会干扰电子束的正常轨迹,而且在贴近屏幕的时候,也可能使得屏幕的荧光层磁化,出现一个个不正常的光斑。优化
下图展现的是摄像机慢放后,电子束的绘制过程。ui
随着科技的不断进步,电视、手机、电脑的体积愈来愈薄,射线管显像方式也逐渐被淘汰。目前在手机市场上占据主流地位的是 LCD 和 OLED 两种屏幕。线程
LCD 全称为 Liquid Crystal Display ,即液晶显示器。OLED 全称为 Organic Light-Emitting Diode ,即有机发光二极管。这二者之间存在显著的差异:
1. 二者成像原理不一样:LCD 是靠白色的背光穿透彩色薄膜显色的,而 OLED 则是靠每一个像素点自行发光。
2. 在耗电量方面:LCD的耗电量较高,即便只显示一个亮点,LCD 的背光源也须要一直发光,并且容易出现漏光现象。而OLED的每一个像素都能独立工做,并且 能够自行发光,所以采用OLED的设备能够制做得更薄,甚至能够弯曲。
3.在制做方面: LCD使用的是无机材料, OLED 则须要使用有机材料,所以 OLED的制做费用更高,而且使用寿命不如 LCD 。
与CPU相对比,GPU的计算单元更多,更擅长大规模并发计算,例如密码破解、图像处理等。CPU 则是遵循冯诺依曼架构存储程序顺序执行,在大规模并行计算能力上,受到的限制更大,所以更擅长逻辑控制。
在没有统一的 API 以前,开发者须要在各式各样的图形硬件上编写各类自定义接口和驱动程序,工做量极大。
1990 年 SGI(硅谷图形公司)成为了工做站 3D 图形领域的领导者,并将其 API 转变为一项开放标准,即 OpenGL。后来,SGI还促成了 OpenGL 架构审查委员会(OpenGL ARB)的建立。
当咱们在使用手机 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 的绘制是从 ViewRootImpl 的 performTraversals() 方法开始的,其总体流程大体分为三步,以下图所示:
控件测量过程从 performMeasure() 方法开始。在该方法中childWidthMeasureSpec 和 childHeightMeasureSpec,分别是用来肯定宽度和高度的。
MeasureSpec 是一个 int 值,它存储着两个信息:低 30 位是 View 的 specSize,高 2 位是 View 的 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 总体流程的方法调用链以下:
在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 的子类便须要重写该方法:
绘制的流程主要以下图所示,该流程也是存在遍历子 View 绘制的过程:
须要注意的是,View 的 onDraw() 方法是空的,这是由于每一个视图的内容都不相同,这个部分交由子类根据自身的须要来处理,才更加合理:
1. APP 在 UI 线程构建 OpenGL 渲染须要的命令及数据;
2. CPU 将数据上传(共享或者拷贝)给 GPU 。(PC 上通常有显存,可是 ARM 这种嵌入式设备内存通常是 GPU 、 CPU 共享内存);
3. 通知 GPU 渲染。通常而言,真机不会阻塞等待 GPU 渲染结束,通知结束后就返回执行其余任务;
4. 通知 SurfaceFlinger 图层合成;
5. SurfaceFlinger 开始合成图层。
移动端技术发展很快,而画面显示优化是一个持续发展的实践课题,贯穿于每一个开发者的平常工做中。将来,个推技术团队将继续关注移动端的性能优化,为你们分享相关的技术干货。