主角:ViewRootImpl、Choreographer、Surfaceflinfer
WMS扮演了什么角色?
作为协调者,协调view布局,绘制;
- 在ActivityThread中创建Actiivty后,调用activity.attach()时,创建一个窗体对象PhoneWindow
- PhoneWindow创建了一个WMS的代理桥接类WindowManagerImpl对象,作为WMS在app中的代表;
- WindowManagerImpl对象中的(mGlobal)WindowManagerGlobal专门和WMS通信,在mGlobal里面获取了到了WMS的Binder:getWindowSession()->WMS::openSession();
setContentView()
- 调用PhoneWindow.setContentView(resouseID)
- PhoneWindow中:创建mDector:窗体上的整个View:里面有官方的主题布局+用户自己的布局;
- PhoneWindow中:创建mContentParent:官方主题布局中提供给用户装载布局的容器:id为content;
- 调用mLayoutInflater.inflater(resouseID,mContentParent):
- 解析用户的布局xml
- 递归调用:解析根布局,通过反射创建根布局;解析子view,通过反射创建view;
- 最后PhoneWindow中的mContentParent加载用户的根布局;
- 提交view数据
ps:这里递归调用,若嵌套层级太多,会导致栈溢出;因为递归调用不会释放栈;
ViewRootImpl
单例,管理所有View的绘制策略;
注意onCreate.setContentView后view数据已解析并实例化了;
- 在状态机为Resume时:
- 调用WindowManagerImpl中的mGlobal.addView(view)
- addView中创建ViewRootImpl root=new ViewRootImpl():
- root.setView(view);
- 在setView总调用requestLayout()
- requestLayout()请求绘制,编舞者出场
帧速率:
CPU/GPU出图速率;
刷新率:
屏幕刷新速率;
- 帧速率>刷新率时,出现丢帧(出图好多张了,但是只显示了开头和结尾两张,中间的丢了)
- 帧速率<刷新率,出现卡顿(屏幕刷新好多次了,但是还是显示的第一帧)
Vsync:
垂直同步绘制信号; 因可能硬件帧速率和刷新率不一致,用来同步刷新的问题;
Choreographer编舞者:
负责管理帧率节奏;
- 在内部维护了个Haner和Looper,保证绘制发生在UI主线程:Looper.myLooper==mLooper判断是否是主线程,是的话去调同步绘制信号,不是的话发送消息,走主线程去调同步绘制信号
- 走native层请求垂直同步信号,实际是找底层驱动要上次绘制的时间
- 请求到垂直同步信号后回调onVsync
- 走doFrame去逻辑管控, 判断当前时间离上次绘制的时间大于了1帧的时间(16.66毫秒) 就跳帧(卡顿优化有用到),若小于16.66毫秒就再次请求垂直同步信号,防止重叠
- 执行callback,让ViewRootImpl去真正绘制,调用ViewRootImpl.performTraversals()
真正的绘制:
ViewRootImpl.performTraversals()
- 调用relayoutWindow():
- 创建用户java层的surface:只有用户提供的画面数据;
- 创建native层的surface:包含用户提供的画面数据(java层的surface)+系统的画面数据(状态栏,电池、wifi等等);
- 创建完surface后:依次调用:performMeasure(对应view的onMeasure)、performLayout(onLayout)、performDraw(onDraw);
在performDraw()中:
- 将view的数据传至native层的surface
- surface中的canvas记录数据
- 生成bitmap图像数据(此时数据是在surface中)
- 将surface放入队列中;生产者消费者模式;
- 通知surfaceflinfer进程去队列中取surface数据
- surfaceflinfer拿到不同的surface,进行融合,生成bitmap数据
- 将bitmap数据放入framebuffer中,进行展示
简单版总结:
Activity.setContentView(R.layout.resId):
解析xml并实例化;
- 调用phoneWindow.setContentView(resId)
- 在setContentView中调用installDector():根据不同的主题,找到系统默认的xml,初始化出mDector和mContentParent(反射实例化出对应的ViewGroup)
- 初始化完成后,调用mLayoutInflater.inflate(resId,mContentParent):
- 解析resId的xml文件,将解析的view反射实例化;递归添加到各节点的viewgroup中;最后将自己定义的xml根布局view添加到mContentParent;
绘制发生时间:
在AMS回调ActivityThread中的handleResumeActivity时,也就是Resume时,而不是onCreate();
- 获取PhoneWindow
- 获取PhoneWindow中的mDector布局视图view
- 将mDector布局视图view传给ViewRootImpl
- ViewRootImpl中调用requestLayout()
- requestLayout()中依次调用:performMeasure()、performLayout()、performDraw()