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

2024-08-28 07:12

本文主要是介绍Android UI绘制原理:UI的绘制流程是怎么样呢?为什么子线程不能刷新UI呢?讲解大体的流程是怎么样的,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录:

  1. 为什么子线程不能刷新UI呢,原因是什么?
  2. UI绘制原理
    2.1 创建Activity 实例和view的树型结构
    2.2 管理绘制的类:ViewRootImpl
    2.3 是如何触发刷新View的?
    2.4 View的绘制流程:测量(Measure)
    2.5 View的绘制流程:布局(Layout)
    2.6 View的绘制流程:绘制(Draw)
  3. 学习总结

在这里插入图片描述


一、 为什么要学习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呢?”入手,比如不能刷新原因是什么,了解原因后,你懂了,但你会发现你不懂的地方也会更多,但是,你已经知道你有哪些不懂了,这个时候,你重新回头去看第二篇的时候,你思路就清晰很多了,你又能看懂很多了。

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

这篇关于Android UI绘制原理:UI的绘制流程是怎么样呢?为什么子线程不能刷新UI呢?讲解大体的流程是怎么样的的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1114086

相关文章

Android 12解决push framework.jar无法开机的方法小结

《Android12解决pushframework.jar无法开机的方法小结》:本文主要介绍在Android12中解决pushframework.jar无法开机的方法,包括编译指令、框架层和s... 目录1. android 编译指令1.1 framework层的编译指令1.2 替换framework.ja

Android开发环境配置避坑指南

《Android开发环境配置避坑指南》本文主要介绍了Android开发环境配置过程中遇到的问题及解决方案,包括VPN注意事项、工具版本统一、Gerrit邮箱配置、Git拉取和提交代码、MergevsR... 目录网络环境:VPN 注意事项工具版本统一:android Studio & JDKGerrit的邮

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel

Python使用Matplotlib绘制3D曲面图详解

《Python使用Matplotlib绘制3D曲面图详解》:本文主要介绍Python使用Matplotlib绘制3D曲面图,在Python中,使用Matplotlib库绘制3D曲面图可以通过mpl... 目录准备工作绘制简单的 3D 曲面图绘制 3D 曲面图添加线框和透明度控制图形视角Matplotlib

redis中使用lua脚本的原理与基本使用详解

《redis中使用lua脚本的原理与基本使用详解》在Redis中使用Lua脚本可以实现原子性操作、减少网络开销以及提高执行效率,下面小编就来和大家详细介绍一下在redis中使用lua脚本的原理... 目录Redis 执行 Lua 脚本的原理基本使用方法使用EVAL命令执行 Lua 脚本使用EVALSHA命令

Java Spring 中 @PostConstruct 注解使用原理及常见场景

《JavaSpring中@PostConstruct注解使用原理及常见场景》在JavaSpring中,@PostConstruct注解是一个非常实用的功能,它允许开发者在Spring容器完全初... 目录一、@PostConstruct 注解概述二、@PostConstruct 注解的基本使用2.1 基本代

Android使用ImageView.ScaleType实现图片的缩放与裁剪功能

《Android使用ImageView.ScaleType实现图片的缩放与裁剪功能》ImageView是最常用的控件之一,它用于展示各种类型的图片,为了能够根据需求调整图片的显示效果,Android提... 目录什么是 ImageView.ScaleType?FIT_XYFIT_STARTFIT_CENTE

Golang HashMap实现原理解析

《GolangHashMap实现原理解析》HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持高效的插入、查找和删除操作,:本文主要介绍GolangH... 目录HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持

Python Transformers库(NLP处理库)案例代码讲解

《PythonTransformers库(NLP处理库)案例代码讲解》本文介绍transformers库的全面讲解,包含基础知识、高级用法、案例代码及学习路径,内容经过组织,适合不同阶段的学习者,对... 目录一、基础知识1. Transformers 库简介2. 安装与环境配置3. 快速上手示例二、核心模

JAVA保证HashMap线程安全的几种方式

《JAVA保证HashMap线程安全的几种方式》HashMap是线程不安全的,这意味着如果多个线程并发地访问和修改同一个HashMap实例,可能会导致数据不一致和其他线程安全问题,本文主要介绍了JAV... 目录1. 使用 Collections.synchronizedMap2. 使用 Concurren