要想搞清楚绘制流程,先要了解Android 中view的树结构。
从源码的角度解析一下上面的图:
首先 DecorView 的父类是个FrameLayout
然后就是下图进一步详细的说 带有TitleView ContentView
其中指导源码里面的视图就是这个 ,也行你会有疑问 Android 系统源码的xml资源文件在哪里?在frameworks/res
然后这个文件在PhoneWindow里面加载
然后phonewindow 给decorview 加载
所以说 DecorView 里面是 一个linearlayout 里面 有Titleview 和content 点视图。最外层是Phonewindow的Framelayout。当然这只是custom_tttle 源码中还有其他布局文件,比如 没有title的等等。
我们主要关注的是Activity ,ViewGroup ,View ,触摸事件的传递方向就是从上到下,再从下返回上,这种u型结构。表述一下:一个触摸事件触发,先来到Actvity的DispatchTouchEvent 方法,Activity接着去询问ViewGroup的dispatchTouchEnvent方法,你告诉我,ViewGroup调用onInterceptouchEvent问自己要不要拦截处理,我不拦截,好,那我问我的子view,调用view的dispachTouchEvent方法,view调用onTouchEvent()方法,到底了,开始往上反。第一种情况:true,告诉你们我处理了,那viewGroup的dispachtouchEvent方法收到true,好,儿子处理了,那我就不用调用我的onTouchEvent方法了,那我赶紧告诉activity,activity的dispachTouchEvent方法收到true的回答,那我Acitity也不用再调用ontouchEvent的方法了。第二种情况:子view 我ontouchEvnet 返回fasle, 告诉你们我不处理,那ViewGroup的dispachTouchEvent就收到fasle,既然儿子不处理,那我调用我的ontouchEvent方法,我的ontoucheEnvent 如果处理了,那Activity的就dispachTouchEvent 收到true,好,父亲干了,也行,我就不用调用ontouchEvnet了,反之,父亲也不干,false告诉你们,那activity的DispachTouchEvent 收到fasle,那activity还要调用ontouchEvent ,事件又回到了顶端。大家都不干。
还有个问题就是大家都不干的时候,为什么Activity 会来两遍
然后是绘制问题:
我在Actvity加载源码解析
提到的Activity加载流程,接着那篇文章接着说(有时间最好一起看看,这样能把actvity加载和view加载的联系到一起), ActivityThread 中的 performLaunchActivity 方法,通过反射创建 Activity 对象,并执行其 attach 方法。Window 就是在这个方法中被创建,详细代码如下:
在 Activity 的 attach 方法中将 mWindow 赋值给一个 PhoneWindow 对象,实际上整个 Android 系统中 Window 只有一个实现类,就是 PhoneWindow。
接下来调用 setWindowManager 方法,将系统 WindowManager 传给 PhoneWindow,如下所示
最终,在 PhoneWindow 中持有了一个 WindowManagerImpl 的引用。
然后就开始Activity的oncreate方法了,里面会有setcontentView。
Activity 的 setContentView
Activity 是 Android 开发人员使用最频繁的 API 之一,最初在接触 Android 开发时,我始终认为它就是负责将 layout 布局中的控件渲染绘制出来的。原因很简单,每当我们想显示一个新的界面时,都是通过 start 一个新的 Activity 方式;对于想显示的内容或者布局,也只需要在 Activity 中添加一行 setContentView 即可,剩下的 Activity 都自动帮我们搞定。但是我们从来没有去创建一个 Window 来绑定 UI 或者 View 元素。
直到我点开 setContentView 源码的那一刻:
PhoneWindow 的 setContentView
Activity 将 setContentView 的操作交给了 PhoneWindow,接下来看下其实现过程:
在 ActivityThread 的 handleResumeActivity 中,会调用 WindowManager 的 addView 方法将 DecorView 添加到 WMS(WindowManagerService) 上,如下所示:
DecorView 被渲染绘制到屏幕上显示;
DecorView 可以接收屏幕触摸事件。
WindowManager 的 addView
PhoneWindow 只是负责处理一些应用窗口通用的逻辑(设置标题栏,导航栏等)。但是真正完成把一个 View 作为窗口添加到 WMS 的过程是由 WindowManager 来完成的。
WindowManager 是接口类型,上文中我们也了解到它真正的实现者是 WindowManagerImpl 类,看一下它的 addView 方法如下:
WindowManagerImpl 也是一个空壳,它调用了 WindowManagerGlobal 的 addView 方法。
WindowMangerGlobal 是一个单例,每一个进程中只有一个实例对象。如上图红框中所示,在其 addView 方法中,创建了一个最关键的 ViewRootImpl 对象,然后通过 ViewRootImpl 的 setView 方法将 view 添加到 WMS 中。
ViewRootImpl 的 setView
解释说明:
图中 1 处的 requestLayout 是刷新布局的操作,调用此方法后 ViewRootImpl 所关联的 View 也执行 measure - layout - draw 操作,确保在 View 被添加到 Window 上显示到屏幕之前,已经完成测量和绘制操作。
图中 2 处调用 mWindowSession 的 addToDisplay 方法将 View 添加到 WMS 中。
WindowSession 是 WindowManagerGlobal 中的单例对象,初始化代码如下:
sWindowSession 实际上是 IWindowSession 类型,是一个 Binder 类型,真正的实现类是 System 进程中的 Session。上图中红框中就是用 AIDL 获取 System 进程中 Session 的对象。其 addToDisplay 方法如下:
图中的 mService 就是 WMS。至此,Window 已经成功的被传递给了 WMS。剩下的工作就全部转移到系统进程中的 WMS 来完成最终的添加操作。
说明:
注释 1 处检查是否为合法线程,一般情况下就是检查是否为主线程。
注释 2 处将请求布局标识符设置为 true,这个参数决定了后续是否需要执行 measure 和 layout 操作。
最后执行 scheduleTraversals 方法,如下:
说明:
注释 1 处向主线程消息队列中插入 SyncBarrier Message。该方法发送了一个没有 target 的 Message 到 Queue 中,在 next 方法中获取消息时,如果发现没有 target 的 Message,则在一定的时间内跳过同步消息,优先执行异步消息。这里通过调用此方法,保证 UI 绘制操作优先执行。
注释 2 处调用 Choreographer 的 postCallback 方法,实际上也是发送一个 Message 到主线程消息队列。
Choreographer 的 postCallback 的执行流程如下:
可以看出最终通过 Handler 发送到 MessageQueue 中的 Message 被设置为异步类型的消息。
mTraversalRunnable 是一个实现 Runnable 接口的 TraversalRunnable 类型对象,其 run 方法如下:
可以看出,在 run 方法中调用了 doTraversal 方法,并最终调用了 ViewRootImpl performTraversals() 方法
ViewRootImpl 类里面的 performTraversals 方法里面会分别调用performMeasure ,performlayout,performdraw方法,分别对应的是onMeasure,onLayout,onDraw 方法。
需要注意ViewGroup遍历子view的顺序问题,因为viewGroup和view 要遍历树的结构的,所以是从ViewGroup onMeasure 调用它的子view onMeasure,拿返回结果,再调用 自己的onlayout,先横着走,再竖着走。还有就是自定义view或viewGroup的时候, 自定义ViewGroup需要重写onmeasure ,onlayout ,ondraw是由子view完成的,它不需要管,自定义View 不用管onlayout方法,布局是它父控件的事,只需要重写onMeasure和ondraw方法就行。