View 的渲染过程

作者: smilEdit | 来源:发表于2019-03-31 20:03 被阅读0次

    View 的显示过程

    如图所示,这就是页面从伊始到显示的整体过程,今天我们将对各步骤进行简略的分析。

    屏幕如何呈像

    像素点

    当我们看屏幕画面时,会觉得那是一张张完整的图片,但是其实每张图片都是由一个个小点点组成的,这些小点被称为像素点。每个像素点都有它们自己的颜色,它们拼接出一幅幅完成的图片。

    每个像素点一般都有 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 则是遵循冯诺依曼架构存储程序,顺序执行,在大规模并行计算能力上极受限制,而更擅长于逻辑控制。

    - Cache: 进行高速数据交换的存储器     

    - DRAM: 进行短暂存储的存储器

    - ALU: 负责计算                     

    - Control: 控制单元

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

    在没有统一的 API 之前,开发者需要在各式各样的图形硬件上编写各种自定义接口和驱动程序,工作量很大,非常的苦逼。在 1990 年 SGI(硅谷图形公司)成为工作站 3D 图形领域领导者,并将其 API 转变话为一项开放标准,即 OpenGL,并领导了 OpenGL 架构审查委员会(OpenGL ARB)的创建。

    这里插一嘴,Direct3D 当时出来不久,Westwood 工作室就根据它开发出了战略游戏《红色警戒》,这游戏好长一段时间内都可谓是风靡全球啊,小编我也是深受其害,无法自己拔。

    垂直同步 Vertical Synchronization

    当我们在使用手机 APP 的过程中,如果发现页面出现卡顿现象,那么极有可能是由于页面在 16ms 内没有被更新导致。为什么是 16ms 呢,根据专家的说法:**人眼与大脑之间的协作无法感知超过 60fps 的画面更新**。60fps 相当于是每秒 60 帧,那么每个页面需要在 1000/60 = 16ms 内更新为其他页面。

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

    在需要显示第二帧的时候,由于此时第二帧还未处理完,Display 中显示的还是第一帧,造成该帧显示时长超过了 16ms 形成了卡顿现象。

    为了使 CPU、GPU 生成帧的速度和 Display 保持一致,都能够在 16ms 内完成工作,Android 系统每 16ms 就会发出一次 VSYNC 信号触发 UI 渲染更新。

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

    在第一个 Display 中,由于 GPU 在处理 B 画面时花费时间过多,导致系统发出 VSync 信号时,GPU 还在处理 B 画面,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 就完成了。

    不过以上只是一个简单控件的一次 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 的子类就都必须要重写该方法了。类似 RelativeLayout、LinearLayout 等布局都肯定重写了该方法,且内部按照各自的规则对子视图进行布局。

    draw

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

    需要注意的一点是,View 的 onDraw() 方法是空的:

    这个最重要的步骤为什么是空的呢? 因为每个视图的内容肯定是不相同,这个部分交由子类根据自身的需要来处理才更加的合理。

    安卓渲染机制 整体流程

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

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

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

    4. 通知 SurfaceFlinger 图层合成

    5. SurfaceFlinger 开始合成图层

    相关文章

      网友评论

        本文标题:View 的渲染过程

        本文链接:https://www.haomeiwen.com/subject/kqguattx.html