Android UI绘制原理:UI的绘制流程是怎么样呢?为什么子线程不能刷新UI呢?讲解大体的流程是怎么样的

目录:

在这里插入图片描述


一、 为什么要学习android UI绘制原理呢?对我们有什么帮助?

1.解决复杂布局问题:了解UI绘制原理可以帮助我们更好地理解和解决布局问题,比如使用自定义View、优化布局层级等。

2.知道何时触发布局(Layout)、绘制(Draw)和测量(Measure)过程,以及如何减少这些过程的调用次数,避免在UI线程上进行耗时的操作,可以显著提升应用的流畅度和响应速度。



二、为什么子线程不能刷新UI呢?原因是什么?


比如,我们写如下这样的代码,那么就有可能报错。

 Thread(object :Runnable{override fun run() {tvTestUi.text = "123412341234"}}).start()

报错内容:只有在主线程种对UI进行操作才行。

Only the original thread that created a view hierarchy can touch its views.

我们可以追踪到源码里面看看。后面我们会讲一下原因。
下面我们可以看到,绘制UI的线程,如果不是主线程,那么就报错。
在这里插入图片描述但,为什么不能子线程呢?多线程更新UI不是会更加高效? 只是因为代码里面限制?!!当然不是。

子线程不能直接刷新UI的原因主要与Android系统的UI线程(主线程)的设计和机制有关。在Android中,UI组件(如视图、控件等)不是线程安全的,这意味着它们的设计初衷是为了在单个线程(即UI线程或主线程)上被访问和修改。如果多个线程尝试同时修改UI组件,就可能会导致不可预见的行为,比如视图的不一致状态、崩溃等。

假如你可以多线程更新,那么你得花时间确定更新状态是否一致,界面重复刷新问题,像素结果是否统一的问题,要同步,所以代价是相当大的,所以绝大多数的系统,对UI刷新,都是采用单线程的方式

但,为什么在oncreate中开子线程刷新ui不会报错呢?看源码我们就会知道,viewRootImpl 的初始化在 onCreate 之后,onResume 之后。所以也就没调用checkThread方法。

具体来说,当Activity调用setContentView()时,它会通过WindowManager(实际上是WindowManagerGlobal和WindowManagerImpl)来请求添加一个窗口(Window)。这个过程中,会创建并初始化ViewRootImpl实例,然后将其与Activity的根视图(DecorView)关联起来。

由于ViewRootImpl的初始化是异步的,并且涉及到与窗口系统的交互,因此很难直接通过Activity的生命周期方法来准确判断ViewRootImpl的初始化完成时刻。但是,我们可以知道,在onResume()之后,并且视图开始绘制之前,ViewRootImpl应该已经被初始化了


三、UI绘制原理


我们再回到上面的代码。

Thread current = Thread.currentThread(); 这行代码的意思是获取当前正在执行的线程对象,那么当前运行的线程是什么线程??为什么会调用ViewRootImpl的checkThread方法呢??为什么text的时候,会重新绘制呢?

这,就需要我们了解UI的绘制流程

Android UI的绘制流程是一个从数据加载到Activity启动,再到View的测量、布局和绘制的过程。我们直接从创建Activity实例这里开始。

在这里插入图片描述下面我们讲一下流程。从创建Activity开始。


3.1 创建Activity 实例和view的树型结构

ActivityThread,通过handleResumeActivity方法创建Activity实例后,并为其创建一个PhoneWindow,合成DecorView。

在这里插入图片描述
在这里插入图片描述我们可以看到当我们调用setContentView的时候,就是调用了window的。
在这里插入图片描述

DecorView是顶级容它内部包含了一个或多个子View或ViewGroup,用于承载应用的UI内容。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述那么接下来,view创建好后,如果我想要进行view的渲染和刷新?由谁来做呢?是如何触发刷新的?下面我们看看ViewRootImpl




3.2 是如何触发刷新view的?

我们先了解一下,VSYNC是什么?VSYNC信号由屏幕(显示设备)产生,并以固定频率发送给Android系统。Android系统中的SurfaceFlinger接收VSYNC信号后,会遍历其层列表以查找新的缓冲区进行渲染。这种机制提升了渲染任务的优先级,优化了渲染性能。

刷新也分为手动刷新和自动刷新(VSYNC就是自动刷新),比如我们调用textView的text方法,就是手动刷新,会调用requestLayout方法,不断的递归requestLayout方法去进行刷新。因为它是一个树形结构。

自动刷新,其实就是一个回调。下面是源码,可以粗略看看。
在这里插入图片描述在这里插入图片描述
performTraversals();方法就是会绘制的方法,比如测量等
在这里插入图片描述


3.3 管理绘制的类:ViewRootImpl

我们都知道,写的这些xml布局代码,都是一个树形的层次结构,比如下面的代码,就对应一个这样的结构(如图),举例哈:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".activity.main.MainActivity"><androidx.fragment.app.FragmentContainerViewandroid:id="@+id/home_fragmentcontainerview"android:name="androidx.navigation.fragment.NavHostFragment"android:layout_width="match_parent"android:layout_height="match_parent"app:defaultNavHost="true"app:navGraph="@navigation/home_nav" /><TextViewandroid:id="@+id/tv_test_ui"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述而这些结构呢,是由ViewRootImpl来进行管理,比如进行测量,布局以及绘制等等。

View的绘制流程是通过ViewRootImpl类来进行管理,在ActivityThread(主线程)中,当Activity对象被创建完毕后,会将DecorView添加到Window中,并创建对应的ViewRootImpl对象,将两者建立关联。

我们可以看到是ActivityThread线程创建了ViewRootImpl,所以Thread current = Thread.currentThread();获得的当前线程,就是主线程。如果是子线程刷新,那么Thread current = Thread.currentThread()就是子线程。


2.4 View的绘制流程:测量(Measure)

为什么需要测量呢?确定View的宽高,用于后续绘制。

有没有想过,wrap_content和match_parcent的宽高如何确定呀,就需要测量,并且每个view的宽高,还要取自于上一层的,所以ViewGroup遍历所有子View进行测量,根据子View的LayoutParams和自身的MeasureSpec计算出子View的测量规格。


2.5 View的绘制流程:布局(Layout)

根据测量的宽高确定View在其父View中的位置(即四个顶点的坐标)。也是会递归遍历对子View进行布局。


2.6 View的绘制流程:绘制(Draw)

这个阶段的作用,就是将View的内容绘制到屏幕上。也是会递归遍历子View,调用子View的draw方法。

到这里了,view的绘制流程就大致完成了。


三、学习总结


刚开始看UI绘制原理的时候,完全看不懂,硬着头皮去看,渐渐的有些可以看懂了,但绝大部分还是不懂。这个时候,我就从“为什么子线程不能刷新UI呢?”入手,比如不能刷新原因是什么,了解原因后,你懂了,但你会发现你不懂的地方也会更多,但是,你已经知道你有哪些不懂了,这个时候,你重新回头去看第二篇的时候,你思路就清晰很多了,你又能看懂很多了。

所以,第一次看肯定有很多不懂,那么就第二次,第三次。慢慢的你就有思路,开始知道一些东西,熟能生巧,很多人都是看一次,觉得难就不学了,但很多东西,都是需要经历无数次,你才会熟悉,才会熟练。

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

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

相关文章

redis面试(二十五)CountDownLatch实现

CountDownLatch最基本的原理&#xff0c;就是用来阻塞线程的&#xff0c;java本身也有CountDownLatch&#xff0c;用多线程处理分批处理多数据的时候很有用 基本的逻辑就是&#xff0c;同时开多个子线程&#xff0c;然后主线程进入等待&#xff0c;只有当其他子线程全都结束之…

JAVA后端框架【spring】--超详解

什么是spring? spring是一个轻量级的ioc和Aop的一站式java开发框架&#xff0c;简化企业级开发 轻量级&#xff1a;框架体积小&#xff08;核心模块&#xff09; IOC IOC:inversion of control 控制反转 把创建对象的控制权反转给spring框架 AOP Aop:面向切面编程 将程…

pnpm 查看库的所有版本

1、最近在做图布局的时候&#xff0c;发现默认版本是beta版 2、那么我们如何来查看远程库中有什么版本可以安装呢&#xff1f; 3、使用命令&#xff1a; pnpm view antv/layout versions pnpm view 这里替换成要查看的库名 versions

Android APP 常见概念与 adb 命令

adb 的概念 adb 即 Android Debug Bridge 。在窗口输入 adb 即可显示帮助文档。adb 实际上就是在后台开启一个 server&#xff0c;会接收 adb 的命令然后帮助管理&#xff0c;控制&#xff0c;查看设备的状态、信息等&#xff0c;是开发、测试 Android 相关程序的最常用手段。…

Jenkins安装使用详解,jenkins实现企业级CICD流程

文章目录 一、资料1、官方文档 二、环境准备1、安装jdk172、安装maven3、安装git4、安装gitlab5、准备我们的springboot项目6、安装jenkins7、安装docker8、安装k8s&#xff08;可选&#xff0c;部署节点&#xff09;9、安装Harbor10、准备带有jdk环境的基础镜像 三、jenkins实…

addroutes和next()导致的页面无法跳转问题,如登录之后无法跳转到首页,无法重定向,使用next(to)

版本 vue router 3 问题说明 登录成功后&#xff0c;想重定向到/index&#xff0c;执行router.push之后进入beforeEach&#xff1b; 由于第一次访问&#xff0c;判断用户信息为空&#xff0c;需要异步拉取用户的权限等信息&#xff0c; 获得响应后&#xff0c;使用addRoutes批…

harbor部署+docker-compose

harbor部署 harbor介绍 Harbor 是一个开源的企业级容器镜像仓库&#xff0c;用于存储和分发 Docker 镜像及其他容器格式的镜像。 Harbor 提供了一个集中化的存储库&#xff0c;方便团队成员共享和管理容器镜像。你可以将构建好的镜像推送到 Harbor 仓库中&#xff0c;其他成…

用python启动nacos和redis

import subprocess import tkinter as tk from tkinter import ttk, messagebox from subprocess import Popen, run, PIPE# 启动Nacos的函数 def start_nacos():startup_cmd_path rD:\environment\code-software\nacos-server-1.4.1\nacos\bin\startup.cmd# 异步启动NacosPop…

Python 和 PyCharm 安装(傻瓜式)

为什么要安装Python&#xff1f; 当我们写 python 代码的时候&#xff0c;需要有环境的支持&#xff0c;才可以运行代码。而 python 的安装支持了两个主要部分&#xff0c;分别是解释器和标准库。当我们安装完成&#xff0c;就可以使用python里面的标准库来写代码&#xff0c;而…

Nginx - 反向代理、缓存详解

概述 本篇博客对配置Nginx的第二篇&#xff0c;主要介绍Nginx设置反向代理、缓存、和负载均衡三个知识点&#xff0c;在之前的生产实践中遇到的问题进行归纳和总结&#xff0c;分享出来&#xff0c;以方便同学们有更好的成长。 Nginx 核心参数配置 在写Nginx反向代理时&…

Kafka的Offset(偏移量)详解

Kafka的Offset详解 1、生产者Offset2、消费者Offset2.1、消费者2.2、生产者2.3、实体类对象2.4、JSON工具类2.5、项目配置文件2.6、测试类2.7、测试2.8、总结 1、生产者Offset 2、消费者Offset 2.1、消费者 package com.power.consumer;import org.apache.kafka.clients.consu…

nexus 清理 docker 镜像

下载配置 nexus-cli 看网上文档都用如下地址&#xff0c;但现在已经不能下载&#xff1a; wget https://s3.eu-west-2.amazonaws.com/nexus-cli/1.0.0-beta/linux/nexus-cli chmod x nexus-cli 在 github 上下载&#xff1a; wget https://github.com/heyonggs/nexus-cli/r…

跟李沐学AI:转置卷积

定义 卷积不会增大输入的高宽&#xff0c;通常卷积层后高宽不变或减半。转置卷积则可以用来增大输入的宽高。 转置卷积是一种卷积&#xff0c;它将输入和核进行了重新排列&#xff0c;通常用作上采用。 如果卷积将输入从变为&#xff0c;同样超参数的情况下&#xff0c;转置…

Java中三大容器类(List、Set、Map)详解

三大容器介绍 名称结构特点常见实现类List&#xff08;列表&#xff09;由有序的元素序列组成&#xff0c;可以包含重复元素可以通过索引访问元素&#xff0c;插入的顺序与遍历顺序一致ArrayList、LinkedList、VectorMap&#xff08;映射&#xff09;由键值对(Key-Value)组成的…

SpringBoot项目中mybatis执行sql很慢的排查改造过程(Interceptor插件、fetchSize、隐式转换等)

刚入职公司&#xff0c;就发现公司项目跑sql特别慢&#xff0c;差不多一万条数据插入到数据库要5秒以上&#xff08;没有听错&#xff0c;就是这个速度&#xff09;&#xff0c;查询修改删除也是特别慢。直到22年年底实在是受不了了&#xff0c;我就去排查了一下。 用的是Oracl…

OpenCV小练习:身份证号码识别

目标&#xff1a;针对一张身份证照片&#xff0c;把身份证号码识别出来&#xff08;转成数字或字符串&#xff09;。 实现思路&#xff1a;需要将目标拆分成两个子任务&#xff1a;(1) 把身份证号码区域从整张图片中检测/裁剪出来&#xff1b;(2) 将图片中的数字转化成文字。第…

Ubuntu 22.04上稳定安装与配置搜狗输入法详细教程

摘要&#xff1a;本教程详细介绍了如何在Ubuntu 22.04上安装和配置搜狗输入法&#xff0c;每个步骤详细配图。由于在Ubuntu 24.04上存在兼容性问题&#xff0c;建议用户继续使用稳定的22.04版本。教程涵盖了从更新系统源、安装fcitx输入法框架&#xff0c;到下载和配置搜狗输入…

存储实验:基于华为存储实现存储双活(HyperMetro特性)

目录 什么是存储双活仲裁机制 实验需求实验拓扑实验环境实验步骤1. 双活存储存储初始化&#xff08;OceanStor v3 模拟器&#xff09;1.1开机&#xff0c;设置密码1.2登录DM&#xff0c;修改设备名、系统时间和导入License1.3 设置接口IP 2. 仲裁服务器配置&#xff08;Centos7…

全局点云配准的新思考:没有良好初值时如何配准?

更多优质内容&#xff0c;请关注公众号&#xff1a;智驾机器人技术前线 1.论文信息 论文标题&#xff1a;BiEquiFormer: Bi-Equivariant Representations for Global Point Cloud Registration 作者&#xff1a;Stefanos Pertigkiozoglou*, Evangelos Chatzipantazis∗ and K…

【循环顺序队的实现】

1.队列的逻辑结构 与 抽象数据类型定义 先进先出的线性表 在顺序队列中&#xff0c;我们使用头指针front指向队首元素&#xff1b;用尾指针rear指向队尾元素的下一个位置&#xff08;当然这里的指针是用下标模拟出来的&#xff09; 同时顺序队列中的元素当然是用数组来存储的 …