Android图形系统学习框架:Android图形系统
SurfaceFlinger 是在init.rc解析的时候被创建的,执行其main方法,实例化了Surfaceflinger,并向ServiceManager注册,SurfaceFlinger运行在单独进程中。
在Activity创建过程中执行scheduleLaunchActivity之后,便调用到了handleLaunchActivity方法.
首先通过Instrumentation创建Activity.然后执行Activity的attach()方法,创建 PhoneWindow,且与activity建立回调关联。获取WindowManager,层层代理最终干活的是WindowManagerGlobal
notes: scheduleLaunchActivity--handleLaunchActivity--Instrumentation--Activity--attach--PhoneWindow--WindowManager--WindowManagerGlobal
setContentView过程,创建DecorView,并把xml的View树解析出来,加到DecorView上的contentParent部分。
Activity调用makeVisible ,实际上是WindowManagerGlobal执行addView操作,然后调用ViewRootImpl setView操作。
ViewRootImpl setView 做了两件事:
1) requestLayout触发绘制流程
2)mWindowSession.addToDisplay 通过IPC 执行WMS.addWindow
requestLayout :中的relayoutWindow过程中app请求SurfaceFlinger创建Surface
mWindowSession.addToDisplay:最终执行WMS.addWindow方法,该方法流程最终建立了app与SurfaceFlinger服务连接。(window=surface从SF获取)
Android应用程序与SurfaceFlinger服务是运行在不同的进程中的,用Binder进行通信,用匿名共享内存进行UI数据传递。
requestLayout draw的流程中:Surface通过dequeueBuffer获取一块GraphicBuffer, 然后onDraw中通过传入的Java层Canvas 调用底层Skia引擎中的SKCanvas(画家)、SKBitmap(画布)进行具体绘制操作.
绘制完成之后把图形数据放入GraphicBuffer.
最后Surface执行queueBuffer,把这块带有图形数据的buffer送回BufferQueue,并通过onFrameAvailable通知Layer更新。(绘图使用2d不是3d opengl,那么合成使用GPU glse)
SurfaceFlinger合成图层依赖于Android的异步消息处理机制,每16ms接收一次vsync信号来执行图层合成操作,最终通过handleMessageRefresh一系列方法的处理,其中包括把GraphicBuffer数据映射为OpenGL的texture ,收集所有Layer计算显示区域,然后通过openG 或 HWC进行合成以及栅格化处理,最后送显。
绘制优化:一部分是原理机制篇.另外一部分就是优化实践篇。
那么本篇文章就开启原理机制部分的总结,打算先捋一下window的加载视图过程。
一、了解整体视图关系
Activity: 本身不是窗口,也不是视图,它只是窗口的载体,一方面是key、touch事件的响应处理,一方面是对界面生命周期做统一调度.
Window: 一个顶级窗口查看和行为的一个抽象基类。它也是装载View的原始容器, 处理一些应用窗口通用的逻辑。使用时用到的是它的唯一实现类:PhoneWindow。Window受WindowManager统一管理。
DecorView: 顶级视图.一般情况下它内部会包含一个竖直方向的LinearLayout,上面的标题栏(titleBar),下面是内容栏。通常我们在Activity中通过setContentView所设置的布局文件就是被加载到id为android.R.id.content的内容栏里(FrameLayout)。
二、window的类型
添加窗口是通过WindowManagerGlobal的addView方法操作的,这里有三个必要参数:view,params,display。
display : 表示要输出的显示设备。
view : 表示要显示的View,一般是对该view的上下文进行操作。(view.getContext())
params : 类型为WindowManager.LayoutParams,即表示该View要展示在窗口上的布局参数。有2个比较重要的参数:flags
,type
。
-flags:表示Window的属性:
-type: 表示窗口的类型
这个type层级到底有什么作用:
Window是分层的,每个Window都有对应的z-ordered,(z轴,从1层层叠加到2999,你可以将屏幕想成三维坐标模式)层级大的会覆盖在层级小的Window上面。如果想要Window位于所有Window的最顶层,那么采用较大的层级即可。另外有些系统层级的使用是需要声明权限的。
那么照这么说,最底层的应该是应用window,子window在其上,系统window在最上面,这也符合视图展示的预期效果。
另外WindowManager的LayoutParams中还有个token:主要作用是为了维护activity和window的对应关系。
三、window的创建
讲window的创建过程,那么肯定得了解activity的启动流程,但是在这里不详细说activitiy的启动流程了,因为后面会有计划单独拎出四大组件开篇章来讲解启动流程。
那么简单的先上个图了解下activity的启动流程(借用辉辉的图):
四、window添加view过程
我们前面知道PhoneWindow对View来说更多是扮演容器的角色,而真正完成把一个 View,作为窗口添加到 WMS 的过程是由 WindowManager 来完成的。而且从上面创建过程我们知道了WindowManager 的具体实现是 WindowManagerImpl。
下面的setViewContent部分有详细介绍
下面用一张图来总结下Activity、PhoneWindow、 DecorView 、WindowManagerGlobal 、ViewRootImpl 、Wms 以及WindowState之间的关系:
Activity在attach的时候,创建了一个PhoneWindow对象,并且实现了Window的Callback接口,这样activity就和window绑定在了一起,通过setContentView,创建DecorView,并解析好视图树加载到DecorView的contentView部分,WindowManagerGlobal一个进程只有唯一一个,对当前进程内所有的视图进行统一管理,其中包括ViewRootImpl,它主要做两件事情,先触发view绘制流程,再通过IPC 把view添加到window上。
另外这是添加视图的方法执行时序图:
至于Window的删除和更新过程,举一反三,也是使用WindowManagerGlobal对ViewRootImpl的操作,最终也是通过Session的IPC跨进程通信通知到WmS。整个过程的本质都是同出一辙的。下一节接着讲DecorView布局的加载流程。
上篇我们了解了window的创建过程和添加视图的流程,但是顶级视图DecorView是怎么被加载的呢?其实这个过程非常简单,分析下setContentView的过程,一切就明了了。
先交代下PhoneWindow 与DecorView 以及mContentParent的关系:
mDecor是窗口顶层视图,mContentParent是mDecor上content framelayout的父容器,用来装xml解析出来的view树。
2.1.setContentView
setContentView是window的一个抽象方法,真正实现类是PhoneWindow.
setContentView有两个重载方法:一个是解析xml视图,一个是直接传入视图
//PhoneWindow
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//1.初始化
//创建DecorView对象和mContentParent对象 ,并将mContentParent关联到DecorView上
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();//Activity转场动画相关
}
//2.填充Layout
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);//Activity转场动画相关
} else {
//将Activity设置的布局文件,加载到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
//让DecorView的内容区域延伸到systemUi下方,防止在扩展时被覆盖,达到全屏,沉浸等不同体验效果.
mContentParent.requestApplyInsets();
//3. 通知Activity布局改变
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//触发Activity的onContentChanged方法
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
那么核心方法就两个:installDecor() 和 mLayoutInflater.inflate(layoutResID, mContentParent).
2.2 installDecor
主要调用函数介绍:generateDecor(-1)很简单,就是new DecorView
重点关注下 mContentParent = generateLayout(mDecor);
protected ViewGroup generateLayout(DecorView decor) {
//1,为Activity配置相应属性,即android:theme=“”,PhoneWindow对象调用getWindowStyle()方法获取值。
TypedArray a = getWindowStyle();
...
//2,获取窗口Features, 设置相应的修饰布局文件,这些xml文件位于frameworks/base/core/res/res/layout下
int layoutResource;
int features = getLocalFeatures();//指定requestFeature()指定窗口修饰符,PhoneWindow对象调用getLocalFeature()方法获取值;
...
mDecor.startChanging();
//3, 将上面选定的布局文件inflate为View树,添加到decorView中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//4,将窗口修饰布局文件中id="@android:id/content"的View赋值给mContentParent, 后续自定义的view/layout都将是其子View
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
mDecor.finishChanging();
return contentParent;
}
installDecor() 做了这么几件事:
1) 创建一个DecorView的对象mDecor,该mDecor对象将作为整个应用窗口的根视图。
2) 配置不同窗口修饰属性(style theme等)。
3) 将DecorView布局中id为content的FrameLayou的Viewt赋值给mContentParent
至此,DecorView 的 contentView 大容器已经设置完成, 但是里面并没有内容,原因是用户自定义的xml文件还没有解析加载到contentView上。
2.3 LayoutInflater.inflate(layoutResID, mContentParent)解析加载视图
典型的pull解析的方式,深度优先地递归解析xml,一层层添加到root view上,最终返回root view.解析的部分大致包含两点:1.解析出View对象,2.解析View对应的Params,并设置给View。
而我们看到LayoutInflater.inflate(layoutResID, mContentParent),传进去的是mContentParent,也就是最终root view就是mContentParent。
xml布局解析完毕,且add到了mContentParent上。
至此DecorView视图组建完成。
稍微总结一下流程:
2.4、DecorView的添加
最终是WMS执行addWindow操作.
下面一张图总结下:
最最重要的是:root.setView(view, wparams, panelParentView); 一方面触发绘制流程,一方面把view添加到window上 .
讲setView之前先普及下WindowManager与WindowManagerService binder IPC的两个接口:IWindowSession: 应用程序向WMS请求功能.实现类:Session
IWindow:WMS向客户端反馈它想确认的信息.实现类:W
下面看看ViewRootImpl的setView
在ViewRootImpl的setView()方法里,
1.执行requestLayout()方法完成view的绘制流程(之后会讲)
2.通过WindowSession将View和InputChannel添加到WmS中,从而将View添加到Window上并且接收触摸事件。这是一次IPC 过程。
当启动Activity调运完ActivityThread的main方法之后,接着调用ActivityThread类performLaunchActivity来创建要启动的Activity组件,在创建Activity组件的过程中,还会为该Activity组件创建窗口对象和视图对象;接着Activity组件创建完成之后,通过调用ActivityThread类的handleResumeActivity将它激活。
我们从ActivityThread的handleLaunchActivity()方法开始
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
Activity a = performLaunchActivity(r, customIntent);
...
}
``
performLaunchActivity中:
通过Instrumentation.newActivity的方法创建Activity, 在之后activity执行attach方法会初始化PhoneWindow.
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
…
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
...
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
}
…
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
...
}
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE); //显示DecorView
}
添加了decorView, 追下下实现类:WindowManagerImpl ,看看addView方法:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
这个mGlobal 即:WindowManagerGlobal, 那就看他的addView方法:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
root.setView(view, wparams, panelParentView);
}
创建ViewRootImpl, 并将DecorView添加到ViewRootImpl上,那么添加的动作是setView 看下:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
//发起绘制流程
requestLayout();
…
//设置ViewRootImpl为DecorView的父控件
view.assignParent(this);
...
}} }
了到这大概已经清楚了整个布局加载流程,其实非常简单,就是创建DecorView,并把xml的View树解析出来,加到DecorView上,形成完整的View组件的过程。下一节接着讲从ViewRootImpl开始的绘制流程。
//这章把几个流程图看懂即可
接上篇 绘制优化-原理篇2-DecorView布局加载流程 讲到的ViewRootImpl,在ViewRootImpl的setView()方法里主要做两件事:
1.执行requestLayout()方法完成view的绘制流程
2.通过WindowSession将View和InputChannel添加到WmS中,从而将View添加到Window上并且接收触摸事件。
2的部分 window加载视图已经介绍了,那么今天就来讲讲1的部分:执行requestLayout()方法完成view的绘制流程
setview->在window add之前调用,确保UI布局绘制完成 -->measure,layout,draw-->mWindowSession.addToDisplay:通过windowsesion进行IPC调用,将view添加到window上。
从requestLayout代码一层层往下追,最终确认view的绘制流程是从performTraversals开始。顺一下整个流程
1.1 performTraversals
private void performTraversals() {
//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.width和lp.height表示
DecorView根布局宽和高
WindowManager.LayoutParams lp = mWindowAttributes;
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// Ask host how big it wants to be
//执行测量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//执行布局操作
performLayout(lp, mWidth, mHeight);
...
//执行绘制操作
performDraw();
}
1.2 MeasureSpec
MeasureSpec 是View的尺寸一种封装手段。MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SepcSize.
下面针对DecorView和普通View分别来看看其MeasureSpec的组成:
这个表怎么用呢?举个例子:
如果View在布局中使用wrap_content,那么它的specMode是AT_MOST.
这种模式下,它的宽高为specSize, 而查表可得View的specSize是parentSize,而parentSize是当前父容器剩余空间大小,这种效果和在布局中使用match_parent完全一致,所以如果是对尺寸有具体要求的自定义控件需要指定specSize大小。
注:LayoutParams类是用于子视图向父视图传达自己尺寸的一个参数包,包含了Layout的高,宽信息.
LayoutParams在LayoutInflater.inflater过程中与View一起被解析成对象,保存在WindowManagerGlobal集合中.
二、View绘制流程
performTraversals里面执行了三个方法,分别是performMeasure()、performLayout()、performDraw()这三个方法,这三个方法分别完成DecorView的measure,layout,和draw这三大流程.
其中performMeasure()中会调用measure()方法,在measure()方法中又会调用onMeasure()方法,在onMeasure()方法中会对所有子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中了,这样就完成了一次measure过程。
接着子元素会重复父容器的measure过程,如此反复就实现了从DecorView开始对整个View树的遍历测量,measure过程就这样完成了。
同理,performLayout()和performDraw()也是类似的传递流程。
notes:onMeasure作用获取view/viewgroup的measurespec,onLayout作用framelayout/linearlayout,left/top/right/bottom; ondraw放置各种控件
针对performTraveals()的大致流程,可以用以下流程图来表示:
以上的流程图只是一个为了便于理解而简化版的流程,真正的流程应该分为以下五个工作阶段:
预测量阶段:这是进入performTraversals()方法后的第一个阶段,它会对View树进行第一次测量.
在此阶段中将会计算出View树为显示其内容所需的尺寸,即期望的窗口尺寸.(调用measureHierarchy)
窗口布局阶段:根据预测量的结果,通过IWindowSession.relayout()方法向WMS请求调整窗口的尺寸等属性,这将引发WMS对窗口进行重新布局,并将布局结果返回给ViewRootImpl。(调用relayoutWindow())
测量阶段:预测量的结果是View树所期望的窗口尺寸。然而由于在WMS中影响窗口布局的因素很多,WMS不一定会将窗口准确地布局为View树所要求的尺寸,而迫于WMS作为系统服务的强势地位,View树不得不接受WMS的布局结果。因此在这一阶段,performTraversals()将以窗口的实际尺寸对View树进行最终测量。(调用performMeasure())
布局阶段:完成最终测量之后便可以对View树进行布局了。(调用performLayout())
绘制阶段:这是performTraversals()的最终阶段。确定了控件的位置与尺寸后,便可以对View树进行绘制了。(调用performDraw())
下面分别来阐述:
2.1 预测量阶段(performTraversals)
这个阶段在performTraversals中最先发生,对View树进行第一次测量,会判断当前期望窗口尺寸是否能满足布局要求。
measureHierarchy()方法最终也是调用了performMeasure()方法对View树进行测量,只是多了协商测量的过程。
2.2 窗口布局阶段(relayoutWindow)
调用relayoutWindow()来请求WindowManagerService服务计算Activity窗口的大小以及过扫描区域边衬大小和可见区域边衬大小。计算完毕之后,Activity窗口的大小就会保存在成员变量mWinFrame中,而Activity窗口的内容区域边衬大小和可见区域边衬大小分别保存在ViewRoot类的成员变量mPendingOverscanInsets和mPendingVisibleInsets中。
这部分不在这细讲了,有耐心的可以把老罗的文章看完:Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析
2.3 测量过程(performMeasure())
WMS的布局结果已经确定了,不管是否满意都得开始终极布局过程了,下面介绍下measure:
measure是对View进程测量,确定各View的尺寸的过程,这个过程分View和ViewGroup两种情况来看.
对于View,通过measure完成自身的测量就行了,而ViewGroup除了完成自身的测量外,还需要遍历去调用所有子view的measure方法,各个子view递归去执行这个过程。
那么先从performMeasure开始:
先判断一下是否有必要进行测量操作,如果有,先看是否能在缓存mMeasureCache中找到上次的测量结果,如果找到了那直接从缓存中获取就可以了.如果找不到,那么乖乖地调用onMeasure()方法去完成实际的测量工作,并且将尺寸限制条件widthMeasureSpec和heightMeasureSpec传递给onMeasure()方法。
2.3.1 View的measure过程:
主要看onMeasure()方法,这里才是真正去测量并设置View大小的地方。
2.3.2 ViewgGroup的measure过程:
ViewGroup提供了几个方法来帮助ViewGroup的子类来实现onMeasure逻辑,包括:
仔细看其实最终还是让child去执行自己对于的measure,只是getChildMeasureSpec有差别,这里加上了margin 和 padding.
具体onMeasure的实现可以参考LinearLayout、FrameLayout、RelativeLayout等。
另外需要关注的是ViewGroup 的 getChildMeasureSpec方法,我们从上面代码中很明显看出,传入的Spec是父容器的measureSpec
很明显看出来,对于普通View来说,getChildMeasureSpec的逻辑是通过其父View提供的MeasureSpec参数得到specMode和specSize,然后根据计算出来的specMode以及子View的childDimension(layout_width或layout_height)来计算自身的measureSpec 。
measure总结://和《android开发艺术探索》这段描述一样
MeasureSpec 由specMode和specSize组成:
DecorView, 其MeasureSpec由窗口尺寸和其自身LayoutParams共同决定。
普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定。
View的measure方法是final的,不允许重载.View子类只重载onMeasure完成自己的测量逻辑.
ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,以及getChildMeasureSpec方法,供具体实现ViewGroup的子类重写onMeasure的时候方便使用.
只要是ViewGroup的子类就必须要求LayoutParams继承子MarginLayoutParams,否则无法使用layout_margin参数。
使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。
比较常用的方式:
view.post(runnable)
view.measure(0,0)之后 get
2.4 布局过程 (performLayout())
Layout的作用是ViewGroup用来确定子view的位置,当ViewGroup的位置被确定之后,它在onLayout中会遍历所有子view并调用其layout方法,在layout方法中onLayout又被调用。
先从performLayout看起:
跟踪代码进入View类的layout方法
public void layout(int l, int t, int r, int b) {
//保存上一次View的四个位置
int oldL = mLeft;int oldT = mTop;int oldB = mBottom;int oldR = mRight;
//如果布局有改变,条件成立,则视图View重新布局
//调用onLayout,将具体布局逻辑留给子类实现
onLayout(changed, l, t, r, b);
ArrayList listenersCopy = (ArrayList)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
} }
首先关注下需要重新layout的条件:
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
其中setOpticalFrame内部也会调用setFrame,所以就看下setFrame好了:
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
...
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
...
}
return changed;
}
通过setFrame方法设定View的四个顶点的位置,并更新本地值,同时判断顶点位置较之前是否有变化,并return是否有变化的boolean值,如果有变化还会执行invalidate(sizeChanged)。
ViewGroup中是个抽象方法,子类必须实现
下面以RelativeLayout为例,对onLayout具体实现做简单的分析:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// The layout has actually already been performed and the positions
// cached. Apply the cached values to the children.
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {//只有不为GONE的才会执行布局
RelativeLayout.LayoutParams st =
(RelativeLayout.LayoutParams) child.getLayoutParams();
child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
}} }
遍历所有子view,并通过其LayoutParams 获取四个方向的位置值,将位置信息传入子view的layout方法进行布局。
layout总结: //l,t,r,b 四个点的坐标
Layout的作用是ViewGroup用来确定子view的位置, 是ViewGroup需要干的活,View不需要,所以View中是空方法,而ViewGroup中是抽象方法,但是View你也可以重写,大多数是利用这个生命周期阶段加写逻辑操作。
当我们的视图View在布局中使用 android:visibility=”gone” 属性时,是不占据屏幕空间的,因为在布局时ViewGroup会遍历每个子视图View,判断当前子视图View是否设置了 Visibility==GONE.如果设置了,当前子视图View就不会添加到父容器上,因此也就不占据屏幕空间.
具体可以参考RelativeLayout的onLayout.
必须在View布局完之后调用getHeight( )和getWidth( )方法获取到的View的宽高才大于0.
layout的整体执行流程:
2.5 绘制过程 (performDraw())
Draw作用是将View绘制到屏幕上.过程相对比较简单。
//ViewRootImpl
performDraw() -- draw(fullRedrawNeeded);--drawSoftware---mView.draw(canvas)
那么接着往下的话就是真正View绘制的部分了:
//View
public void draw(Canvas canvas) {
......
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1\. Draw the background
* 2\. If necessary, save the canvas' layers to prepare for fading
* 3\. Draw view's content
* 4\. Draw children
* 5\. If necessary, draw the fading edges and restore layers
* 6\. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
......
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
......
// Step 2, save the canvas' layers
......
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
......
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
......
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
......
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
......
}
从摘要可以看出,绘制过程分如下几步:
private void drawBackground(Canvas canvas) {
//获取xml中通过android:background属性或者代码中setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景Drawable
//根据layout过程确定的View位置来设置背景的绘制区域
.....
//调用Drawable的draw()方法来完成背景的绘制工作
background.draw(canvas);
......
}
@Override
protected void dispatchDraw(Canvas canvas) {
......
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
......
for (int i = 0; i < childrenCount; i++) {
......
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
......
}
可以看见,ViewGroup确实重写了View的dispatchDraw()方法,该方法内部会遍历每个子View,然后调用drawChild()方法,我们可以看下ViewGroup的drawChild方法,如下:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
可以看见其实任何一个View都是有(水平垂直)滚动条的,只是一般情况下没让它显示而已。这部分不做详细分析了。
draw拓展点
默认情况下子View的ViewGroup.drawChild绘制顺序和子View被添加的顺序一致,但是你也可以重载ViewGroup.getChildDrawingOrder()方法提供不同顺序。
draw整体执行流程:
在之前分析的绘制流程中,我们或多或少都见过这三个方法,他们到底是干什么的,下面做下简单说明:
View#invalidate( ) 和 View#postInvalidate( )
invalidate和postInvalidate:都是用来重绘View,区别就是invalidate只能在主线程中调用,postInvalidate可以在子线程中调用.
View#requestLayout
requestLayout: 当前view及其以上的viewGroup部分都重新走ViewRootImpl 重新绘制 ,分别重新onMeasure onLayout onDraw ,其中onDraw比较特殊,有内容变化才会触发。
最后一张图总结下invalidate/postInvalidate 和 requestLayout
requestLayout 和 invaldate 有什么区别?
requestLayout 和 invalidate 都会触发整个绘制流程。但是在 measure 和 layout 过程中,只会对 flag 设置为 FORCE_LAYOUT 的情况进行重新测量和布局,而 draw 只会重绘 flag 为 dirty 的区域
requestLayout 是用来设置 FORCE_LAYOUT 标志,invalidate 用来设置 dirty 标志。所以 requestLayout 只会触发 measure 和 layout,invalidate 只会触发 draw。
所以一般都是组合使用。比如:只要刷新的时候就调用 invalidate,需要重新 measure 就调用 requestLayout,后面再跟个 invalidate(为了保证重绘)
本篇文章对之前3篇描述的Activity、Window、View关系做个粗略的总结.
在Activity 创建过程中执行 scheduleLaunchActivity() 之后便调用到了 handleLaunchActivity() 方法.
1 handleLaunchActivity内调用performLaunchActivity()
//创建目标Activity对象
activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent);
2 执行activity.attach()
//创建 PhoneWindow
mWindow = new PhoneWindow(this);
//与activity建立回调关联
mWindow.setCallback(this);
//设置并获取 WindowManagerImpl 对象
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken...);
mWindowManager = mWindow.getWindowManager();
针对WindowManager要多说两句,每个 Activity 会有一个 WindowManager 对象,这个 mWindowManager 就是和 WindowManagerService 进行通信,也是 WindowManagerService 识别 View 具体属于那个 Activity 的关键,创建时传入 IBinder 类型的 mToken。
3 回调 Activity.onCreate()
会执行setContentView方法
installDecor(); 主要就是初始化了DecorView
mLayoutInflater.inflate(layoutResID, mContentParent);//将layout解析为View树,添加到DecorView的contentView部分
这时只是创建了 PhoneWindow,和DecorView,但目前二者也没有任何关系。
4. WindowManagerGlobal.addView()
在ActivityThread.performResumeActivity 中,调用 r.activity.performResume(),调用 r.activity.makeVisible(), makeVisible中:WindowManager 的 addView 的具体实现在 WindowManagerImpl 中.而 WindowManagerImpl 的 addView 又会调用 WindowManagerGlobal.addView():
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
...
ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
...
}
一个app进程共享一个WindowManagerGlobal,它是一个统筹大管家,内部方法主要是对View的处理 和 与 WMS的 IPC.
对View的处理交给它的得力助手ViewRootImpl:
5. ViewRootImpl setView()
以WindowManagerGlobal的addView为例,最终会调用ViewRootImpl setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
…
//开启DecorView绘制流程
requestLayout();
...
//将DecorView添加到window上
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
...
}}
两张图总结下:
setContentView流程
总结:
Activity主要作用还是生命周期的管理,Window是一个视图容器,将Activity与View解耦,WindowManager统一管理View。
所以, Activity与window的关联主要还是体现在生命周期的管理,和key touch事件回调上。 Window与View的关联体现在对View视图的处理上。
参考:Android10_原理机制系列_事件传递机制 - 流浪_归家 - 博客园
消息通过InputChannel发送到目标窗口的进程了。接下来看目标窗口是如何接收传递的。
前面也提到过,这里创建的mInputChannel 最终作为参数传递到WMS中,此时它什么都没有。
在 WMS.addWindow()中 WindowState创建了一对InputChannel,其中一个通过transferTo()传递给了 mInputChannel。接下来就看WindowInputEventReceiver的创建。
下面进入view的输入系统,
从JAVA层接收-->native looper监听fd ->回调到java 层input 系统->Activtiy
Activity--DecorView--ViewGroup--view(dispatchKeyEvent--KeyEvent)
1) WindowInputEventReceiver 的创建 //java层对外接收
//ViewRootImpl.java
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
}
//InputEventReceiver.java
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
...
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference(this),
inputChannel, mMessageQueue);
mCloseGuard.open("dispose");
}
通过nativeInit() 进入 native层:(view 的input 接口)
//android_view_InputEventReceiver.cpp
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
jobject inputChannelObj, jobject messageQueueObj) {
...
//参考2.2,创建NativeInputEventReceiver
sp receiver = new NativeInputEventReceiver(env,
receiverWeak, inputChannel, messageQueue);
//参考2.3,执行initialize
status_t status = receiver->initialize();
...
receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object
return reinterpret_cast(receiver.get());
}
2) 创建NativeInputEventReceiver
//android_view_InputEventReceiver.cpp
class NativeInputEventReceiver : public LooperCallback {
NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env,
jobject receiverWeak, const sp& inputChannel,
const sp& messageQueue) :
mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
mInputConsumer(inputChannel), mMessageQueue(messageQueue),
mBatchedInputEventPending(false), mFdEvents(0) {
...
}
InputConsumer::InputConsumer(const sp& channel) :
mResampleTouch(isTouchResamplingEnabled()),mChannel(channel), mMsgDeferred(false){
}
创建NativeInputEventReceiver,注意两个地方 后面会讲到的:
3) 执行initialize() //looper socket 监听
//android_view_InputEventReceiver.cpp
status_t NativeInputEventReceiver::initialize() {
setFdEvents(ALOOPER_EVENT_INPUT);
return OK;
}
void NativeInputEventReceiver::setFdEvents(int events) {
if (mFdEvents != events) {
mFdEvents = events;
int fd = mInputConsumer.getChannel()->getFd();
if (events) {
//fd添加到Looper中,监听InputChannel 读取事件
mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
} else {
mMessageQueue->getLooper()->removeFd(fd);
}} }
/*** The file descriptor is available for read operations.*/
ALOOPER_EVENT_INPUT = 1 << 0,
addFd()参数this是LooperCallback,即NativeInputEventReceiver。
跟踪下,fd最终添加到Looper的mRequests列表中。
当Looper监听到有输入事件时,会回调 NativeInputEventReceiver的handleEvent()方法。 (这里面的机制也还没细究)
这个可以参考下:Looper::pollInner()中 int callbackResult = response.request.callback->handleEvent(fd, events, data);
。
4) 回调handleEvent()
//android_view_InputEventReceiver.cpp
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
...
if (events & ALOOPER_EVENT_INPUT) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
//获取事件,然后回调到java层的 InputEventReceiver.dispatchInputEvent
status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
}
...
return 1;
}
//android_view_InputEventReceiver.cpp
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
...
ScopedLocalRefreceiverObj(env, NULL);
bool skipCallbacks = false;
for (;;) {
uint32_t seq;
InputEvent* inputEvent;
//从InputChannel读取信息,并处理保存事件到inputEvent,参考2.4.1
status_t status = mInputConsumer.consume(&mInputEventFactory,
consumeBatches, frameTime, &seq, &inputEvent);
...
if (inputEventObj) {
...
//回调java层的 InputEventReceiver.dispatchInputEvent,参考2.4.2
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
}...}}
//jni注册:android_view_InputEventReceiver.cpp
int register_android_view_InputEventReceiver(JNIEnv* env) {
int res = RegisterMethodsOrDie(env, "android/view/InputEventReceiver",
gMethods, NELEM(gMethods));
jclass clazz = FindClassOrDie(env, "android/view/InputEventReceiver");
gInputEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
gInputEventReceiverClassInfo.dispatchInputEvent = GetMethodIDOrDie(env,
gInputEventReceiverClassInfo.clazz,
"dispatchInputEvent", "(ILandroid/view/InputEvent;)V");
gInputEventReceiverClassInfo.dispatchBatchedInputEventPending = GetMethodIDOrDie(env,
gInputEventReceiverClassInfo.clazz, "dispatchBatchedInputEventPending", "()V");
return res;
}
NativeInputEventReceiver::handleEvent() 到 NativeInputEventReceiver::consumeEvents()。这里关注两个:
a) 从InputChannel读取事件
//InputTransport.cpp
status_t InputConsumer::consume(InputEventFactoryInterface* factory,
bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
...
*outSeq = 0; *outEvent = nullptr;
// Fetch the next input message.
// Loop until an event can be returned or no additional events are received.
while (!*outEvent) {
//前面列出过InputConsumer创建时 mMsgDeferred为false
if (mMsgDeferred) {
...
} else {
// Receive a fresh message.
//mChannel接收消息,即从socket中读取
status_t result = mChannel->receiveMessage(&mMsg);
...
}...}
switch (mMsg.header.type) {
case InputMessage::TYPE_KEY: {
...
initializeKeyEvent(keyEvent, &mMsg);
*outSeq = mMsg.body.key.seq;
*outEvent = keyEvent;
break;
}
case InputMessage::TYPE_MOTION: {
...
updateTouchState(mMsg);
initializeMotionEvent(motionEvent, &mMsg);
*outSeq = mMsg.body.motion.seq;
*outEvent = motionEvent;
break;
}}return OK;}
通过InputChannel接受IMS端发送过来的消息,并且根据事件类型做了一些处理。
b) 回调java层的 InputEventReceiver.dispatchInputEvent
//InputEventReceiver.java
@UnsupportedAppUsage
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
前面知道,创建的是InputEventReceiver的子类WindowInputEventReceiver,因此onInputEvent()调用的是子类中方法:
//ViewRootImpl.java
final class WindowInputEventReceiver extends InputEventReceiver {
@Override
public void onInputEvent(InputEvent event) {
...
//输入事件 加入队列
enqueueInputEvent(event, this, 0, true);
}}
5) 输入事件处理 //放入queue
这里继续看 enqueueInputEvent():
//ViewRootImpl.java
@UnsupportedAppUsage
void enqueueInputEvent(InputEvent event,InputEventReceiver receiver...) {
//获取QueuedInputEvent,event等封装进去。
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
//获取队尾
QueuedInputEvent last = mPendingInputEventTail;
//插入队尾
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
//数目加1
mPendingInputEventCount += 1;
//是否立即执行
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
enqueueInputEvent() 首先将event等信息封装到了QueuedInputEvent,然后将其插入输入事件队列的队尾。
继续看doProcessInputEvents():
//ViewRootImpl.java
void doProcessInputEvents() {
// Deliver all pending input events in the queue.
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
...
deliverInputEvent(q);
}
}
循环处理队列中所有事件,每次取队首元素 传递处理。交由deliverInputEvent()方法处理。
6) 输入事件 传递到View
继续看deliverInputEvent():
//ViewRootImpl.java
private void deliverInputEvent(QueuedInputEvent q) {
...
InputStage stage;
if (stage != null) {
handleWindowFocusChanged();
//传递,参考3.1
stage.deliver(q);
} else {
finishInputEvent(q);
} }
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
....
}
在setView() 中,创建了 input pipeline,将事件一层层传递下去。 调用stage.deliver(q);
传递下去.
notes: Activity--DecorView--ViewGroup--view(dispatchKeyEvent--KeyEvent)
前面讲到事件已经传递到input pipeline中。这个暂不细究,往下继续看传递到View中的传递。
1) 传递到DecorView
直接看 stage.deliver(q) :
//ViewRootImpl.java
abstract class InputStage {
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else {
apply(q, onProcess(q));
}} }
onProcess(q)返回一个处理结果,apply根据这个结果再决定是否传递到InputStage的下一层。
这主要关注的 onProcess()。在ViewPostImeInputStage阶段,开始向DecorView传递。
//ViewRootImpl.java
final class ViewPostImeInputStage extends InputStage {
@Override
protected int onProcess(QueuedInputEvent q) {
//处理不同类型的事件
if (q.mEvent instanceof KeyEvent) {
//按键事件处理
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}} }
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
...
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
...
return FORWARD;
} }
onProcess()中对不同类型事件进行不同的处理。这里仍然以按键事件为例,处理方法processKeyEvent()。
这个mView即DecorView,setView()时 传入的。
为什么是DecorView? 这个过程请参考:Android10_原理机制系列_Activity窗口添加到WMS过程。
2) 传递到Activity
//DecorView.java
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final boolean isDown = action == KeyEvent.ACTION_DOWN;
...
if (!mWindow.isDestroyed()) {
final Window.Callback cb = mWindow.getCallback();
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}
return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
: mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
如果是Activity的窗口,cb获取到的是Activity,mFeatureId是-1。
这里的mWindow是PhoneWindow,即Activity在attach()时 创建的PhoneWindow,在setContentView()过程 通过mDecor.setWindow()传入到DecorView中的。
这个mWindow.getCallback获取的是Activity本身,即Activity在attach()时setCallback传入的this本身.
这个过程请参考( 那篇窗口添加到WMS中 说的很明白,这里不列出了): Android10_原理机制系列_Activity窗口添加到WMS过程。
由于按键事件 和 触摸事件是 最常见的,这里都简单列举了下。
a) 按键事件传递处理
接着前面,按键事件 可以直接看cb.dispatchKeyEvent(event):
//Activity.java
public boolean dispatchKeyEvent(KeyEvent event) {
...
Window win = getWindow();
//交由Window继续传递,返回false,则继续交由Activity处理。若返回的true,则下层已处理掉了。
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
//PhoneWindow.java
@Override
public boolean superDispatchKeyEvent(KeyEvent event) {
//传递给DecorView
return mDecor.superDispatchKeyEvent(event);
}
//DecorView.java
public boolean superDispatchKeyEvent(KeyEvent event) {
...
//传递到ViewGroup。返回true,则下层处理了 上层不处理。
if (super.dispatchKeyEvent(event)) {
return true;
}
return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);
}
//ViewGroup.java
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
//传递给具体的View
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
return false;
}
//View.java
public boolean dispatchKeyEvent(KeyEvent event) {
...
// Give any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
return false;
}
//KeyEvent.java
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
mFlags &= ~FLAG_START_TRACKING;
boolean res = receiver.onKeyDown(mKeyCode, this);
...
return res;
}
case ACTION_UP:
...
return receiver.onKeyUp(mKeyCode, this);
case ACTION_MULTIPLE:
...
return false;
}
return false;
}
由上述代码过程,keyEvent由外到内传递,由Activity到具体的View。
ListenerInfo就是关联的我们自定义的监听,如setOnClickListener(),setOnLongClickListener。
这里的传递是:由Activity到ViewGrop再到View,如果某个环节返回true,即事件被处理掉不再向下层传递。如果最底层View仍未处理 而返回false,则再依次向外传递至Activity(向外传递中仍未被处理的话)处理。
注意:dispatchKeyEvent()都是有的。 onKeyDown,onKeyUp、onKeyLongPress等是View中有,同样为true即处理掉 不在传递了。
b) 触摸事件传递处理
触摸事件,类似按键事件,这里直接看 最终传递到的Activity的地方。
注意:dispatchTouchEvent(),onTouchEvent()都是有的。 ViewGroup中多了个onInterceptTouchEvent(),若为true, 则是将事件拦截,不在传递。
//============上面是上层应用,下面是surfaceFlinger相关=====
文章从如下三个层次进行讲解.其中每一层之间的数据传递是使用Buffer(缓冲区)作为载体, 上层和framework间的buffer为图形缓冲区,framework与显示屏间的buffer是硬件帧缓冲区。
一、图形渲染流程
1.1 app层绘制
由ViewRootImpl发起performTraversals开始View的绘制流程:
1)测量View的宽高(Measure)
2)设置View的宽高位置(Layout)
3)创建显示列表,并执行绘制(Draw)
4)绘制通过图形处理引擎来实现,生成多边形和纹理(Record、 Execute)。
其中引擎包括:
2D : Canvas,Canvas调用的API到下层其实封装了skia的实现。
3D: OpenGL ES , 当应用窗口flag等于WindowManager.LayoutParams.MEMORY_TYPE_GPU , 则表示需要用OpenGL接口来绘制UI.
1.2 Surface
每个Window对应一个Surface,任何View都要画在Surface的Canvas上。图形的传递是通过Buffer作为载体,Surface是对Buffer的进一步封装,也就是说Surface内部具有多个Buffer供上层使用,如何管理这些Buffer呢?请看下面这个模型:
Surface对应生产者代理对象,Surface(Native)对应生产者本地对象。那么流程就是:
上层app通过Surface获取buffer,供上层绘制,绘制过程通过Canvas来完成,底层实现是skia引擎,绘制完成后数据通过Surface被queue进BufferQueue.
然后监听会通知SurfaceFlinger去消费buffer.
接着SurfaceFlinger就acquire数据拿去合成, 合成完成后会将buffer release回BufferQueue。
如此循环,形成一个Buffer被循环使用的过程。
另外,这个过程有这么几个状态:
Free:可被上层使用;Dequeued:出列,正在被上层使用;Queued:入列,已完成上层绘制,等待SurfaceFlinger合成;Acquired:被获取,SurfaceFlinger正持有该Buffer进行合成
所以Surface主要干两件事:
1.3 SurfaceFlinger
SurfaceFlinger 是一个独立的Service, 它接收所有Surface作为输入,创建layer(其主要的组件是一个 BufferQueue)与Surface一一对应,根据ZOrder, 透明度,大小,位置等参数,计算出每个layer在最终合成图像中的位置,然后交由HWComposer或OpenGL生成最终的栅格化数据, 放到layer的framebuffer上。
1.4 Layer
Layer是SurfaceFlinger 进行合成的基本操作单元,其主要的组件是一个 BufferQueue。Layer在应用请求创建Surface的时候在SurfaceFlinger内部创建,因此一个Surface对应一个 Layer。Layer 其实是一个 FrameBuffer,每个 FrameBuffer 中有两个 GraphicBuffer 记作 FrontBuffer 和 BackBuffer.
1.5 Hardware Composer
它的主要目标是通过可用硬件确定组合缓冲区的最有效方式。
1)SurfaceFlinger 为 HWC 提供完整的 layers 的列表并询问,“你想要如何处理它?”。
2)HWComposer根据硬件性能决定是使用硬件图层合成器还是GPU合成,分别将每个layer对应标记为 overlay 或 GLES composition 来进行响应。
3)SurfaceFlinger处理需要GPU合成的layers,将结果递交给HWComposer做显示(通过Hwcomposer HAL),需要硬件图层合成器合成的layers由HWComposer自行处理(通过Hwcomposer HAL)。
4)合成Layer时,优先选用HWComposer,在HWComposer无法解决时,SurfaceFlinger采用默认的3D合成,也即调OpenGL标准接口,将各layer绘制到fb上。
分析:这样设计的好处是可以充分发挥硬件性能,同时降低SurfaceFlinger和硬件平台的耦合度(方便移植),另外SurfaceFlinger能将一些合成工作委托给Hardware Composer,从而降低来自OpenGL和GPUd的负载。
总结下两种合成方式:
opengl 或者硬件HWComposer合成.
经过上述分析,我们大概了解了整个显示数据的产生、传送、合成的过程以及相关类在这个过程中所起的作用,最后总结一张图形数据流:
1.6 Screen显示
显示屏上的内容,是从硬件帧缓冲区读取的,大致读取过程为:从Buffer的起始地址开始,从上往下,从左往右扫描整个Buffer,将内容映射到显示屏上。
下图显示的是双缓冲:一个FrontBuffer用于提供屏幕显示内容,一个BackBuffer用于后台合成下一帧图形。
假设前一帧显示完毕,后一帧准备好了,屏幕将会开始读取下一帧的内容,也就是开始读取上图中的后缓冲区的内容.此时,前后缓冲区进行一次角色互换,之前的后缓冲区变为前缓冲区,进行图形的显示,之前的前缓冲区则变为后缓冲区,进行图形的合成。
最后通过官方给出的图了解下关键组件如何协同工作:
总结下渲染Android应用视图的渲染流程:
测量流程用来确定视图大小,布局流程用来确定视图位置,绘制流程最终将视图绘制在应用窗口上.
Android应用程序窗口UI首先是使用Canvas通过Skia图形库API来绘制在一块画布上,实际地是通过Surface绘制在这块画布里面的一个图形缓冲区中.
这个图形缓冲区最终会通过layer的形式交给SurfaceFlinger来合成.
而合成后栅格化数据的操作交由HWComposer或OpenGL生成,即将这个图形缓冲区渲染到硬件帧缓冲区中,供屏幕显示.
接上节,我们已经知道系统层不断地合成显示内容到后缓冲区,屏幕消费前缓冲区的显示内容,两缓冲区再交换。那么两者的频率是怎样的呢?
我们先来了解两个概念:
屏幕刷新率(HZ):代表屏幕在一秒内刷新屏幕的次数,Android手机一般为60HZ(也就是1秒刷新60帧,大约16.67毫秒刷新1帧)。
系统帧速率(FPS):代表了系统在一秒内合成的帧数,该值的大小由系统算法和硬件决定。
屏幕刷新率决定了屏幕消费显示内容的速度,而系统帧速率则决定了生产显示内容的速度。这是一个典型的生产者消费者的问题。两个缓冲区的操作速率不一致,势必会出现同步问题,如何解决?
从Android4.1版本开始,Android对显示系统进行了重构,引入了三个核心元素:VSYNC, Tripple Buffer和Choreographer。来解决同步问题。下面分别来介绍:
这节主要是关于vysnc和choreography的介绍,内容和Android图形显示系统(一)_良秋的博客-CSDN博客_android 显示系统 一模一样
先来总结下app与SurfaceFlinger服务连接过程。
经过前面的activity 、window 、view 的分析我们大致了解了Activity的显示过程。其实Surface的创建过程与Activity的显示过程密不可分。
那么就从Activity.makeVisible 开始捋下流程:
1 Activity.makeVisible getWindowManager() 并执行addView。
2 经过WindowManagerImpl 和 WindowManagerGlobal addView , 最终创建了ViewRootImpl.
3 ViewRootImpl 内部会new Surface() ,它是一个Parcelable对象,可在进程间传递,但目前仅是一个空壳,还未被赋值。
同时,通过WindowManagerGlobal.getWindowSession()获取了Session实例,准备好了与WMS通信,并且Session内有个成员变量SurfaceSession值得关注,它的初始化是在Session的windowAddedLocked方法,先埋个伏笔。
4 根据流程我们知道,最终ViewRootImpl会在走setView, 在这个方法中开始了两个流程:
requestLayout() 开启了绘制流程。
mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
binder ipc 让WMS执行添加并显示window操作。
requestLayout执行会走消息,因此它虽然在addToDisplay前面,但是执行是在之后的,因此我们先来看看mWindowSession.addToDisplay,这个过程最终是在WMS执行addWindow方法:
addWindow(…){
...
//创建 WindowState,WindowState是系统层面对Window管理的一个封装类,与Window一一对应的
WindowState win = new WindowState(...);
…
//别的都不看了,重点关注attach方法
win.attach();
...
}
//WindowState.java
void attach() {
mSession.windowAddedLocked();//mSession 是 Session
}
下面的内容是SF进程与app进程使用binder进程通信的具体实现.不需要掌握
是不是感觉特别绕,下面来简单总结下:
首先ComPoserService作为client 与 SurfaceFlinger server进行binder IPC , 获取到SurfaceFlinger创建的Client对象,它相当于是SurfaceFlinger内部对应用程序客户端的封装对象.
而Client与SurfaceComposerClient又互为binder ipc的两端,SurfaceComposerClient为client端,Client为server端。
SurfaceComposerClient对象的两个成员变量分别跟着两个Binder服务通信:
也就是说只需要调用new SurfaceComposerClient()
便建立应用程序跟SurfaceFlinger服务建立连接, 获取了其中两个Binder的代理类。每一个app在SurfaceFlinger中都有一个Client对象相对应。
这样,应用进程成功通过SurfaceComposerClient与SurfaceFlinger建立了连接
接上篇,WindowManager addView流程来:
ViewRootImpl走setView 我们已经讲了mWindowSession.addToDisplay这条线,app与SurfaceFlinger服务建立了连接,下面我们接着看另外一条线: requestLayout()。
前面我们讲了,requestLayout()开启了绘制流程,具体流程如下图所示:
前三步不啰嗦了,不了解的可以去看View绘制篇,这里我们从WMS中relayoutWindow开始说起.
调用WindowState对象win的成员函数createSurfaceLocked来为它创建一个Surface。
先来看看win.createSurfaceLocked().
简单理解就是new 了一个java层Surface,然后初始化了应用程序窗口的各种属性:包括pid、标题、像素格式、宽、高、图形缓冲区属性标志等。
那么接着我们来看下Surface这个对象:
Surface类有三个成员变量mSurfaceControl、mCanvas和mName.
其中,mSurfaceControl保存的是在C++层的一个SurfaceControl对象的地址值,mCanvas用来描述一块类型为CompatibleCanvas的画布,mName用来描述当前正在创建的一个绘图表面的名称。这里我们主要关注成员变量mSurfaceControl所关联的C++层的SurfaceControl对象是如何创建的。
static void Surface_init(JNIEnv*env, jobject clazz, jobject session, jint pid, jstring jname, jint dpy, jint w, jint h, jint format, jintflags) {
//从 SurfaceSession 对象中取出之前创建的那个 SurfaceComposerClient 对象
SurfaceComposerClient* client = (SurfaceComposerClient*)env->GetIntField(session, sso.client);
spsurface;//注意它的类型是 SurfaceControl
//调用 SurfaceComposerClient 的 createSurface 函数,返回的 surface 是一个 SurfaceControl 类型,这个Surface是为WMS服务的。
surface = client->createSurface(pid, dpy, w, h, format, flags);
//把这个 surfaceControl 对象设置到 Java 层的 Surface 对象中,对应mSurfaceControl
setSurfaceControl(env, clazz, surface);
}
很明显看出来,SurfaceComposerClient在这里执行了createSurface方法,要创建Surface.
但是经过上篇我们知道,SurfaceComposerClient最终会通过binder IPC 到Client 最终会在SurfaceFlinger中来执行createSurface:
spSurfaceFlinger::createSurface(ClientID clientId, int pid, const String8& name, ISurfaceFlingerClient::surface_data_t* params, DisplayID d, uint32_t w, uint32_t h, PixelFormat format, uint32_t flags) {
sp layer;//LayerBaseClient 是 Layer 家族的基类
//这里又冒出一个 LayerBaseClient 的内部类,它也叫Surface
spSurface> surfaceHandle;
Mutex::Autolock _l(mStateLock);
//根据 clientId 找到 createConnection 时加入的那个 Client 对象
sp client = mClientsMap.valueFor(clientId);
......
//注意这个 id,它的值表示 Client 创建的是第几个显示层
//同时也表示将使用 SharedBufferStatck 数组的第 id 个元素
int32_t id = client->generateId(pid);
//一个 Client 不能创建多于 NUM_LAYERS_MAX 个的Layer
if(uint32_t(id) >= NUM_LAYERS_MAX) {
return surfaceHandle;
}
//根据 flags 参数来创建不同类型的显示层
switch(flags & eFXSurfaceMask) {
case eFXSurfaceNormal:
if (UNLIKELY(flags & ePushBuffers)) {
//创建 PushBuffer 类型的显示层
layer = createPushBuffersSurfaceLocked(client, d, id, w, h, flags);
} else {
//创建 Normal 类型的显示层
layer = createNormalSurfaceLocked(client, d, id, w, h, flags, format);
}
break;
case eFXSurfaceBlur:
//创建 Blur 类型的显示层
layer = createBlurSurfaceLocked(client, d, id, w, h, flags);
break;
case eFXSurfaceDim:
//创建 Dim 类型的显示层
layer = createDimSurfaceLocked(client, d, id, w, h, flags);
break;
}
if(layer != 0) {
layer->setName(name);
setTransactionFlags(eTransactionNeeded);
//从显示层对象中取出一个 ISurface 对象赋值给 SurfaceHandle
surfaceHandle = layer->getSurface();
if(surfaceHandle != 0) {
params->token = surfaceHandle->getToken();
params->identity = surfaceHandle->getIdentity();
params->width = w;
params->height = h;
params->format = format;
}
}
return surfaceHandle;//ISurface 的 Bn 端就是这个对象
}
在SurfaceFlinger::createSurface方法中,首先根据不同参数创建不同类型的显示层Layer,它是SurfaceFlinger中对Surface的描述,也是合成视图的基本单元.以createNormalSurfaceLocked为例:
spSurfaceFlinger::createNormalSurfaceLocked(const sp& client, DisplayID display, int32_t id, uint32_t w, uint32_t h, uint32_t flags, PixelFormat& format) {
//图像方面的参数设置
switch(format) {
case PIXEL_FORMAT_TRANSPARENT:
case PIXEL_FORMAT_TRANSLUCENT:
format = PIXEL_FORMAT_RGBA_8888;
break;
case PIXEL_FORMAT_OPAQUE:
format = PIXEL_FORMAT_RGB_565;
break;
}
//创建一个 Layer 类型的对象,进去看构造方法,有一些初始工作
sp layer = new Layer(this, display,client, id);
//设置 Buffer
status_t err = layer->setBuffers(w, h, format, flags);
if (LIKELY(err == NO_ERROR)) {
//初始化这个新 layer 的一些状态
layer->initStates(w, h, flags);
//下面这个函数把这个 layer 加入到 Z 轴集合中
addLayer_l(layer);
}
......
return layer;
}
Layer类的成员函数setBuffers就创建了一个SurfaceLayer(像素格式信息)对象,并且保存成员变量mSurface中.initStates初始化这个新 layer 的一些状态,addLayer_l把这个layer加入到 Z轴集合中.
然后返回去看看:surfaceHandle = layer->getSurface();
前面我们已经知道了Layer类的成员变量mSurface 就是一个SurfaceLayer对象。
这时候SurfaceFlinger服务就完成了Android应用程序所请求创建的Surface了,最后就会将用来描述这个Surface的一个SurfaceLayer对象的一个Binder代理对象返回Android应用程序,
以便Android应用程序可以将它封装成一个SurfaceControl对象。
Surface类的成员变量mClient指向了Android应用程序进程中的一个SurfaceClient单例。
Surface类的成员变量mSharedBufferClient指向了一个SharedBufferClient对象。
删掉的内容是new surface的实现过程,没必要掌握
总结:
每一个应用程序窗口都对应有两个Java层的Surface对象,其中一个是在WindowManagerService服务这一侧创建的,而另外一个是在应用程序进程这一侧创建的。在应用程序进程这一侧创建的ava层的Surface在C++层关联有一个Surface对象,用来绘制应用程序窗口的UI.
在WindowManagerService服务这一侧创建的Java层的Surface对象在C++层关联有一个SurfaceControl对象,用来设置应用窗口的属性,例如,大小和位置等。
为什么需要两个Surface,因为绘制应用程序窗口是独立的,由应用程序进程来完即可,而设置应用程序窗口的属性却需要全局考虑,即需要由WindowManagerService服务来统筹安排,两者必须结合考虑。
应用程序进程是UI绘制表面是通过两个Surface来描述,而在SurfaceFlinger服务内部使用的是一个Layer对象来描述。他们相互对应。
最后附上两张非常好的图,所谓一图胜千言可不是盖的:
Surface创建时序图:(仅仅用于参考,不需要掌握)
Surface创建过程://这个图没有上图那么多细节,而且模块比较清晰
Android应用程序与SurfaceFlinger服务是运行在不同的进程中的,因此,它们采用Binder进程间通信机制来进行通信。
但是我们知道一个Android应用程序可能会有很多个窗口,而每一个窗口都有自己的UI元数据,因此,Android应用程序需要传递给SurfaceFlinger服务的UI元数据是相当可观的。
在这种情况下,通过Binder来在Android应用程序与SurfaceFlinger服务之间传递UI元数据是不合适的,因此这里选择了Android系统的匿名共享内存的方案。
在每一个Android应用程序与SurfaceFlinger服务之间的连接上加上一块用来传递UI元数据的匿名共享内存。而这块区域被包装为SharedClient。
在每一个SharedClient里面,有至多31个SharedBufferStack,那什么又是SharedBufferStack?
SharedBufferStack就是共享缓冲区堆栈,每一个SharedBufferStack与一个Surface一一对应,每一个Surface又对应一个窗口,那就是一个应用程序内部最多可创建31个窗口。SharedBufferStack 内部包含N个缓冲buffer, 开篇介绍的双缓冲(front buffer , back buffer) ,三缓冲(front buffer , back buffer, tripple buffer),有了它SurfaceFlinger服务就可以使用N个缓冲区技术来绘制UI了。
下面我们再来了解下SharedBufferStack的结构:
SharedBufferStack中分为空闲buffer和已使用的buffer。其中SharedBufferStack中的每一个已经使用了的缓冲区都对应有一个GraphicBuffer,用来描述真正的UI数据。
客户端一次申请GraphicBuffer且将UI元数据写入GraphicBuffer的流程:
1) 当Android应用程序需要更新一个Surface的时候,它就会找到与它所对应的SharedBufferStack,并且从它的空闲缓冲区列表的尾部取出一个空闲的Buffer。我们假设这个取出来的空闲Buffer的编号为index。
2) 接下来Android应用程序就请求SurfaceFlinger服务为这个编号为index的Buffer分配一个图形缓冲区GraphicBuffer。SurfaceFlinger服务分配好图形缓冲区GraphicBuffer之后,会将它的编号设置为index,然后再将这个图形缓冲区GraphicBuffer返回给Android应用程序访问。
3) Android应用程序得到了SurfaceFlinger服务返回的图形缓冲区GraphicBuffer之后,就在里面写入UI数据。写完之后,就将与它所对应的缓冲区,即编号为index的Buffer,插入到对应的SharedBufferStack的已经使用了的缓冲区列表的头部去。
4) 这一步完成了之后,Android应用程序就通知SurfaceFlinger服务去绘制那些保存在已经使用了的缓冲区所描述的图形缓冲区GraphicBuffer了。
那么我们也知道一个绘图表面,在SurfaceFlinger服务和Android应用程序中分别对应Layer对象和Surface对象,其中这两个对象在内部分别使用一个SharedBufferServer对象和一个SharedBufferClient对象来操作这个绘图表面的UI元数据缓冲堆栈。
操作过程如下:
1) 在Android应用程序这一侧,当它需要渲染一个Surface时,它就会首先找到对应的SharedBufferClient对象,然后再调用它的成员函数dequeue来请求分配一个UI元数据缓冲区。
2) 有了这个UI元数据缓冲区之后,Android应用程序再调用这个SharedBufferClient对象的成员函数setDirtyRegion,setCrop和setTransform来设置对应的Surface的裁剪区域,纹理坐标以及旋转方向.
3) 此外,Android应用程序还会请求SurfaceFlinger服务为这个Surface分配一个图形缓冲区,以便可以往这个图形缓冲区写入实际的UI数据。
4) 最后,Android应用程序就可以调用这个SharedBufferClient对象的成员函数queue把前面已经准备好了的UI元数据缓冲区加入到它所描述的一个UI元数据缓冲区堆栈的待渲染队列中,以便SurfaceFlinger服务可以在合适的时候对它进行渲染。
5) 当SurfaceFlinger服务需要渲染一个Surface的时候,它就会找到对应的一个SharedBufferServer对象,然后调用它的成员函数getQueueCount来检查它所描述的一个UI元数据缓冲区堆栈的待渲染队列的大小。如果这个大小大于0,那么SurfaceFlinger服务就会继续调用它的成员函数retireAndLock来取出队列中的第一个UI元数据缓冲区,以及调用它的成员函数getDirtyRegion,getCrop和getTransform来获得要渲染的Surface的裁剪区域,纹理坐标和旋转方向.
6) 最后,SurfaceFlinger服务就可以结合这些信息来将保存这个Surface的图形缓冲区中的UI数据渲染在显示屏中。
另外想深入了解BufferQueue的生产者消费者模型,详细可以阅读下如下这篇博文,感觉还不错:https://blog.csdn.net/stn_lcd/article/details/73801313
我们已经分析了,mWindowSession.addToDisplay 通过WMS.addWindow 我们建立了app与SurfaceFlinger服务连接。并且通过requestLayout中的relayoutWindow, app请求SurfaceFlinger创建了Surface。
那么接下来,我们再分析下app的视图是如何被绘制到GraphicFrame上的。
这里面牵扯到的View,Canvas与Surface的关系,用这篇文章来梳理一下.(流程走的是软件绘制流程)
之前我们讲到requestLayout,开始了view的measure、layout、draw流程,我们从performDraw开始关注下最后的视图绘制工作:
这里我们看到,surface 在这里被接收了,并传入了drawSoftware。
//ViewRootImpl
private void performDraw() {
...
draw(fullRedrawNeeded);
...
}
//ViewRootImpl
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
...
drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty);
...
}
//ViewRootImpl
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
//1.获取Canvas
canvas = mSurface.lockCanvas(dirty);
...
//2.通过Canvas绘制视图
mView.draw(canvas);
...
//3.绘制结束
surface.unlockCanvasAndPost(canvas);
}
在drawSoftware方法中,我们重点关注如上三个方法:
一、 Surface的lockCanvas函数
这里主要关注几个点:
1.1 surface->lock(&outBuffer, dirtyRectPtr)
调用了Surface的lock函数实际上主要是调用了Surface的dequeueBuffer,而这个函数的主要目的是从SurfaceFlinger中申请GraphicBuffer, 这个buffer是用来传递绘制的元数据的。
1.2 GraphicsJNI::getNativeCanvas(env, canvasObj)
构造一个native的Canvas对象(SKCanvas),再返回这个Canvas对象,java层的Canvas对象其实只是对SKCanvas对象的一个简单包装,所有绘制方法都是转交给SKCanvas来做。
1.3 SkBitmap bitmap
Canvas底层是通过2D图形引擎skia进行图形绘制的,SkBitmap是skia中很重要的一个类,很多画图动作涉及到SkBitmap,它封装了与位图相关的一系列操作。那么在这里,bitmap对下设置了获取的内存buffer,对上关联了Canvas ,即把这个bitmap放入了Canvas中( nativeCanvas->setBitmap(bitmap) )
总结:Surface的lockCanvas函数会通过jni调用对应的native方法,本质是通过Surface的dequeueBuffer获取一块用于存放绘制元数据的GraphicBuffer,然后构造一个SKBitmap,它是绘制的核心, 对下关联buffer,对上关联canvas。
二、mView.draw(canvas)
这其实就是通过Canvas去实现具体的绘制。
以TextView的一部分绘制代码为例:
protected void onDraw(Canvas canvas) {
...
canvas.save();//坐标系的原点,坐标轴方向的信息。
canvas.translate(scrollX + mPaddingLeft + leftOffset,
scrollY + compoundPaddingTop +
(vspace - dr.mDrawableHeightLeft) / 2);
dr.mShowing[Drawables.LEFT].draw(canvas);
canvas.restore();//恢复Canvas之前保存的状态
...
}
实例:
//使用适配器将ViewPager与Fragment绑定在一起
mViewPager= (ViewPager) findViewById(R.id.viewPager);
myFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(myFragmentPagerAdapter);
//将TabLayout与ViewPager绑定在一起
mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
mTabLayout.setupWithViewPager(mViewPager);
三、surface.unlockCanvasAndPost(canvas);
我们看到了queueBuffer函数, 而在Surface的queueBuffer函数中调用了如下函数:
mGraphicBufferProducer->queueBuffer
这个函数最终会将BufferItem的buffer清除,通知消费者的onFrameAvailable接口。然后消费者可以根据mSlots的序号再来拿buffer。
//frameworks/native/libs/gui/BufferQueueProducer.cpp
status_t BufferQueueProducer::queueBuffer(int slot,
const QueueBufferInput &input, QueueBufferOutput *output) {
...
item.mGraphicBuffer.clear();
item.mSlot = BufferItem::INVALID_BUFFER_SLOT;
// Call back without the main BufferQueue lock held, but with the callback
// lock held so we can ensure that callbacks occur in order
{
Mutex::Autolock lock(mCallbackMutex);
while (callbackTicket != mCurrentCallbackTicket) {
mCallbackCondition.wait(mCallbackMutex);
}
if (frameAvailableListener != NULL) {
frameAvailableListener->onFrameAvailable(item);
} else if (frameReplacedListener != NULL) {
frameReplacedListener->onFrameReplaced(item);
}
++mCurrentCallbackTicket;
mCallbackCondition.broadcast();
}
...
}
所以整个过程看起来还是比较简单的。最后把整个流程再简单总结下,View、Canvas与Surface的关系也就一目了然了:
Surface通过dequeueBuffer流程(具体操作在此不多赘述)获取一块存放绘制数据的buffer。
View 在onDraw的时候,通过传入的Canvas进行绘制。(这里只是一个绘制的入口而已,本文是针对requestLayout流程来讲述的,当然你单独用Canvas实现绘制也是一样的)。
调用java层的CanvasAPI,实际真正负责绘制工作的是底层的Skia引擎,这里核心类包括SKCanvas(画家)以及SKBitmap(画布),绘制好的内容放入Surface 通过dequeueBuffer获取到的GraphicBuffer。(也可以是opengl画图)
绘制完毕后,Surface通过queueBuffer将存放好绘制数据的buffer投递到队列中,并通知SurfaceFlinger消费。
这个系列最后一篇文章,简单总结下SurfaceFlinger的启动流程及合成视图过程。
一、SurfaceFlinger启动流程
SurfaceFlinger 进程是由 init 进程创建的,运行在独立进程中。
//init.rc
service surfaceflinger /system/bin/surfaceflinger
class core
user system
group graphics drmrpc
onrestart restart zygote
writepid /dev/cpuset/system-background/tasks
SurfaceFlinger 的创建会执行 main() 方法:
//main_surfaceflinger.cpp
int main(int, char**) {
ProcessState::self()->setThreadPoolMaxThreadCount(4);
sp ps(ProcessState::self());
ps->startThreadPool();
//实例化 surfaceflinger
spflinger = new SurfaceFlinger();
//初始化
flinger->init();
//发布 surface flinger,注册到 ServiceManager
sp sm(defaultServiceManager());
sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false);
// 运行在当前线程
flinger->run();
return 0;
}
事实上到这里,SurfaceFlinger进程就已经启动了.之后我们再来了解下SurfaceFlinger的一些初始化操作:
然后会执行到 SurfaceFlinger::init():
//SurfaceFlinger.cpp
void SurfaceFlinger::init() {
Mutex::Autolock _l(mStateLock);
//初始化 EGL,作为默认的显示
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(mEGLDisplay, NULL, NULL);
// 初始化硬件 composer 对象
mHwc = new HWComposer(this, *static_cast(this));
//获取 RenderEngine 引擎
mRenderEngine = RenderEngine::create(mEGLDisplay, mHwc->getVisualID());
//检索创建的 EGL 上下文
mEGLContext = mRenderEngine->getEGLContext();
//初始化非虚拟显示屏
for (size_t i=0 ; iisConnected(i) || type==DisplayDevice::DISPLAY_PRIMARY) {
bool isSecure = true;
createBuiltinDisplayLocked(type);
wp token = mBuiltinDisplays[i];
sp producer;
sp consumer;
//创建 BufferQueue 的生产者和消费者
BufferQueue::createBufferQueue(&producer, &consumer,
new GraphicBufferAlloc());
sp fbs = new FramebufferSurface(*mHwc, i, consumer);
int32_t hwcId = allocateHwcDisplayId(type);
//创建显示设备
sp hw = new DisplayDevice(this,
type, hwcId, mHwc->getFormat(hwcId), isSecure, token,
fbs, producer,
mRenderEngine->getEGLConfig());
if (i > DisplayDevice::DISPLAY_PRIMARY) {
hw->setPowerMode(HWC_POWER_MODE_NORMAL);
}
mDisplays.add(token, hw);
}
}
getDefaultDisplayDevice()->makeCurrent(mEGLDisplay, mEGLContext);
//当应用和 sf 的 vsync 偏移量一致时,则只创建一个 EventThread 线程
if (vsyncPhaseOffsetNs != sfVsyncPhaseOffsetNs) {
sp vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
vsyncPhaseOffsetNs, true, "app");
mEventThread = new EventThread(vsyncSrc);
sp sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
sfVsyncPhaseOffsetNs, true, "sf");
mSFEventThread = new EventThread(sfVsyncSrc);
mEventQueue.setEventThread(mSFEventThread);
} else {
//创建 DispSyncSource 对象
sp vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
vsyncPhaseOffsetNs, true, "sf-app");
//创建线程 EventThread
mEventThread = new EventThread(vsyncSrc);
//设置 EventThread
mEventQueue.setEventThread(mEventThread);
}
//创建 EventControl
mEventControlThread = new EventControlThread(this);
mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY);
//当不存在 HWComposer 时,则设置软件 vsync
if (mHwc->initCheck() != NO_ERROR) {
mPrimaryDispSync.setPeriod(16666667);
}
//初始化绘图状态
mDrawingState = mCurrentState;
//初始化显示设备
initializeDisplays();
//启动开机动画
startBootAnim();
}
该方法主要是执行一些初始化工作,包括:EGL标准下OpenGL环境的创建、创建 HWComposer、初始化非虚拟显示屏、启动 EventThread 线程、启动开机动画等等。
notes:当画UI采用Skia画图,则合成采用opengl。当画图采用opengl,则合成采用硬件.
在这里,有几个比较重要的对象需要介绍下:
EGL:OpenGL是一个操作GPU的API,它通过驱动向GPU发送相关指令,控制图形渲染管线状态机的运行状态。但OpenGL需要本地视窗系统进行交互,这就需要一个中间控制层,最好与平台无关。EGL正是这个中间控制层,它作为OpenGL ES和本地窗口的桥梁,主要作用是:其主要作用是为OpenGL指令创建 Context 、绘制目标Surface 、配置Framebuffer属性、Swap提交绘制结果等。
HWComposer: 硬件组合抽象层,介于SurfaceFlinger和HAL之间,具体到代码级别就是一个类,封装对于Hwcomposer HAL和Gralloc HAL的使用。 主要作用是:一方面处理部分SurfaceFlinger委托过来的合成工作,另一方面就是产生vsync信号
EventThread: 它是Surfaceflinger中的一个线程 ,主要作用:接收VSync事件通知,并分发VSync通知给系统中的每一个感兴趣的注册者。
VSync控制cpu,gpu同步。在GPU处理完成之后将同步信号发给上层,又实现上层view和下层同步.
SurfaceFlinger->FramebufferNativeWindow->设备符fbDev-->framebuffer(fb是本台无关的)->hw_module_t->gralloc library(平台相关)
二、SurfaceFlinger图层合成过程
2.1 什么是图层合成
图层合成就是把多个图层按既定的显示区域,展现到显示屏上。
例如Android手机的Launcher主界面图层合成如下:
adb shell dumpsys SurfaceFlinger
com.android.systemui.ImageWallpaper
com.miui.home/com.miui.home.launcher.Launcher
StatusBar
2.2 SurfaceFlinger合成消息
SurfaceFlinger合成图层依赖于Android的异步消息处理机制。
首先,它包含了一个MessageQueue对象(消息队列)用于处理各种异步消息,在onFirstRef()中对消息队列进行了初始化:
//SurfaceFlinger.cpp
SurfaceFlinger::onFirstRef()
{
mEventQueue.init(this);
}
分析一下MessageQueue的实现:
//MessageQueue.cpp
void MessageQueue::init(const sp& flinger)
{
mFlinger = flinger;//保存MessageQueue的拥有者SurfaceFlinger
mLooper = new Looper(true);//创建Looper对象
mHandler = new Handler(*this);//创建Handler对象
}
调用Handler::handleMessage()处理INVALIDATE和REFRESH消息,并将其转发给SurfaceFlinger进行处理,调用onMessageReceived():
void MessageQueue::Handler::handleMessage(const Message& message) {
switch (message.what) {
case INVALIDATE:
android_atomic_and(~eventMaskInvalidate, &mEventMask);
mQueue.mFlinger->onMessageReceived(message.what);
break;
case REFRESH:
android_atomic_and(~eventMaskRefresh, &mEventMask);
mQueue.mFlinger->onMessageReceived(message.what);
break;
}
}
接下来看一下SurfaceFlinger对消息的处理:
void SurfaceFlinger::onMessageReceived(int32_t what) {
ATRACE_CALL();
switch (what) {
case MessageQueue::INVALIDATE: {
bool refreshNeeded = handleMessageTransaction();
refreshNeeded |= handleMessageInvalidate();
refreshNeeded |= mRepaintEverything;
if (refreshNeeded) {
// Signal a refresh if a transaction modified the window state,
// a new buffer was latched, or if HWC has requested a full
// repaint
signalRefresh();
}
break;
}
case MessageQueue::REFRESH: {
handleMessageRefresh();
break;
}} }
SurfaceFlinger处理的消息分为两种:
INVALIDATE消息:用于处理Layer或者display属性的变化以及Layer对应buffer的更新。
1) Layer或者Display属性的更新通过调用handleMessageTransaction()处理;
2) buffer的更新通过调用handleMessageInvalidate()处理。
REFRESH消息:表示SurfaceFlinger需要进行一次合成操作(Refresh),通过handleMessageRefresh()实现;主要有三种情况:
1) Layer属性的变化导致window state发生变化;
2) Layer有新的buffer到来;
3) HWC请求进行一次repaint。
如果这三种情况之一发生,则置refreshNeeded为true,调用signalRefresh()发出MessageQueue::REFRESH消息;
当VSync信号来之前,Layer或者display属性的变化会做本地保存,只有当VSync信号到来时,SurfaceFlinger才会通过INVALIDATE和REFRESH消息来做统一的合并渲染和输出的处理工作。
2.3 handleMessageTransaction()
处理之前对屏幕和应用程序窗口的改动。因这些改动很有可能会改变图层的可见区域,进而影响脏区域的计算。
主要处理以下几个方面的transaction:
1)Layer属性的变化;
2)Layer的增加和减少;
3)Display属性的变化;
4)Display的增加和减少;
2.4 handleMessageInvalidate()
主要调用handlePageFlip()函数。这里Page Flip是指从BufferQueue中取下一个图形缓冲区内容,就好像是“翻页”一样。该函数主要是从各Layer对应的BufferQueue中拿图形缓冲区数据,并根据内容更新脏区域(注:脏区域是需要重绘的屏幕区域.).并且把GraphicBuffer映射为OpenGL的texture 。
2.5 handleMessageRefresh()
合并和渲染输出。
void SurfaceFlinger::handleMessageRefresh() {
...
preComposition(); //合成前的准备
rebuildLayerStacks();//重建layer堆栈
setUpHWComposer();//hwcomposer的设定
doComposition();//正式的合成处理
postComposition(refreshStartTime);//合成后期的处理
...
}
2.5.1 void SurfaceFlinger::preComposition()
合成前准备工作。首先得到当前所有layer的集合,然后对所有的Layer调用其onPreComposition()检查是否需要ExtralInvalidate,如果需要就调用一次signalLayerUpdate(),即通过EventThread安排一次vsync。
2.5.2 void SurfaceFlinger::rebuildLayerStacks()
计算可见layer及它们的可见区域。首先每个layer都有一个layerStack来区别它属于哪个Display,系统的Display可能不止一个,所以需要逐个处理Display,根据所有layers的当前状态通过SurfaceFlinger::computeVisibleRegions方法计算各个Layer在当前display上的可见区域和脏区域等。最后把需要绘制的layer添加到layersSortedByZ中。
notes:当前的理解是,根据z轴windows分层覆盖。window分成可见区域,脏局域,遮挡局域,不可见区域等。先计算上层,然后下层根据上层区域计算下层的可见区域.
2.5.3 void SurfaceFlinger::setUpHWComposer()
为合成搭建环境。这个HWComposer并不是真正的Hal模块,而是surfaceflinger为管理HWComposer模块而设计的一个类,路径是:frameworks/native/service/surfaceflinger/displayhardware/。依次处理各个Display,构造WorkList,合成过程既可以有Hwc模块完成,也可以通过OpengGlEs来完成,具体用哪种方式是有prepare()中的compositionType来决定的。
2.5.4 void SurfaceFlinger::doComposition()
执行合成操作。执行openGl合成 or HWC合成。
2.5.5 void SurfaceFlinger::postComposition(refreshStartTime)
将图像传递到物理屏幕。
最后借用一张流程图做最后的总结:
这节的作用Choreographer 源码分析,可以不看。大致是一个handler机制+一个callback
在Android4.1之后增加了Choreographer机制,用于同Vsync机制配合,统一动画、输入和绘制时机.
本文以绘制为例来简单学习下Choreographer。
一、从绘制流程开始
ViewRootImpl的requestLayout开启绘制流程:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();//检查是否在主线程
mLayoutRequested = true;//mLayoutRequested 是否measure和layout布局。
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {//同一帧内不会多次调用遍历
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//拦截同步Message
//Choreographer回调,执行绘制操作
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
这里主要关注两点:
postSyncBarrier
: Handler 的同步屏障。它的作用是可以拦截 Looper 对同步消息的获取和分发,加入同步屏障之后,Looper 只会获取和处理异步消息. 如果没有异步消息那么就会进入阻塞状态。也就是说,对View绘制渲染的处理操作可以优先处理(设置为异步消息)。
Choreographer
: 编舞者。统一动画、输入和绘制时机。也是这章需要重点分析的内容。
二、Choreographer启动
frameworks\base\core\java\android\view\Choreographer.java
每一个Looper线程都有自己的Choreographer,其他线程发送的回调只能运行在对应Choreographer所属的Looper线程上
三、Choreographer执行流程
mDisplayEventReceiver 对应的是FrameDisplayEventReceiver,它继承自 DisplayEventReceiver ,主要是用来接收同步脉冲信号 VSYNC.scheduleVsync()方法通过底层nativeScheduleVsync()向SurfaceFlinger服务注册,即在下一次脉冲接收后会调用DisplayEventReceiver的dispatchVsync()方法.这里类似于订阅者模式,但是每次调用nativeScheduleVsync()方法都有且只有一次dispatchVsync()方法回调.
然后再看看接收VSYNC信号:
底层向应用层发送VSYNC信号,java层通过dispatchVsync()接收,最后回调在FrameDisplayEventReceiver的onVsync.
run方法被执行,所以doTraversal()被执行,开启View的绘制流程。
所以整个绘制过程总的流程如下所示:
简单总结:
一、Android流畅度概念
显示器刷新率(HZ):显示器物理刷新速率。
显卡合成帧速率 (FPS ):显卡一秒内能合成的帧数。
显示器依赖显卡提供的输入(显卡合成帧,显示器消费帧),典型的生产者消费者问题。那么自然会考虑到同步问题:
那么就引出了两个问题:
由于人类眼睛的特殊生理结构,如果所看画面的帧率是高于24的话,则会认为是连贯的,这个现象称之为视觉暂留。一般来说,30fps是可以接受的。但是如果提升到60fps的话,可以明显提升交互感和逼真感,超过75fps则不容易察觉到有明显的流畅度提升了。另外,在分辨率固定的情况下,刷新率越高,对显卡的数据处理能力要求也越高,例如:当画面的分辨率是1024×768时,画面的刷新率要达到80帧/秒,那么显卡在一秒钟内需要处理的像素量就达到了“1024×768×80”。为了将资源利用达到最大化,Android系统将输出的帧率固定在了60fps。那么要求最少16.67ms要绘制出一帧并显示出来,这样用户将会感觉非常流畅。
Android系统引入了垂直同步信号(VSync)来解决此问题。每16.6ms发送一次垂直同步信号,通知CPU与GPU在这个时间段内处理UI任务,只能保证整个流程步调一致而已。
讲到垂直同步,那么需要先简单交代下图形系统整个生产消费模型,简单示意如下:
在图形系统中,主要有两对生产者消费者模型:
应用层作为生产者,通过软件绘制(CPU绘制)/硬件绘制(GPU绘制)途径,分别由Skia/OpenGl绘图引擎绘制出UI图形数据,由GraphicBuffer作为数据载体,交由消费者SurfaceFlinger去把多层视图计算、裁剪、组合为最终视图,然后SurfaceFlinger作为生产者把最终的UI视图交由HWComposer或OpenGL合成最终的像素点数据,由FrameBuffer作为数据载体,供消费者显示器使用。
那么具体Vsync怎么同步呢?
在第一次同步信号发出过程中,SurfaceFlinger轮空,因为还没有可消费的帧素材,第一轮主要由Choreographer通知注册监听的动画、输入和绘制执行响应操作,准备好第一帧,这一帧会在一下轮Vsync信号发出时被SurfaceFlinger消费,因此应用层的UI绘制是领先于SurfaceFlinger一轮的。
3 流畅度问题探讨
Vsync解决了同步问题,那么要保证流畅度,必须在16.6ms内完成UI任务。那么在哪些情况下无法保证绘制在16.6ms内完成呢?
1)绘制本身任务太重,导致耗时。
2)Handler更新UI,但是之前的消息任务耗时,导致当前UI绘制被delay。
3)当前时间内没有得到CPU调度。
总结: