当前位置: 首页 > news >正文

再谈Android View绘制流程

一,先思考何时开始绘制

笔者在这里提醒读者,Android的View是UI的高级抽象,我们平时使用的XML文件也好,本质是设计模式中的一种策略模式,其View可以理解为一种底层UI显示的Request。各种VIew的排布,来自于开发者编写的XML文件,或动态增添删除View,这一系列的集合即一种策略。而这种策略最终会被Android系统解析为一种Request请求。什么意思呢?就是应用进程在此充当绘制的客户端,而服务器是谁?SurfaceFinger。记住,即便屏幕前是我们所描绘的某种策略绘制的UI,那也是Surface系统服务器的Response表现。

希望读者能理解这一点。

接下来,笔者假设读者已经了解了Activity启动流程,并且当前Activity已经进入onCreate生命周期,我们就从这里讲起。

相信各位读者一定对Activity#onCreate中调用setContent方法一定熟悉,我们直接看下源码。

很简单,拿到Window,相信读者明白,这个Window就是PhoneWindow,在此不赘述。不过笔者仍啰嗦一下,这里Activity依然将视图委托给Window,其Activity-Window-View之间的关系越来越明显了吧。

然后,调用PhoneWindow#setContentView,传入layoutId,我们继续跟进。

这里的mContentParent就是我们setContent传入View的父View,在初始化时,这里肯定是null,因此调用到installDecor方法。这个方法是什么呢?简单来讲,这里面创建了一个DecorView,并且通过我们常用的findVIewById初始化一些View,如mContentParent。

我们回到正轨,当DecorView已经创建完毕,接下来就是使用LayoutInflater去膨胀我们传入的layoutID,其parentView参数时mContentParent。关于如何膨胀,读者可以自行解读XML解析实现,不过其核心是根据XML的一系列层次结构和属性,创建多个View,并按照层次与属性赋值而已。

然后呢?View从何时添加到所谓的WindowManager中呢?

此时我们需要快进到ActivityThread#handleResumeActivity方法。

关键核心在下面,

由于视图可见,DecorView添加至WindowManager中。在此之前,有必要解释下ViewRootImpl是啥。先看下ViewRootImpl的构造方法如下。

核心注意IWIndowSession,这是什么呢?可以理解为应用进程持有的Window#Takon,是Binder接口,窗口唯一,主要负责与WMS通信。笔者在这里通俗理解为View的抽象顶层,它既负责管理底层View(事件分发、View绘制等),又负责与系统交互,是应用层View的顶层通信抽象。

明白了这点,我们回到正轨,继续分析。

wm.addView,将DecorView添加到wm中,wm是WindowManagerImpl,我们跟进。

委托给mBlobal,这是进程唯一的WindowManagerGlobal,我们跟进。

上述部分是参数的一系列检查,我们继续跟进逻辑,

在这里,ViewRootImpl这种抽象,会发现是在每次添加WindowManagerGlobal时创建。我们继续跟进,核心逻辑是root.setView。由于VIewRootImpl#setView太长,就不在此截图,不过核心逻辑如下,

requestLayout,我们继续跟进,

这里可以理解为View视图逻辑Request创建的起点。检查线程、scheduleTraversals,里面便涉及三大流程。那么我们得到第一个答案,View从什么时候开始绘制的呢?是ActivityThread#handleResumeActivity时,通过wm.addView(decorView,l)开始进行绘制。但只有这一个起点吗?

相信读者知道,对指定View调用类似于setVisibile或TextView#setText时,也是绘制的起点。但最终会到哪呢?话不多说,一看了之。

我们跟进TextView#setText

查看核心逻辑,checkForRelayout,跟进,

不管怎样,都会调用到requestLayout方法。这是View内置方法,并且用final关键字修饰。我们看下该方法定义。

笔者在这里注意到,通过设置mPrivateFlags,标记此View强制layout,重绘,然后请求到mParent.requestLayout。mParent正如笔者所述,其顶层一定是VIewRootImpl。那么这里我们发现,通过更改子View的属性,仍通过委托机制到父View,此过程中如果需要自己View绘制流程中更改,需标记某些Flag。于是乎,我们又来到VIewRootImpl#requestLayout。

有兴趣的读者可自行分析setVisiblity方法,会发现仍是设置某种flag到自身mFlag类成员上,通过向父View委托,最终仍触发VIewRootImpl#requestLayout,那么笔者就假设VIewRootImpl#requestLayout是一切绘制流程的起点吧。

二,真正绘制前过程

checkThread,检查是否requestLayout在UI线程,这里的UI线程是主线程。读者在这里思考下,假设在Activity#onCreate中在子线程设置TextView#setText,会抛出异常吗?可能不会,为什么呢?因为只有在onResume时期,才会创建VIewRootImpl,而TextView通过setText委托了自己的请求向上传,但终点mParent是null,也就不会抛出此异常了。

我们继续跟进核心逻辑scheduleTraversals方法。

posySyncBarrier是在当前MessageQueue中插入消息屏障。什么是消息屏障呢?笔者在此啰嗦一下。消息屏障的本质在是MessageQueue中插入一条特别的Message,其target字段为null,代表没有处理者。这个时候,所有在消息屏障后面的同步消息都被阻塞,只有异步消息能通过屏障执行。如下,

那什么是异步呢?可以理解为高优先级Message,可以插队。我们跟进到Choregrapher看看,

通过Message#setAsynchronous为true,指定MSG_DO_SCHEDULE_CALLBACK为异步消息,action即使上文传入的runnable,即VIewRootImpl#TraversalRunnable。我们继续跟进。

笔者在这里不太想暂开讲了,上述代码在Choreographer中,是与帧同步相关的逻辑,感兴趣的读者可自行了解。当满足当前帧条件时,执行到Choreographer#doFrame方法。Choreographer#doFrame中会计算下是否有跳帧现象,如下

最终执行doCallbacks逻辑,

层层递进,终于执行VIewRootImpl#TraversalRunnable,

在此总结下,ViewRootImpl#scheduleTraversals会开启消息屏障,这时候在Choreographer中的消息又全部是异步消息,那样不管我们的MessageQueue中有多少同步消息,也不会延迟帧逻辑,就不会造成卡顿。当我们真正执行到scheduleTraversales时,就会移除消息屏障,调用performTraversals,这时候,可以说,进入了绘制的逻辑。

三,所谓的绘制过程

VIewRootImpl#performTraversals,该函数在Android中出了名的长,笔者在这里挑选核心逻辑解释下。

首先是performMeasure,dfs方式去解析每个View的大小,这很重要。我们跟进看一下,

由于meaure是被final修饰,内部会回调onMeasure方法,

测量参数widthMeasureSpec和heightMeasureSpec传入。

注意到,View的ionMeasure不复杂,重点在于VIewGroup提供了measureChildren方法,而各个继承ViewGroup的View需在OnMeasure中调用measureChildren,才能def测量过程,我们跟进,

进而继续调用View#measure,进而调用setMeasureDimensionRaw随后将测量大小保存到measureHeight,measureWidth中,这样一通操作下来,从跟DecorView->每个叶子View,其内部大小都被保存,接下来,我们回到ViewRootImpl#performTraversals中,开始performLayout过程,此过程众所周知,计算出每个View的位置。

跟进到layout,

笔者说下核心,isLayoutValid返回此次layout是否合理。注意到从子view委托到父view中,有设置一些Flag,来表示自己需要layout,如下

因此只有需要layout的view有此flag,就不会全局layout了。

此方法会回调View#Onlayout方法,View#Onlayout方法默认无实现,

ViewGroup重写此方法,标记为abstract,意图让ViewGroup的子类必须决定子View的摆放位置

具体的实现,感兴趣的读者可以自行阅读,比较简单。

那么,当ViewTree中所有的view大小和位置都确定后,该干什么呢?在ViewRootImpl#performTravels中,最后调用performDraw方法,如下。

注意,这很关键。正如笔者前面所说,所有的应用层绘制,本质是构造一种绘制Request,发给服务端SurfaceFinger,所以与其说此处是draw,不如说是createDrawRequest。不过,我们继续看下ViewRootImpl#draw实现。

注意到如下mSurface

这里的mSurface可以理解为DrawRequestClient这个概念,在ViewRootImpl中被创建,

其lockCavas方法可以同等理解为DrawRequestClient.createRequest,即我们向Cavas中添加的各种绘制操作,都可以理解为一种请求,通过unlockCanvasAndPost本质就是提交请求,具体的过程我们稍后再谈。

我们继续跟进逻辑drawSoftware,除了每个父View构造自己的请求,子View也需要在此添加些什么。

在drawSoftware中

这里的mView是DecorView,不再赘述,其直接调用View#draw方法,默认实现中会绘制前景图、背景图等,然后回调众所周知的onDraw方法,canvas作为参数传入。

并且,调用dispatchDraw方法dfs便利子View,ViewGroup实现了此方法,如下,

通过drawchild,

随后dfs式向下传递canvas对象,当收集了此ViewRootImpl的所有绘制请求后。接下来干什么呢?笔者猜想,该去请求SurfaceFinger,真正绘制这些请求了。

四,真正的绘制过程

通过三过程,ViewRootImpl#draw中,最后执行surface.unlockCanvasAndPost方法将绘制请求提交,如下

那么,笔者对此非常感兴趣,因此继续跟踪。

此函数最终调用到nativeUnlockCanvasAndPost方法,

我们进入native层看看,

在native层,先校验surface是否有效,如果有效,进而调用unlockAndPost方法

由于笔者没有native层的源码,阅读起来实在不便。感兴趣的读者可以自行阅读下底层实现吧。不过大意可以说下,native层通过与SurfaceFinger进行通信,将绘制请求交给SufaceFinger,SurfaceFinger会在其后置缓存中绘制内存,等到下一给垂直帧信号来到时,如果绘制成功,则交换前置缓存和后置缓存,这样就实现了绘制内容的显示。

另外,简单总结下View的绘制流程。

当ActivityResume时或某个View变化时,通过委托机制请求到ViewRootImpl方法,调用其requestLayout方法,这时设置同步屏障。当垂直信号有效,移除同步屏障。开始对需要变化的View重新测量、布局,最后通过surface拿到canvas,将绘制过程添加到canvas中,然后与SurfaceFinger通信,实现内容的展示。

相关文章:

再谈Android View绘制流程

一,先思考何时开始绘制 笔者在这里提醒读者,Android的View是UI的高级抽象,我们平时使用的XML文件也好,本质是设计模式中的一种策略模式,其View可以理解为一种底层UI显示的Request。各种VIew的排布,来自于开…...

分布式定时任务系列8:XXL-job源码分析之远程调用

传送门 分布式定时任务系列1:XXL-job安装 分布式定时任务系列2:XXL-job使用 分布式定时任务系列3:任务执行引擎设计 分布式定时任务系列4:任务执行引擎设计续 分布式定时任务系列5:XXL-job中blockingQueue的应用 …...

python+Qt5 UOS 摄相头+麦克风测试,摄相头自动解析照片二维条码,麦克风解析音频文件

UI图片: 源代码: # -*- coding: utf-8 -*-# Form implementation generated from reading ui file CameraTestFrm.ui # # Created by: PyQt5 UI code generator 5.15.2 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is…...

MongoDB日期存储与查询、@Query、嵌套字段查询实战总结

缘由 MongoDB数据库如下: 如上截图,使用MongoDB客户端工具DataGrip,在filter过滤框输入{ profiles.alias: 逆天子, profiles.channel: },即可实现昵称和渠道多个嵌套字段过滤查询。 现有业务需求:用Java代码来查询…...

Windows版本Node.js常见问题及操作解决方式(小白入门必备)

npm i时ERROR:reason: certificate has expired问题 https://blog.csdn.net/m0_73360677/article/details/135774500 # 1.取消ssl验证;npm config set strict-ssl false#这个方法一般可以直接解决问题,如不能请尝试第二种方法# 2.更换npm镜像源&#x…...

09.Elasticsearch应用(九)

Elasticsearch应用(九) 1.搜索结果处理包括什么 排序分页高亮返回指定字段 2.排序 介绍 Elasticsearch支持对搜索结果排序,默认是根据相关度算分来排序 支持排序的字段 keyword数值地理坐标日期类型 排序语法 GET /[索引名称]/_sear…...

ROS2常用命令工具

ROS2常用命令工具 包管理工具ros2 pkg ros2 pkg create ros2 pkg create --build-type ament_python pkg_name rclpy std_msgs sensor_msgs –build-type : C或者C ament_cmake ,Python ament_python pkg_name :创建功能包的名字 rclpy std_msgs sens…...

Linux之快速入门

一、Linux目录结构 从Windows转到Linux最不习惯的是什么: 目录结构 Windows会分盘,想怎么放东西就怎么放东西,好处自由,缺点容易乱 Linux有自己的目录结构,不能随随便便放东西 /:根目录/bin:二进制文件&…...

C语言——操作符详解1

目录 1. 操作符的分类2. 二进制和进制转换2.1 二进制的概念2.2 二进制转十进制2.3 十进制转二进制2.4 二进制转八进制和十六进制2.4.1 二进制转八进制二进制转十六进制 3. 原码、反码和补码4. 移位操作符4.1 左移操作符4.2 右移操作符 5. 位操作符5.1 &5.2 |5.3 ^5.4 ~ 1. …...

C++学习| QT快速入门

QT简单入门 QT Creater创建QT项目选择项目类型——不同项目类型的区别输入项目名字和路径选择合适的构建系统——不同构建系统的却别选择合适的类——QT基本类之间的关系Translation File选择构建套件——MinGW和MSVC的区别 简单案例:加法器设计界面——构建加法器界…...

Android App开发-简单控件(1)——文本显示

本章介绍了App开发常见的几类简单控件的用法,主要包括:显示文字的文本视图、容纳视图的常用布局、响应点击的按钮控件、显示图片的图像视图等。然后结合本章所涉及的知识,完成一个实战项目“简单计算器”的设计与实现。 1.1 文本显示 本节介绍…...

[GYCTF2020]Ezsqli1

打开环境,下面有个提交表单 提交1,2有正确的查询结果,3以后都显示Error Occured When Fetch Result. 题目是sql,应该考察的是sql注入 简单fuzz一下 发现information_schema被过滤了,猜测是盲注了。 测试发现只要有东…...

【npm包】如何发布自己的npm包

随着Node.js的普及,npm(Node Package Manager)已成为JavaScript开发者中不可或缺的一部分。发布自己的npm包,不仅可以将自己的项目分享给更多人,还可以为社区做出贡献。本文将详细介绍如何从零开始发布自己的npm包。 …...

《WebKit技术内幕》学习之十五(2):Web前端的未来

2 嵌入式应用模式 2.1 嵌入式模式 读者可能会奇怪本章重点表达的是Web应用和Web运行平台,为什么会介绍嵌入式模式(Embedded Mode)呢?这是因为很多Web运行平台是基于嵌入式模式的接口开发出来的,所以这里先解释一下什…...

【教学类-综合练习-11】20240116 大4班 最后一次

只有图片 加了两条链接 背景需求 年终了,清理库存,各种打印的题型纸都拿出来,当个别化学习材料 教学过程: 时间:2024年1月5日下午 班级:大4班(额外带班 真正的最后一次大班) 人…...

【阻塞队列】阻塞队列的模拟实现及在生产者和消费者模型上的应用

文章目录 📄前言一. 阻塞队列初了解🍆1. 什么是阻塞队列?🍅2. 为什么使用阻塞队列?🥦3. Java标准库中阻塞队列的实现 二. 阻塞队列的模拟实现🍚1. 实现普通队列🍥2. 实现队列的阻塞功…...

Cocos Creator使用VS Code调试代码配置

创建项目 首先我们先打开cocos创建一个项目 随便添加一个Cube和脚本,然后保存场景: 添加Chrome Debug配置 在Cocos 中选择添加Chrome Debug配置 然后再VS Code中就可以看到有一个cocos launch Chrome: 然后,就可以按快捷键F…...

【投稿优惠|EI优质会议】2024年材料化学与清洁能源国际学术会议(IACMCCE 2024)

【投稿优惠|优质会议】2024年材料化学与清洁能源国际学术会议(IACMCCE 2024) 2024 International Conference Environmental Engineering and Mechatronics Integration(ICEEMI 2024) 一、【会议简介】 随着全球能源需求的不断增长,清洁能源的研究与应用成为了国际…...

ubuntu设置右键打开terminator、code

前言: 这里介绍一种直接右键打开本地目录下的terminator和vscode的方法。 一:右键打开terminator 1.安装terminator sudo apt install terminator 2.安装nautilus-actions filemanager-actions sudo apt-get install nautilus-actions filemanager…...

PHP AES加解密:用代码为数据加上保护的盾牌

在网络世界里,数据的传输和存储是一个敏感而重要的问题。为了保护数据的安全性,加密算法是一项不可或缺的技术。而在PHP中,AES(Advanced Encryption Standard)加解密算法是一种常用的选择。本篇博客将深入解析PHP中的A…...

Socket实现服务器和客户端

Socket 编程是一种用于在网络上进行通信的编程方法,以下代码可以实现在不同主机之间传输数据。 Socket 编程中服务器端和客户端的基本步骤:服务器端步骤: 1.创建 Socket: int serverSocket socket(AF_INET, SOCK_STREAM, 0);…...

智能GPT图书管理系统(SpringBoot2+Vue2)、接入GPT接口,支持AI智能图书馆

☀️技术栈介绍 ☃️前端主要技术栈 技术作用版本Vue提供前端交互2.6.14Vue-Router路由式编程导航3.5.1Element-UI模块组件库,绘制界面2.4.5Axios发送ajax请求给后端请求数据1.2.1core-js兼容性更强,浏览器适配3.8.3swiper轮播图插件(快速实…...

面试经典 150 题 ---- 合并两个有序数组

面试经典 150 题 ---- 合并两个有序数组 合并两个有序数组方法一:直接合并后排序方法二:双指针方法三:逆向双指针 合并两个有序数组 方法一:直接合并后排序 这种方法最简单,直接将 nums2 的数组放到 nums1 数组的尾部…...

防火墙在企业园区出口安全方案中的应用(ENSP实现)

拓扑图 需求: 1、企业出口网关设备必须具备较高的可靠性,为了避免单点故障,要求两台设备形成双机热备状态。当一台设备发生故障时,另一台设备会接替其工作,不会影响业务正常运行。 2、企业从两个ISP租用了两条链路&…...

单片机学习笔记---矩阵键盘密码锁

目录 一,设置密码按键 1.设置密码区域 2.设置输入的数字左移 3.设置记录按键的次数 二,设置确认键 1.密码正确时显示OK 2.密码错误时显示ERR 3.密码错误恢复初始状态重输 三,设置取消键 学了这么久,迫不及待想要做一个密…...

8-小程序数据promise化、共享、分包

小程序API Promise化 wx.requet 官网入口 默认情况下,小程序官方异步API都是基于回调函数实现的 wx.request({method: , url: , data: {},header: {content-type: application/json // 默认值},success (res) {console.log(res.data)},fail () {},complete () { }…...

[HTML]Web前端开发技术18(HTML5、CSS3、JavaScript )HTML5 基础与CSS3 应用——喵喵画网页

希望你开心,希望你健康,希望你幸福,希望你点赞! 最后的最后,关注喵,关注喵,关注喵,佬佬会看到更多有趣的博客哦!!! 喵喵喵,你对我真的…...

Threejs 展示——obj 格式模型导入

文章目录 需求分析1. HTML版本2. Vue 版本 需求 导入obj 格式的模型数据 分析 .obj&#xff1a;Wavefront OBJ 格式&#xff0c;是一种广泛使用的三维模型文件格式。预览 .obj格式文件的软件可点此下载需要准备两种格式的数据&#xff0c;如下所示 1. HTML版本 html <!…...

深入浅出 diffusion(3):pytorch 实现 diffusion 中的 U-Net

导入python包 import mathimport torch import torch.nn as nn import torch.nn.functional as F silu激活函数 class SiLU(nn.Module): # SiLU激活函数staticmethoddef forward(x):return x * torch.sigmoid(x) 归一化设置 def get_norm(norm, num_channels, num_groups)…...

C#使用RabbitMQ-2_详解工作队列模式

简介 &#x1f340;RabbitMQ中的工作队列模式是指将任务分配给多个消费者并行处理。在工作队列模式中&#xff0c;生产者将任务发送到RabbitMQ交换器&#xff0c;然后交换器将任务路由到一个或多个队列。消费者从队列中获取任务并进行处理。处理完成后&#xff0c;消费者可以向…...

上海网站建设平台/微信群免费推广平台

Delphi中字符串的操作很简单&#xff0c;但幕后情况却相当复杂。Pascal传统的字符串操作方法与Windows不同&#xff0c;Windows吸取了C语言的字符串操作方法。32位Delphi中增加了长字符串类型&#xff0c;该类型功能强大&#xff0c;是Delphi缺省的字符串类型。 字符串类型在…...

网站首页加载特效/写软文是什么意思

安装choco choco 是Windows上类似Mac的HomeBrew的命令行软件安装工具&#xff0c;按Windows键-Q搜索Power Shell&#xff0c;右击选择以管理员身份运行&#xff0c;打开后粘贴以下代码运行 iwr https://chocolatey.org/install.ps1 -UseBasicParsing | iex 用choco安装常用软件…...

网站建设费用多少钱/seo营销排名

工作中我们可能会需要将EXCEL中做好的表格导入到WORD里面来&#xff0c;今天我们要讲的不是简单的复制&#xff0c;而是要在WORD中导入一个具有完整功能的EXCEL表格。第一步&#xff1a;我们需要打开一个WORD文档和一个EXCEL表格;第二步&#xff1a;选择EXCEL表格中需要导入到W…...

有没有做ppt好看的免费网站/软件推广平台有哪些?哪个比较好

Java技术栈www.javastack.cn关注阅读更多优质文章“这个网站相当简单&#xff0c;所有你需要做的就是完成X&#xff0c;Y&#xff0c;Z。你看起来应该是技术很好&#xff0c;所以&#xff0c;我相信&#xff0c;你不需要花费太多时间就能把它搭建起来。”我时不时的就会收到这样…...

网站建设竞标需要怎么做/营业推广的目标通常是

选用ZIP Archive版本的win64下面我再把步骤说明一下&#xff1a;选中上图的版本&#xff0c;开始下载。2、解压安装包解压下载的安装包&#xff0c;直接放到系统根目录下&#xff0c;我放置的是D:\mysql3、配置my.ini文件解压后&#xff0c;根目录下&#xff0c;有一个my-defau…...

网站怎么做中英文切换/重庆排名seo公司

643. 子数组最大平均数 I 给定 n 个整数&#xff0c;找出平均数最大且长度为 k 的连续子数组&#xff0c;并输出该最大平均数。 示例 1: 输入: [1,12,-5,-6,50,3], k 4 输出: 12.75 解释: 最大平均数 (12-5-650)/4 51/4 12.75 注意: 1 < k < n < 30,000。 所给…...