欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 新车 > Android UI绘制原理:UI的绘制流程是怎么样呢?为什么子线程不能刷新UI呢?讲解大体的流程是怎么样的

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

2024/10/25 6:20:37 来源:https://blog.csdn.net/qq_40853919/article/details/141611692  浏览:    关键词: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呢?”入手,比如不能刷新原因是什么,了解原因后,你懂了,但你会发现你不懂的地方也会更多,但是,你已经知道你有哪些不懂了,这个时候,你重新回头去看第二篇的时候,你思路就清晰很多了,你又能看懂很多了。

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com