重要的类
- PhoneWindow:自定义内容与DecorView、WMS之间的桥梁。
- Callback/OnWindowDismissedCallback:WMS回调。
- mWindowManager(WindowManagerImpl):向WMS发送信息的装饰层对象。
- mDecor(DecorView):控件树的根节点。
- mContentRoot(ViewGroup):DecorView的唯一子结点,根据mWindowStyle的不同解析不同xml文件生成。
- mContentParent(ViewGroup):mContentRoot中ID为ID_ANDROID_CONTENT的ViewGroup;自定义内容的根节点。
- mWindowAttributes(WindowManager.LayoutParams):窗口参数;随PhoneWindow创建默认值。
- WindowManagerGlobal:进程单例;应用进程向WMS发送信息的客户端Session.Proxy的装饰类。
- mViews:应用进程中所有窗口的DecorView集合。
- mRoots:应用进程中所有窗口的ViewRootImpl集合
- mParams:应用进程中所有窗口的WindowManager.LayoutParams集合。
- 以上3者以索引一一对应。
- ViewRootImpl:WMS与View框架之间协议的实现类;WindowManagerGlobal的细节实现;封装了一棵控件树的信息。
- mWindow(ViewRootImpl.W):窗口唯一标示;其他组件向ViewRootImpl发送信息的服务端。
- mView(DecorView):控件树的根节点。
- mWindowAttributes:
- mAttachInfo:View所属窗口信息;随ViewRootImpl创建;控件树中所有View都持有它的引用。
- mWinFrame;和WMS中窗口的位置尺寸是同步的,在ViewRootImpl.W的回调中被更新。
- mWidth/mHeight:DecorView的尺寸,不是随WMS实时更新的;每次relayout时从mWinFrame读取数据更新。
- mDirty:脏区域。
- mLayoutRequested:在发送测布绘消息前置为true;在执行测布绘时作为进入“控件树布局”的条件之一;在进入“控件树布局”之后置为false。
- mStoped:所属窗口是否处于“停止”状态;在Activity.onStop()后置为true;在Activity.onResume()后置为false;作为进入“控件树布局”的条件之一;
- mLayoutRequesters:当某控件调用requestLayout(),便会把该控件的引用加入mLayoutRequesters;
- mSuface:当前窗口的Surface。
- Surface:代表保存在Native的一个”raw buffer”,”raw buffer”的内容是屏幕上的一块画面;并提供了操作”raw buffer”的方法。
- mLockedObject:保存了Native层”raw buffer”的地址。
- View.AttachInfo:View所属窗口信息。
- mWindowLeft/mWindowTop:View所属窗口的位置,不是随WMS实时更新的;每次relayout时从mWinFrame读取数据更新;是View的绝对坐标的原点。
- mGivenInternalInsets:
- mHardwareAccelerated/mHardwareAccelerationRequest:这两者决定是否启用“硬件绘制”。
- mHardwareRenderer:
- ThreadedRender:硬件绘制的工具类。
- mRootNodeNeedsUpdate:当需要绘制控件树时置为true;作为真正开始绘制(updateRootDisplayList())的条件之一。
- mRootNode:“作用相当于”复制当前处理的DecorView的RenderNode,粘贴给Native层的“绘制执行者”。
- mSurfaceWidth/mSurfaceHeight:当前操作的Surface的尺寸;在DecorView再测量后,取DecorView.mWidth/DecorView.mHeight。
- mInsetTop/mInsetLeft:当前绘制操作的坐标系原点;在DecorView再测量后,取ViewRootImpl.mWindowAttributes.surfaceInsets。
- Choreographer:Android刷新机制在Java层的实现者。
- View:
- mRenderNode:只有根View的才有用;由它创建的DisplayListCanvas是这棵控件树的所有绘制操作的记录者。
- mLayerPaint:
- RenderNode:对应一个Native层的“绘制节点”;封装了所属控件的引用,和Native层的“一组绘制动作”的地址。
- mNativeRenderNode:Native层“一组绘制动作”的地址。
- start():创建一个DisplayListCanvas,并设置Viewport;意味着开始记录“绘制操作”。
- end():通知Native层绘制结束,并回收DisplayListCanvas;意味着结束记录并开始执行“绘制操作”。
- DisplayListCanvas:对应一个RenderNode;Canvas的硬件绘制实现,把“绘制动作”记录在对应的RenderNode.mNativeRenderNode;使用了对象池技术。
控件树结构实现
- View.mParent(ViewParent):该控件的父节点;
- ViewGroup.mChildren(View[]):该控件的所有子节点;
创建控件树
分为两个过程,一是DecorView加入WMS,二是自定义内容接到DecorView。这两个过程不分先后。
DecorView加入WMS
在Activity.handleResumeActivity()中:
- Activity生命周期onNewIntents() -> onActivityResult() -> onResume();
- 获取(有则返回没有创建)Activity中的PhoneWindow;
- 获取(有则返回没有创建)PhoneWindow中的DecorView;
- 获取(有则返回没有创建)PhoneWindow中的WindowManager;
- 获取(有则返回没有创建)PhoneWindow中的WindowManager.LayoutParams;
- 把WindowManager.LayoutParams的type设置为TYPE_BASE_APPLICATION;
- 构造ViewRootImpl;
- 把WindowManager.LayoutParams设置给DecorView;
- 把DecorView加入WindowManager.mViews;
- 把ViewRootImpl加入WindowManager.mRoots;
- 把WindowManager.LayoutParams加入WindowManager.mParams;
- 用DecorView、WindowManager.LayoutParams初始化ViewRootImpl的成员变量;
- 发送测布绘消息;
- 向WMS发送信息“用参数mWindowAttributes,向mDisplay添加一个窗口,窗口唯一标示为mWindow,窗口所属组件标示为mWindowAttributes.token”;
- 进行第一次完整测布绘。
自定义内容接到DecorView
在Activity.setContainView() -> PhoneWindow.setContainView()中:
- 如果DecorView为null,构造DecorView;
- 根据mWindowStyle的不同解析不同xml文件生成mContentRoot;
- 找到mContentRoot中ID为ID_ANDROID_CONTENT的mContentParent;
- 把自定义内容加入mContentParent。
测布绘的驱动力
Choreographer
测量控件树
测量控件树分为三步:自测量、窗口resize、再测量。测量后,控件树中每个控件(包括DecorView)的最终尺寸被赋值到View.measuredWidth/View.measuredHeight。
自测量
- 确定DecorView的指导尺寸。如果是第一次测布绘,应用进程和WMS进程都没有窗口尺寸,如果是系统窗口,取DisplayInfo.logical;如果不是,取DisplayMatrix;如果不是第一次测布绘,应用进程和WMS进程都有上次协商的窗口尺寸,取这个尺寸;
- 开始后根遍历控件树。
- 第一步确定子控件的指导尺寸。
- 如果父控件的指导尺寸是“EXACTLY”,
- 子控件的mLayoutParams是精确数据,那么子控件的指导尺寸是“mLayoutParams的数据,EXACTLY”;
- 子是“MATCH“,那么子的指导尺寸是“父的数据 - padding,EXACTLY”;
- 子是“WRAP“,那么子的指导尺寸是“父的数据 - padding,AT_MOST”;
- 如果父控件的指导尺寸是“AT_MOST”,
- 子控件的mLayoutParams是精确数据,那么子控件的指导尺寸是“mLayoutParams的数据,EXACTLY”;
- 子是“MATCH“,那么子的指导尺寸是“父的数据 - padding,AT_MOST”;
- 子是“WRAP“,那么子的指导尺寸是“父的数据 - padding,AT_MOST”;
- 如果父控件的指导尺寸是“UNSPECIFIED”,
- 子控件的mLayoutParams是精确数据,那么子控件的指导尺寸是“mLayoutParams的数据,EXACTLY”;
- 子是“MATCH“,那么子的指导尺寸是“父的数据 - padding,UNSPECIFIED”;
- 子是“WRAP“,那么子的指导尺寸是“父的数据 - padding,UNSPECIFIED”;
- 第二步,如果子控件有PFLAG_FORCE_LAYOUT标记且可见,
- 如果子控件是ViewGroup,递归3;
- 如果子控件是View,计算尺寸并设置到measuredWidth/measuredHeight。默认的计算方法是,如果指导尺寸是“AT_MOST”或“EXACTLY”,则计算结果为指导尺寸的数据部分;否则为mMinWidth/mMinHeight。View的子类会重写onMeasure()以实现自己特定的计算方法。
- 第三步,根据子控件的计算结果、父控件的指导尺寸,计算父控件的尺寸。ViewGroup没有默认实现方法,子类必须重写onMeasure()以实现自己特定的计算方法。
整个遍历过程总结为:先从根开始进行广度优先遍历,计算所有控件的指导尺寸;再从叶子结点回溯,计算所有控件的准确尺寸。
窗口resize
进入条件
满足以下任意一条。
- 第一次测布绘;
-
DecorView完成自测量;&&(窗口尺寸与协商尺寸不同; 指导尺寸是WRAP; 自测量尺寸与协商尺寸不同) - WMS进程中Insets与应用进程不同;
- DecorView可见性发生变化;
- mWindowAttributes有变化;
- 软键盘有变化;
过程
- 向WMS发送信息“唯一标示为mWindow的窗口请求重新布局,参数为params,宽高为DecorView自测量尺寸”;
- 在WMS中,根据mWindow找到WindowState;
- 根据params和DecorView自测量尺寸修改WindowState的宽高、可见性等;
- 全局窗口排序布局(performLayoutAndPlaceSurfacesLocked());
- 处理Native层Surface;
- 向应用进程发送信息“窗口的位置和尺寸改变了”;
- 在应用进程中,更新ViewRootImpl.mWinFrame;
再测量
进入条件
窗口resize后,ViewRootImpl.mWidth/mHeight与ViewRootImpl.mWinFrame不同
过程
- 根据ViewRootImpl.mWinFrame更新ViewRootImpl.mWidth/mHeight、View.AttachInfo.mWindowLeft/mWindowTop;
- 以ViewRootImpl.mWidth/mHeight为DecorView的指导尺寸,再次后根遍历控件树(与自测量相同)。
布局控件树
布局后,控件树中每个控件(包括DecorView)的mLeft、mTop、mRight、mBottom被赋值。
进入条件
ViewRootImpl.mLayoutRequested为true,且所属窗口不处于“停止”状态。
过程
- 先根遍历控件树;
- 第一步,设置父控件自己的mLeft、mTop、mRight、mBottom。DecorView的取值是0,0,measuredWidth,measuredHeight;
- 第二步,在onLayout()中计算并设置子控件的mLeft、mTop、mRight、mBottom。ViewGroup没有默认实现,子类必须重写onLayout()以实现自己特定的计算方法。
- 第三步,如果子控件是ViewGroup,递归3。
绘制控件树(Java层)
硬件绘制
- mRootNodeNeedsUpdate置为true;
- 重置mDirty;
- 创建一个DisplayListCanvas;
- DisplayListCanvas记录:设置Viewport为DecorView.mWidth/DecorView.mHeight;
- DisplayListCanvas记录:坐标系对齐到ViewRootImpl.mWindowAttributes.surfaceInsets;
- DisplayListCanvas记录:Reorder分界线;
- 先根遍历控件树,依次调用View.draw(),参数是DecorView的RenderNode创建的DisplayListCanvas;最终控件树中所有绘制操作记录在DecorView的RenderNode下;
- DisplayListCanvas记录:绘制操作是DecorView的RenderNode记录的“一组绘制操作”;
- DisplayListCanvas记录:Inorder分界线;
- 通知Native层绘制结束,并回收DisplayListCanvas。
软件绘制
- 为mDirty区域创建一个”raw buffer”,并返回操作”raw buffer”的接口Canvas;
- Canvas操作:设置屏幕分辨率;
- Canvas操作:清除原来的数据;
- 重置mDirty;
- Canvas操作:坐标系对齐到ViewRootImpl.mWindowAttributes.surfaceInsets;
- 先根遍历控件树,依次调用View.draw();最终”raw buffer”就是屏幕上应该显示的内容;
- 把”raw buffer”复制到帧缓存,并释放”raw buffer”。
一些小问题
对透明区域的处理方法
先假设整个窗口是透明的;在布局DecorView之后,遍历所有控件,如果控件没有PFLAG_SKIP_DRAW标记,就把它从透明区域中减去,否则,把View的背景、前景中不透明的部分减去。背景/前景是Drawable类,getTransparentRegion()可以获得它的透明区域。
滑动列表是否会引起测布绘
不会引起列表控件及其父控件的测量、布局,会引起列表控件的绘制、和其内子控件的布局。因为改变mScroll实际上是在改变ViewPort。
requestLayout()的作用
从此View回溯到DecorView,mPrivateFlags加上PFLAG_FORCE_LAYOUT、PFLAG_INVALIDATED;调用ViewRootImpl的requestLayout(),发送测布绘消息。
invalidate()的作用
为此View的mPrivateFlags加上PFLAG_DIRTY;回溯到DevorView,调用invalidateChildInParent(),最终把此View加入ViewRootImpl的mDirty;发送测布绘消息。当测布绘时会进入绘制阶段。
invalidate()和postInvalidate()的区别
- 在UI线程中可以使用invalidate(),也可以使用postInvalidate();
- 在子线程中必须使用postInvalidate();postInvalidate()实质上是利用Handler机制向UI线程发送一个Runnable,执行体中调用了invalidate()。
View排序
绘制过程中会依次遍历ViewGroup.mChildren,并比较子View的Z值。因此Z值越大越在上面;Z值相同的,在ViewGroup.mChildren中索引越大越在上面。