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.mzph.cn/news/124447.shtml

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

相关文章

树莓派 Qt中 QCameraInfo 无法使用

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、QCameraInfo 是什么?二、使用步骤1.测试代码2.解决方案2.1输入命令2.2输出 3. 成功打印了摄像头的信息 总结 前言 提示:这里可以添…

Spring的条件注解,一篇文章盘得清清楚楚明明白白

前言 在Spring中,条件注解可根据特定的条件来决定是否创建或配置Bean,这些条件可以基于类、属性、环境等因素。通过使用条件注解,我们可以在Spring容器中更加灵活地管理和控制组件的创建和注入,帮助我们更加灵活地管理和控制Bean…

51单片机-点阵屏led

代码配置 这样就能选择每一列哪个亮了 进行位选,段清零,这样就不会影响多列同时了 实现动画 1、使用文字摸提取文件,提取图案的16进制表示数组 offest作为偏移量,count作为计时。count10,偏移量加1,就相当于得到下一…

毕业设计基于SpringMVC+Mybatis+Bootstrap的电影院管理系统源码+数据库

<<电影院管理系统>> 电影院管理系统&#xff1a;SpringMVCJSPTomcatMybatisBootstrapJqueryAnimateCSSLayerJS 项目部署&#xff1a;该项目是IDEA版本&#xff0c;Maven项目 前端依赖&#xff1a; Bootstrap-3.4.1Animate.css- 4.1.1Jquery-3.6.0Layer-v3.5.1B…

自学爬虫—作业1—requests模块

视频&#xff1a; 要求&#xff1a; 肯德基地址查询&#xff0c;爬某个关键字&#xff0c;获取下面的所有page的信息&#xff0c;存到一个json或者txt。 代码&#xff1a; 关键点&#xff0c;&#xff08;1&#xff09;每一个ajax的请求第一个键值对就是所有获得的地址的总数…

oracle rac-归档满处理

有客户反馈数据库无法使用了&#xff0c;客户手动启动报错如下 SQL> startup; ORACLE instance started. Total System Global Area 2.6924E10 bytes Fixed Size 2265984 bytes Variable Size 1.3959E10 bytes Database Buffers 1.2952E10 bytes R…

Python语言: 切片的使用

切片的本质&#xff1a;通过切片来截取指定的元素&#xff0c;形成一个新的容器。 切片的具体阐释&#xff1a; 此切片非切片面包的切片&#xff0c;而是python语言中的切片。切片&#xff1a;顾名思义&#xff0c;就是把整块的东西分割开来。python语言中的切片是把一个容器截…

内存学习(1):内存分类与常用概念1(RAM)

随机存取存储器&#xff08;RAM&#xff09; 随机存取存储器 &#xff08;Random Access Memory&#xff09;&#xff0c;也叫主存&#xff0c;是与CPU直接交换数据的内部存储器。工作时&#xff08;刷新时除外&#xff09;可以随时从任何一个指定的地址写入&#xff08;存入&…

cocosCreator 之 crypto-es数据加密

版本&#xff1a; 3.8.0 语言&#xff1a; TypeScript 环境&#xff1a; Mac 简介 项目开发中&#xff0c;针对于一些明文数据&#xff0c;比如本地存储和Http数据请求等&#xff0c;进行加密保护&#xff0c;是有必要的。 关于加密手段主要有&#xff1a; 对称加密 使用相…

微信小程序 slot 不显示

问题:创建组件&#xff0c;使用带名字的slot&#xff0c;页面调用组件使用slot不显示 源码&#xff1a; 组件xml <view class"p-item br24" style"{{style}}"><slot name"right" wx:if"{{!custBottom}}"></slot>&l…

明星艺人类的百度百科怎么创建 ?

明星艺人们的知名度对于其事业的成功至关重要&#xff0c;而作为国内最大的中文百科全书网站&#xff0c;百度百科成为了人们获取信息的重要来源。一线明星当然百科不用自己操心&#xff0c;平台和网友就给维护了&#xff0c;但是刚刚走红的明星艺人应提早布局百科词条&#xf…

枚举类型 表示不同的 HTTP 状态码和相应的错误消息

java web业务中经常用常量来表示不同的 HTTP 响应状态,比如 public enum AppHttpCodeEnum {// 成功段0SUCCESS(200,"操作成功"),// 登录段1~50NEED_LOGIN(1,"需要登录后操作"),LOGIN_PASSWORD_ERROR(2,"密码错误"),// TOKEN50~100TOKEN_INVALID…

Docker容器音频播放之WSL篇

截至发文起&#xff0c;WSL已经支持音频播放。 但是&#xff0c;想在Docker中音频播放还需要一些额外的操作。 在启动容器时设置环境变量 PULSE_SERVER${PULSE_SERVER}并映射目录 /mnt/wslg/:/mnt/wslg/例如&#xff0c;docker-compose.yml version: 3 services:model-scop…

Who‘s Harry Potter? Approximate Unlearning in LLMs

本文是LLM系列文章&#xff0c;针对《Who’s Harry Potter? Approximate Unlearning in LLMs》的翻译。 哈利波特是谁&#xff1f;LLMs中的近似遗忘 摘要1 引言2 我们方法的描述3 评估方法4 结果5 结论 摘要 大型语言模型&#xff08;LLM&#xff09;是在大量的互联网语料库…

java商城免费搭建 VR全景商城 saas商城 b2b2c商城 o2o商城 积分商城 秒杀商城 拼团商城 分销商城 短视频商城

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…

maven环境变量,安装源,本地仓库配置

1. maven环境变量 我这里用的是idea自带的maven 数值为&#xff1a; D:\software\computer_software\java\IDEAJ\IDEAJ2021.2.1\IntelliJ IDEA 2021.2.1\plugins\maven\lib\maven3\bin 2. 安装源更换为阿里云&#xff08;我不知道清华源是什么网址&#xff0c;网上也没查到&am…

GPT实战系列-如何用自己数据微调ChatGLM2模型训练

GPT实战系列-如何用自己数据微调ChatGLM2模型训练 目录 GPT实战系列-如何用自己数据微调ChatGLM2模型训练1、训练数据广告文案生成模型训练和测试数据组织&#xff1a; 2、训练脚本3、执行训练调整运行 4、问题解决问题一问题二问题三问题四 1、训练数据 广告文案生成模型 输…

vue导出功能

<a-buttontype"primary"class"search-btn"click"downLoadClick">下载导出结果</a-button> // 下载导出结果 const downLoadClick async () > {try {const response await axios({url: ${getBaseUrl()}/gpt/admin/messageExport…

分布式:一文吃透分布式事务和seata事务

目录 一、事务基础概念二、分布式事务概念什么是分布式事务分布式事务场景CAP定理CAP理论理解CAPCAP的应用 BASE定理强一致性和最终一致性BASE理论 分布式事务分类刚性事务柔性事务 三、分布式事务解决方案方案汇总XA规范方案1&#xff1a;2PC第一阶段&#xff1a;准备阶段第二…

【计算机网络】什么是HTTPS?HTTPS为什么是安全的?

【面试经典题】 前言&#xff1a; HTTP最初的设计就是用于数据的共享和传输&#xff0c;并没有考虑到数据的安全性&#xff0c;如窃听风险&#xff0c;篡改风险和冒充风险。HTTPS是在 HTTP 的基础上引入了一个加密层。HTTPS通过数据加密&#xff0c;数据完整性检验和身份认证…