前言
在 android 当中对于 UI 体系当中往往咱们会在绘制 UI 的时候碰到各种各样的问题而不晓得从何解决,也有时须要开发更改自定义组件时,须要做本人的调整,或者是实现某个自定义特效时的思路不明确,想要达到去玩转 UI 的最为根底的局部,就是去全面的深刻理解 UI 的绘制流程. 所以接下来带大家去进行全面剖析 UI 整体的绘制体系.
- 公众号:小新聊 Android
- github 地址
UI 整体的绘制体系思路及纳闷
思路
android 程序启动 —>Activity 加载并实现生命周期 —>setContentView—> 图形绘制
纳闷:
1.Android 程序是如何启动,Activity 生命周期如何调用?
2. 在 Activity onCreate 当中咱们的 setContentView 是如何将 UI 文件加载?
3.UI 是如何绘制的?
答案:
1.Android 程序流程
家喻户晓,咱们的 java 程序想要开启须要依赖于 main 办法,也就是咱们的程序入口(主线程)进入,然而在咱们日常开发 android 程序的过程当中咱们并没有发现 main 办法的存在,那么 android 当中的是如何开始运行的?
相熟的敌人们可能都晓得在 android 当中存在一个叫做 ActivityThread 的类,这个类代表的是 android 当中的主线程,而在这个类当中咱们看到了比拟相熟的 main 办法,那么当初是否能够认为咱们的 android 在关上 app 时是首先调用的是以后这个类的 main,也就是此处为咱们的启动点
在此处能够看到 Activity 调用了一个 attach()办法
在这里咱们可能首先要思考的是 getService 拿进去的是什么? 进去之后,咱们会发现
在这个当中,外面调用了的零碎的 ActivityManagerService 这个服务,并且给出了一个 Binder 接口
那么在这里,咱们能够联想到,在 android 当中的 binder 通信机制,那么实际上咱们的 ActivityManager 是有零碎服务所调用治理,并且通过在 binder 接口当中进行调用,这也是为什么咱们讲 Activity 是跨过程拜访的起因
那么明确了这个时候可能失去 ActivityManager 之后,咱们接着回到 attach 当中持续看上来,这个时候会发现,咱们调用了一个 attachApplication 办法(见图 2)这个办法又是干嘛的? attachApplication 在这里的作用其实实际上是 ActivityThread 通过 attach 获取到,而后将 applciationThread 将其关联,把 activity 相干信息存储在 applciationThread 外面,apllicationThread 的类为 activity 的各种状态做了绝对应的筹备工作
这个时候咱们须要关注,ApplicationThread 当中做了什么? 当咱们关上 ApplicationThread 中咱们会看到一堆的 schedle 办法,这些办法的名称其实就能够给咱们表明,代表的是在执行 Activity 的某种状态时调用的打算执行办法
这时咱们会看到一个 scheduleLaunchActivity 办法,示意打算加载时调用的。这里我门发现了一个很有意思的事件
这个下面咱们会看到一个 ActivityClientRecord 对象,这个对象其实实际上就是咱们的 Activity, 而且仿佛每一个办法还干了一件让咱们十分相熟的一件事,进行了一次 sendMessage()将以后创立的 Activity 发送了进来
当走到这里咱们会发现最终咱们调用的是 Handler 的音讯通信机制,也就是说,在这里咱们能够总结一下,当 Activity 状态扭转时,都会有对应的一个音讯发送进来。而接管这里,我能发现通过发送时不同的状态,这边调用了不同的 handlerXXXActivity
办法
在这里,我门貌似发现了 Activity 的生命周期的调用痕迹,那么其实到此为止,我门能够得出一个论断,Application
运行的过程当中,对于 Activity 的操作,状态转变,其实实际上是通过 Handler 音讯机制来实现的,Application
当中只管去发,由音讯机制负责调用,因为在 main 办法当中我门的 Looper 轮训器是始终在进行轮训的, 而当咱们在加载 Activity 的时候,当中调用了一个 performLaunchActivity()
办法,在这个两头我发现了咱们 onCreate 的调用痕迹
也就是说,到目前为止咱们可能明确,整个 Application 加载 Activity 的整套流程是怎么回事? 那么接下来咱们须要关注的是,在 onCreate 当中咱们所写的 setContentView 到底干了什么
2.setContentView
在 onCreate 当中咱们往往会应用 setContentView 去进行设置咱们本人的布局文件或者 view,那么在这当中他到底是怎么做的?通过观察源码,这个时候通过一系列线索我找到了最终的地位 PhoneWindow 类
这个时候咱们会看到他做了两个事件,一个是 installDecor,另一个是 inflate,这两个后一个不难猜出他是在进行布局文件的解析,后面的咱们认为她是在初始化某个货色
进来之后发现他初始化了两个货色,一个叫做 mDecor, 一个叫做 mContentParent
咱们看到了 mDecor 是一个DecorView
,mContentParent 是一个ViewGroup
透过正文的翻译,其实咱们就能很明确晓得这两个是用来干嘛的
// This is the view in which the window contents are placed. It is either(这是窗口内容搁置的视图)// mDecor itself, or a child of mDecor where the contents go.(它要么是 mDecor 自身,要么是 mDecor 的子类的内容。)
//This is the top-level view of the window, containing the window decor.(** 这是在窗口当中的顶层 View,蕴含窗口的 decor**)
一个代表的是顶层 view,一个用来装他上面的视图内容。 在接着往下看的时候,我门发现 generateLayout
办法当中,发现了在此处进行了大量的 requestFeature
的调用,也就是说,咱们的 requestFeature
设置其实是在 setContentView
办法当中就开始了,这也是为什么咱们本人要去 getWindow.requestFeature
时必须在 setContent
之前的起因
而后在上面咱们会发现在做了一件事件
以后这里居然在加载布局文件,并且生成了一个 view,然而如同貌似不是我门本人的。所以咱们须要去探寻他到底加载了一个什么东东?
这是我找到了一个比拟有意思的组件,在这个下面我看到了一句这样的正文
//This is an optimized layout for a screen, with the minimum set of features enabled.
这是一个屏幕的优化布局,具备最小的特色集启用。通过正文和一些材料剖析,失去了一个比拟坑的后果。
这是 DecorView
默认的一个渲染,而后我门本人的布局都是渲染到她的 FrameLayout
上的。那么在这里我门当初可能明确,installDector
其实实际上是在初始化两个视图容器,而后加载零碎的 R 资源及特色,产生了一个根本布局
那么接着回到之前我门关注的另外一个办法 mLayoutInflater.inflate(layoutResID, mContentParent); 这个办法就比拟好了解了。
这这段正文下面我门就能够失去一个信息
//Inflate a new view hierarchy from the specified xml resource.(从指定的视图当中获取试图的层次结构,意思就是,当初在加载本人的资源)
而具体流程就不贴代码了给各位上一张图
那么在这里我门就可能明确,setContentView 其实做了两件比拟外围的事件,就是加载环境配置,和本人的布局,那么接下来我门须要思考的事件就是,他到底怎么画到界面上的
3.UI 是如何绘制的?
通过后面两个章节,我门理解到,程序对于 activity 生命周期的调用,以及咱们的视图资源的由来。这是我门须要找到的是我门的绘制终点在哪?
在 ActivityThread 启动时,我发现在加载 handleLaunchActivity 办法调用 performLaunchActivity 办法之后又调用了一个 handleResumeActivity 在这里我发现了绘制流程的开始
通过后面的流程我门晓得,onCreate 之行实现之后,所有资源交给 WindowManager 保存。在这里将咱们的 VIew 交给了 WindowManager, 此处调用了 addView
进入 addView 之后咱们发现了一段这样的代码,他将视图,和参数还有我门的一个 ViewRoot 对象都用了容器去装在了起来,那么在此处我门能够得出,是将所有的相干对象保存起来:
- mViews 保留的是 View 对象,DecorView
- mRoots 保留和顶层 View 关联的 ViewRootImpl 对象
- mParams 保留的是创立顶层 View 的 layout 参数。
- 而 WindowManagerGlobal 类也负责和 WMS 通信
而在此时,有一句要害代码 root.setView,这里是将咱们的参数,和视图同时交给了 ViewRoot,那么这个时候咱们来看下 ViewRoot 当中的 setView 干了什么
终于在这里让我发现了让我明确的一步
在这里我门会看到 view.assignParent 的设置是 this,那么也就是说在 view 当中 parent 其实实际上是 ViewRoot
那么在 setContentView 当中调用了一个 setLayoutParams()是调用的 ViewRoot 的,而在 ViewRoot 当中发现了 setLayoutParams 和 preformLayout 对 requestLayout 办法的调用
在 requestLayout 当中发现了对 scheduleTraversals 办法的调用而 scheduleTraversals 当中调用了 doTraversal 的拜访,最终拜访到了 performTraversals(),而在这个外面我发现了整体的绘制流程的调以后外面顺次是用了
UI 绘制先回去测量布局,而后在进行布局的摆放,当所有的布局测量摆放结束之后,进行绘制。至此整体 UI 绘制过程咱们就曾经十分分明了,咱们能够依据这种绘制的流程来操作本人的自定义组件。
看完三件事❤️
如果你感觉这篇内容对你还蛮有帮忙,我想邀请你帮我三个小忙: 点赞反对,有你们的『点赞和评论』,才是我发明的能源。