深入安卓系统卷3之控件系统简单笔记

更多关于安卓源码分析文章,请看:安卓源码分析专栏

WindowManager内部都是对进程唯一的WindowManagerGlobal的代理

WindowManagerGlobal
1. 三个数组管理一个进程中所有窗口的信息:views,layoutparams,roots分别保存一个窗口的根控件,Layoutparams,viewrootImpl
2. ViewRootImpl.setView托管了根控件,完成了控件窗口的添加,并且建立了事件接收机制
3. 删除窗口释放资源:ViewRootImpl.die()

ViewRootImpl
1. 会将创建它的线程保存起来作为UI线程
2. 内部有个Handler是将从WMS通信的回调从Binder线程池传递到主线程
3. mAttachInfo保存当前控件树的信息
4. Choreographer用于处理重绘操作
5. performTraversal(控件树尺寸变化或者重绘请求):
1. 预测量:遍历调用子View的onMeasure(measure?),得到控件树每个View为显示内容的期望尺寸
1. 预测量最终调用measureHierarchy(MEASURED_STATE_TOO_SMALL等会导致测量的调整)
2. 布局窗口:IWindowSession.reLayout请求WMS调整窗口属性
1) 因为控件系统有修改窗口的需求
3. 最终测量:控件树期望的尺寸和WMS给的窗口尺寸结果不一致,需要进行“协商”,进行再一次测量(和预测量相比只有参数不一样以窗口的实际尺寸进行测量,还是会遍历子View的onMeasure)
4. 布局控件树:确定View的位置,调用View的layout
5. 绘制:调用View的draw进行绘制

测量:
1. 第一次测量使用屏幕最大的尺寸作为根View的尺寸,以后使用窗口的最新尺寸。
2. 对于悬浮窗的测量,会进行协商。
3. View类的onMeasure仅仅根据背景大小作为尺寸
4. ViewGroup的onMeasure传入每个字View的measureSpec都是一样的?
5. 测量之后将测量结果保存在变量中,并且置位标志位为已测量
6. 一次测量之后,ViewRootImpl需要根据得到的测量结果判断是否需要改变窗口尺寸
7. ViewRootImpl的requestLayout被调用之后,layoutRequest标志位被置为true,接着触发performTraversal的调用。
8. 如果是仅View的绘制的内容发生变化,则通过invalide回溯到ViewRootImpl,通过scheduleTraversal触发performTraversal,此时layoutRequest为false,表明窗口尺寸不发生变化

布局:
1. layoutRequest为布局的条件标志位
2. 如果窗口尺寸发生变化则需要进行窗口布局(WindowManager.updateViewLayout())(开销很大)
3. setFrame设置View四个角的坐标
4. 测量结果仅为参考,真正确定View的尺寸、位置是布局阶段
5. 测量是先序遍历,布局是后序遍历
6. 计算并设置窗口透明区域(为SurfaceView挖洞)

绘制阶段:
1. 跳过绘制阶段的条件:
1. 正在进行窗口动画
2. View不可见
3. 窗口在本次遍历过程中获取了一块新的Surface
2. 在控件树尺寸没有发生变化的情况下通过invalidate也会导致绘制
3. 每次有控件需要重绘,就会记录一个重绘区域Dirty,通过控件树将Dirty赋值给ViewRootImpl的mDirty区域,等待渲染信号来临再绘制。如果在一次Sycn信号来之前有多个控件invalidate,ViewRootImpl会将它们累加到mDirty(如何累加?))
4. ViewRootImpl的scrollToRectOrFocus会根据例如软键盘的弹出等计算出当前控件树的mScroll的值以使得控件树不被遮挡
5. getChildrenDrawingOrder改变子View的绘制顺序(同时影响事件分发顺序)
6. Canvas:
1. 每个控件onDraw中得到的Canvas,都是从窗口坐标一步步变换到当前View的坐标系的
1) 当本身绘制结果后通过restore将坐标系变换到父控件的坐标系
a) draw(Canvascanvas,ViewGroupparent,longdrawingTime)就是在正常的绘制中被ViewGroup(dispatchDraw)调用的方法,里面先对Canvas进行相应的变换:
i) 使用translate进行mLeft和mTop的变换
ii) 动画变换(View.startAnimation())
iii) 自身变换矩阵(setScale等方法的作用)
iv) 滚动量mScrollX/Y的变换
b) mLeft/mTop是本控件在父控件的相对位置,mScrollX/mScrollY指的是控件内容相对于控件坐标系的位置。所以控件坐标系相对于控件内容来说就是(mScrollX,mScrollY)
c) 每次绘制都会将坐标系变换到控件内容的坐标系

2. 软件Canvas:
1) 绘制在建立在Surface的Bitmap
a) Surface.lockCanvas会在Surface内存创建一个Bitmap

3. 硬件Canvas:
1) 绘制目标
a) HardWareLayer(硬件加速下的Bitmap)
b) DisplayList:绘制指令序列(即绘制过程),在需要的时候放回HardWareLayer

4. View动画:
1) Draw–drawAnimation通过Animation.getTransformationn获得当前时间点的变换,将其保存在父控件的成员中,在draw(Canvascanvas,ViewGroupparent,longdrawingTime)对坐标系应用该变换,如果动画没有结束则调用invalidate继续。

屏幕绘制基础:

  1. SurfaceFlinger进程:系统唯一一个实例。用于分配窗口,Surface代表一个窗口
  2. 每个窗口代表一个平面,每个平面代表一段内存(屏幕缓冲区)
  3. 应用程序不可直接访问Surface,只能通过WM向WMS(SystemServer进程)发送一个创建窗口请求(带上新的ViewRoot对象创建的未初始化的Surface对象)
  4. wms通过JNI向SurfaceFlinger请求创建一个段屏幕缓冲区去初始化Surface
  5. 初始化后的Surface对象具有绘制功能,可以通过其Canvas对象对屏幕进行绘制
  6. Java的Canvas只是对C中的SKCanvas的包装而已(最终绘制调用Skia绘制库)
发布了69 篇原创文章 · 获赞 76 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/sinat_23092639/article/details/92389677