Android---如何同view进行渲染

ViewRootImpl 在 Activity、window 和 View 三者关系之间起着承上启下的作用。一方面,ViewRootImpl 中通过 Binder 通信机制,远程调用 WindowSession 将 View 添加到 Window 中;另一方面,ViewRootImpl 在添加 View 之前,需要调用  requestLayout 方法,执行完整的 View 树的渲染操作。

ViewRootImpl 执行 View 的渲染

屏幕绘制

ViewRootImpl requestLayout 流程。requestLayout 第一个次被调用是在 setView() 方法中。

从名字也能看出,这个方法的主要目的就是请求布局操作。其中包括 View 的测量、布局和绘制等。具体代码如下

注释1处,检测是否为合法线程。一般情况下,就是检测是否为主线程;注释2处,将请求布局标识符设置为 true。这个参数决定了后续是否执行 measure 和 layout 操作。最后执行 scheduleTraversals() 方法,如下

注释1处向主线程消息队列中插入 SyncBarrierMessage。该方法发送了一个没有 target 的 Message 到 queue 中。在 next 方法获取消息时,如果发现没有 target 的 message,则在一定的时间内跳过同步消息,优先执行异步消息。这里通过调用次方法,保证 UI 绘制操作优先执行。注释2处,调用 postCallback 方法。实际上也是发送一个 Message 到主线程消息队列。

postCallback 的执行流程如下

可以看出,最终通过 Handler 发送 MessageQueue 中的 message ,被设置为异步类型的消息。

 mTraversalRunable 是一个实现 Runable 接口的 TraversalRunable 对象,其 run 方法如下

可以看出,在 run() 方法中,调用了 doTraversal() 方法,并最终调用了 performTraversals() 方法。 这个方法就是真正的开始 View 的绘制流程:measure-->layout-->draw。核心代码如下

这是个比较重的方法,只负责做3件事情,即在自定义 view 中常用到的 3 个主要过程:measue、layout、draw。 下面以测量 performMeasure 实现举例。

ViewRootImpl 的 measureHierarchy

View 的测量是一层递归调用,递归执行子 View 的测量工作之后,最后决定父视图的宽和高。但是,这个递归的起源是在哪里呢?

答案就是 DecorView。因为在 measureHierarchy 方法中,最终是调用 performMeasure 方法来进行测量工作的。performMeasure 方法的实现,如下代码所示

在这个方法中,通过 getRootMeasureSpec() 方法获取了根 View 的 MeasureSpec() 方法。实际上 MeasureSpec 的宽高此处获取的值是 Window 的宽高。

ViewRootImpl 的 performMeasure

这个方法很简单,只是执行了 mView 的 measure 方法。这个 mView 就是 DecorView。其 DecorView 的 measure 方法中会调用 onMeasure 方法,而 DecorView 是继承 FramLayout 的。因此,最终会执行 FramLayout 的 onMeasure 方法,并递归调用子 View 的 onMeasure 方法。

performLayout()  也是类似的过程,这里不再分析。

ViewRootImpl 的 performDraw

从图中可以看出,在 performDraw 方法中调用的 ViewRootImpl 的 draw 方法。在 draw 方法中进行 UI 绘制操作,Android 系统提供了两种绘制方式。图中1处标识 App 开启了硬件加速功能,所以会启动硬件加速绘制;图中2处表示使用软件绘制,

软件和硬件绘制

ViewRootImpl 中有一个非常重要的对象 Suface,之所以说 ViewRootImpl 的一个核心功能是负责 UI 渲染。原因就在于,在 ViewRootImpl 中会将在 draw 方法中绘制的 UI 元素,绑定到这个 Surface 上。如果说 canvas 是画板,那么 surface 就是画板上的画纸。Surface 中的内容最终会被传递给底层的 SurfaceFlinger,最终将 Surface 中的内容进行合成并显示在屏幕上。

软件绘制 drawSoftware

图中1处,就是调用 DecorView 的 draw 方法。将 UI 元素绘制到画布 canvas 对象中;图中2处,请求将 canvas 中的内容显示到屏幕上。实际上就是将 canvas 中的内容提交给 SurfaceFlinger 进行合成处理。默认情况下,软件绘制没有采用 GPU 渲染的方式,drawSoftware 工作完全由 CPU 来完成。

DecorView 并没有复写 draw 方法,因此,实际上是调用顶层 View 的 draw 方法,如下代码

图中1处绘制 view 的背景;图中2处绘制 view 的自身内容;图中3处表示对 draw 事件进行分发,在 view 中是空实现,实际调用的是 viewGroup 中的实现,并递归调用子 View 的 draw 事件。

启用硬件加速

可以在 ViewRootImpl 的 draw 方法中,通过如下方法判断是否启用硬件加速

我们可以在 AndroidManifest.xml 清单文件中,指定 Application 或者某一个 Activity 支持硬件加速

此外,还可以进行粒度更小的硬件加速设置,比如设置某个 View 支持硬件加速

之所以会有这么多级的支持区分,主要是因为并不是所有的 2D 绘制操作都支持硬件加速。

硬件加速优势

硬件加速能够提高 UI 渲染的性能。在 ViewRootImpl 的 draw 方法中,mThreadedRenderer 是 ThreadedRenderer 类型

其 darw 方法具体如下

图中1处就是硬件加速的特殊之处,通过 updateRootDisplayList() 方法,将 View 视图抽象成一个 RendererLoad 对象,并构建 View 的 drawOp 树;图中2处通知 renderThread 进行绘制操作。

renderThread 是一个单例线程,每个进程最多只有一个硬件渲染线程,这样就不会存在多线程并发访问冲突问题。updateRootDisplayList() 具体如下

Android 硬件加速过程中,View 视图被抽象成 RenderNode 节点。View 中的绘制操作都会被抽象成一个个 DrawOp。比如,View 中 drawLine,构建过程中就会被抽象成一个 DrawLineOp。drawBitmap 操作会被抽象成 DrawBitmapOp,每个子 View 的绘制被抽象成 DrawRenderNodeOp,每个 DrawOp 有对应的 OpenGL 绘制命令。

上图中1处遍历 view 递归构建 DrawOp;2处根据 canvas 将所有的 operation 进行缓存操作。所有的 DrawOp 对应 OpenGL 命令构建完成后,就需要使用 RenderProxy 向 RenderThread 发送消息。请求 OpenGL 线程进行渲染。整个渲染过程是通过 GPU 并在不同线程绘制渲染图像。因此整个流程会更加流程。

View 的两种刷新方式

Invalidate 轻量级刷新

通过 invalidate 来刷新 View,与 requestLayout 的区别在于它不一定会触发 View 的 measure 和 layout 的操作,多数情况下只会执行 draw 操作。在 View 的 measure 方法中,有如下几行代码

可以看出,如果要触发 onMeasure 方法,需要对 View 设置 PFLAG_FORCE_LAYOUT 的标志位。而这个标志位在 requestLayout 方法中被设置。 invalidate 并没有设置此标志位。

再看一下 onLayout 方法

可以看出当 view 的位置发生改变或者添加 PFLAG_FORCE_LAYOUT 标志位,onLayout 才会被执行。

当调用 invalidate 方法时,如果 View 的位置并没有发生改变,则 View 不会触发重新布局的操作。

postInvalidate

postInvalidate 在面试中经常被问道,实际开发中使用频率也是较高的。invalidate 与 postInvalidate 两者之间的区别是 invalidate 是在 UI 线程调用,postInvalidate 是在非 UI 线程调用。postInvalidate 实现如下

最终还是在 ViewRootImpl 中进行操作。

ViewRootImpl 的 dispatchInvalidateDelayed 在 非 UI 线程中,通过 Handler 发送了一个延时 Message。

因为 Handler 是在主线程中创建的,所以 handleMessage 最终会在主线程中被执行。方法如下

上图中的 msg.obj 就是发生 postInvalidate 的 view 对象。可以看出,最终还是回到了 UI 线程,执行了 View 的 invalidate 方法。

个人理解,做过 android 开发的都知道,只有 UI 线程才可以刷新 view 控件。但是,事实却并非如此。在 ViewRootImpl 中对 view 进行刷新时,会检测当前线程的合法性,下图中 mThread 是被赋值为当前线程。而 ViewRootImpl 是在 UI 线程中被创建的,因此只有 UI 线程可以进行 view 刷新。但是,如果我们能在非 UI 线程中创建 ViewRootImpl,并通过这个 ViewRootImpl 进行 view 的添加和绘制操作,那么后续理论上也是可以在非 UI 线程中刷新 view 控件的。只是维护成本较高,很少有人去做这件事情。

总结

\bullet 主要介绍了 ViewRootImpl 是如何执行 View 的渲染操作的。其中核心方法在 performTraversals 方法中会按顺序执行 measure->layout->draw 操作。

\bullet 介绍了软件绘制和硬件绘制的区别

\bullet 介绍了 View 刷新的两种方式 Invalidate 和 postInvalidate。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/174898.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vscode打开settings.json方法

cmd shift p,输入setting Open Workspace Settings 也会打开UI设置界面; Open User Settings (JSON) 会打开用户设置 settings.json 文件; Open Workspace Settings (JSON) 会打开工作区设置 settings.json 文件 vscode存在两种设置 sett…

Rust编程基础之变量与可变性

1.Rust变量 在Rust语言中, 变量默认是不可改变的(immutable), 这是Rust提供给我们的众多优势之一, 让我们可以充分利用Rust提供的安全性和简单并发性来编写代码。 当变量不可变时, 一旦值被绑定在一个名称上, 就不能改变这个值。下面是一段代码的例子: fn main() {let x 1;…

Panda3d 介绍

Panda3d 介绍 文章目录 Panda3d 介绍Panda3d 的安装Panda3d 的坐标系统介绍Panda3d 的运行Panda3d 加载一个熊猫父节点和子节点之间的关系 验证Panda3d 的坐标系统X 轴的平移Y 轴的平移Z 轴的平移X 轴的旋转Y 轴的旋转Z 轴的旋转 Panda3D是一个3D引擎:一个用于3D渲染和游戏开发…

[Linux]线程池

[Linux]线程池 文章目录 [Linux]线程池线程池的概念线程池的优点线程池的应用场景线程池的实现 线程池的概念 线程池是一种线程使用模式。线程池是一种特殊的生产消费模型,用户作为生产者,线程池作为消费者和缓冲区。 线程过多会带来调度开销&#xff0c…

Generalized Zero-Shot Learning With Multi-Channel Gaussian Mixture VAE

L D A _{DA} DA​最大化编码后两种特征分布之间的相似性 辅助信息 作者未提供代码

1400*C. Element Extermination(贪心规律)

Problem - 1375C - Codeforces 解析: 可以发现,最左端的数字,无论删除自己还是下一个,这个位置的值都不会变小。 同理,最右端位置的值都不会变大。 所以当最后剩余两个数字的时候,只有左端小于右端数字&…

【经典面试】87 字符串解码

字符串解码 题解1 递归(程序栈)——形式语言自动机(LL(1)) : O(S)另一种递归(直观) 题解2 2个栈(逆波兰式)1个栈(参考官方,但是不喜欢) 给定一个经过编码的字符串,返回它解码后的字符串。 编码规则为: k[encoded_string],表示其中方括号内部的…

深入探究深度学习、神经网络与卷积神经网络以及它们在多个领域中的应用

目录 1、什么是深度学习? 2、深度学习的思想 3、深度学习与神经网络 4、深度学习训练过程 4.1、先使用自下上升非监督学习(就是从底层开始,一层一层的往顶层训练) 4.2、后自顶向下的监督学习(就是通过带标签的数…

文件管理怎么清内存?效率提升一倍

定期清理文件管理可以释放存储空间和提高系统性能。随着时间的推移,手机中可能会存储大量无用的数据,例如缓存、垃圾文件等,导致系统运行缓慢。那么如何清理文件管理的内存呢?下面介绍三种方法。 一、搜索无用的文件夹进行清理 1…

【Bug—eNSP】华为eNsp路由器设备启动一直是0解决方案!

目录 一、项目场景 二、问题描述 三、原因分析 四、解决方案 注意&#

(二开)Flink 修改源码拓展 SQL 语法

1、Flink 扩展 calcite 中的语法解析 1)定义需要的 SqlNode 节点类-以 SqlShowCatalogs 为例 a)类位置 flink/flink-table/flink-sql-parser/src/main/java/org/apache/flink/sql/parser/dql/SqlShowCatalogs.java 核心方法: Override pu…

高阶数据结构学习 —— 图(1)

文章目录 1、并查集2、了解图3、邻接矩阵4、压缩路径5、基本概念6、邻接表 1、并查集 并查集是一个森林,是由多棵树组成的。这相当于整套数据,分成多个集合。查找有交集的集合们,会把它们合并起来,所以叫并查集。 一开始拿到的是…

电脑突然提示找不到msvcp140.dll怎么办,解决msvcp140.dll丢失的办法

当我们在电脑上运行某些软件或游戏时,可能会遇到一个常见的错误消息:“找不到msvcp140.dll”。出现这样的情况通常意味着系统缺少一个重要的动态链接库文件,而这可能会导致程序无法正常启动。如果你现在遇到了这个问题,哪有可以用…

3D LUT 滤镜 shader 源码分析

最近在做滤镜相关的渲染学习,目前大部分 LUT 滤镜代码实现都是参考由 GPUImage 提供的 LookupFilter 的逻辑,整个代码实现不多。参考网上的博文也有各种解释,参考了大量博文之后终于理解了,所以自己重新整理了一份,方便…

最新Microsoft Edge浏览器如何使用圆角

引入 最近我看了edge官方的文档,里面宣传了edge的最新UI设计,也就是圆角,但是我发现我的浏览器在升级至最新版本之后,却没有圆角 网上有很多人说靠实验性功能即可解锁,但是指令我都试过了,每次都是搜索无结…

OpenLayers入门,OpenLayers从vue的assets资源路径加载TopoJson文件并解析数据叠加到地图上,以加载世界各国边界为例

专栏目录: OpenLayers入门教程汇总目录 前言 本章以加载世界各国边界的TopoJson格式数据为例,讲解如何使用OpenLayers从vue的assets资源路径加载TopoJson文件并解析数据叠加到地图上。 GeoJson介绍 GEOJSON是gis地图中常用的数据格式,制作地图时用于存储各种地理数据,使…

一站式解决安全问题

端玛科技致力于攻克困难的应用软件安全问题,我们的解决方案以安全标准、安全教育和安全风险评估三大支柱为安全SDLC的基础,这三大支柱相互依存,创建了一个可重复的、安全的软件开发生态系统。 主要业务范围:关注整个软件开发过程…

基于SpringBoot的在线笔记系统

技术介绍 🔥采用技术:SpringSpringMVCMyBatisJSPMaven 🔥开发语言:Java 🔥JDK版本:JDK1.8 🔥服务器:tomcat 🔥数据库:mysql 🔥数据库开发工具&…

七、【图像添加水印】

文章目录 一、制作水印1、先新建图层2、新建文字图层并调好水印文字的大小与角度3、添加图层样式4、添加定义图案 二、添加水印 一、制作水印 1、先新建图层 2、新建文字图层并调好水印文字的大小与角度 3、添加图层样式 1、打开“描边” 2、选择“颜色” 4、添加定义图案 二…

spring-代理模式

代理模式 一、概念1.静态代理2.动态代理 一、概念 ①介绍 二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标 方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不…