Android图形系统篇总结摘要

 Android图形系统篇总结摘要_第1张图片


Android图形系统学习框架:Android图形系统

简单总结下Activity启动后布局显示过程

  1. SurfaceFlinger 是在init.rc解析的时候被创建的,执行其main方法,实例化了Surfaceflinger,并向ServiceManager注册,SurfaceFlinger运行在单独进程中。

  2. 在Activity创建过程中执行scheduleLaunchActivity之后,便调用到了handleLaunchActivity方法.
    首先通过Instrumentation创建Activity.然后执行Activity的attach()方法,创建 PhoneWindow,且与activity建立回调关联。获取WindowManager,层层代理最终干活的是WindowManagerGlobal
    notes: scheduleLaunchActivity--handleLaunchActivity--Instrumentation--Activity--attach--PhoneWindow--WindowManager--WindowManagerGlobal

  3. setContentView过程,创建DecorView,并把xml的View树解析出来,加到DecorView上的contentParent部分。

  4. Activity调用makeVisible ,实际上是WindowManagerGlobal执行addView操作,然后调用ViewRootImpl setView操作。

  5. ViewRootImpl setView 做了两件事:
    1) requestLayout触发绘制流程
    2)mWindowSession.addToDisplay 通过IPC 执行WMS.addWindow

  6. requestLayout :中的relayoutWindow过程中app请求SurfaceFlinger创建Surface

  7. mWindowSession.addToDisplay:最终执行WMS.addWindow方法,该方法流程最终建立了app与SurfaceFlinger服务连接。(window=surface从SF获取)

  8. Android应用程序与SurfaceFlinger服务是运行在不同的进程中的,用Binder进行通信,用匿名共享内存进行UI数据传递。

  9. requestLayout draw的流程中:Surface通过dequeueBuffer获取一块GraphicBuffer, 然后onDraw中通过传入的Java层Canvas 调用底层Skia引擎中的SKCanvas(画家)、SKBitmap(画布)进行具体绘制操作.
    绘制完成之后把图形数据放入GraphicBuffer.
    最后Surface执行queueBuffer,把这块带有图形数据的buffer送回BufferQueue,并通过onFrameAvailable通知Layer更新。(绘图使用2d不是3d opengl,那么合成使用GPU glse)

  10. SurfaceFlinger合成图层依赖于Android的异步消息处理机制,每16ms接收一次vsync信号来执行图层合成操作,最终通过handleMessageRefresh一系列方法的处理,其中包括把GraphicBuffer数据映射为OpenGL的texture ,收集所有Layer计算显示区域,然后通过openG 或 HWC进行合成以及栅格化处理,最后送显。

Android图形系统(一)-Window加载视图过程

绘制优化:部分是原理机制篇.另外部分就是优化实践篇。
那么本篇文章就开启原理机制部分的总结,打算先捋下window的加载视图过程。

一、了解整体视图关系

Android图形系统篇总结摘要_第2张图片

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的启动流程(借用辉辉的图):
Android图形系统篇总结摘要_第3张图片

四、window添加view过程

我们前面知道PhoneWindow对View来说更多是扮演容器的角色,而真正完成把一个 View,作为窗口添加到 WMS 的过程是由 WindowManager 来完成的。而且从上面创建过程我们知道了WindowManager 的具体实现是 WindowManagerImpl。

下面的setViewContent部分有详细介绍

五、总结

下面用张图来总结下Activity、PhoneWindow、 DecorView 、WindowManagerGlobal 、ViewRootImpl 、Wms 以及WindowState之间的关系:

Android图形系统篇总结摘要_第4张图片

Activity在attach的时候,创建了个PhoneWindow对象,并且实现了Window的Callback接口,这样activity就和window绑定在了起,通过setContentView,创建DecorView,并解析好视图树加载到DecorView的contentView部分,WindowManagerGlobal个进程只有唯个,对当前进程内所有的视图进行统管理,其中包括ViewRootImpl,它主要做两件事情,先触发view绘制流程,再通过IPC 把view添加到window上。

另外这是添加视图的方法执行时序图:

Android图形系统篇总结摘要_第5张图片

 至于Window的删除和更新过程,举反三,也是使用WindowManagerGlobal对ViewRootImpl的操作,最终也是通过Session的IPC跨进程通信通知到WmS。整个过程的本质都是同出辙的。下节接着讲DecorView布局的加载流程。

Android图形系统(二)-DecorView布局加载流程

上篇我们了解了window的创建过程和添加视图的流程,但是顶级视图DecorView是怎么被加载的呢?其实这个过程非常简单,分析下setContentView的过程,一切就明了了。

一、关系介绍

先交代下PhoneWindow 与DecorView 以及mContentParent的关系:
mDecor是窗口顶层视图,mContentParent是mDecor上content framelayout的父容器,用来装xml解析出来的view树。

setContent流程

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视图组建完成。
稍微总结一下流程:

Android图形系统篇总结摘要_第6张图片

2.4、DecorView的添加

最终是WMS执行addWindow操作.
下面一张图总结下:

Android图形系统篇总结摘要_第7张图片

最最重要的是:root.setView(view, wparams, panelParentView); 方面触发绘制流程,方面把view添加到window上 .
讲setView之前先普及下WindowManager与WindowManagerService binder IPC的两个接口:IWindowSession: 应用程序向WMS请求功能.实现类:Session
IWindow:WMS向客户端反馈它想确认的信息.实现类:W

Android图形系统篇总结摘要_第8张图片

下面看看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开始的绘制流程。

 Android图形系统-View绘制流程

//这章把几个流程图看懂即可

接上篇 绘制优化-原理篇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开始

从requestLayout代码一层层往下追,最终确认view的绘制流程是从performTraversals开始。顺一下整个流程

Android图形系统篇总结摘要_第9张图片

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的组成:

Android图形系统篇总结摘要_第10张图片

这个表怎么用呢?举个例子:
如果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()的大致流程,可以用以下流程图来表示:

Android图形系统篇总结摘要_第11张图片

以上的流程图只是一个为了便于理解而简化版的流程,真正的流程应该分为以下五个工作阶段:

  • 预测量阶段:这是进入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

measure整体执行流程:
Android图形系统篇总结摘要_第12张图片

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的整体执行流程:

 Android图形系统篇总结摘要_第13张图片

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);
       ......
   }

从摘要可以看出,绘制过程分如下几步:

  • 绘制背景 background.draw(canvas)
   private void drawBackground(Canvas canvas) {
       //获取xml中通过android:background属性或者代码中setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景Drawable
       //根据layout过程确定的View位置来设置背景的绘制区域
       .....
           //调用Drawable的draw()方法来完成背景的绘制工作
           background.draw(canvas);
       ......
   }
  • 绘制children(dispatchDraw)
    View的dispatchDraw()方法是一个空方法,而且注释说明了如果View包含子类需要重写他。所以ViewGroup肯定重写了,来看看:
   @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);
}
  • 绘制装饰(onDrawScrollBars)

可以看见其实任何一个View都是有(水平垂直)滚动条的,只是一般情况下没让它显示而已。这部分不做详细分析了。

draw拓展点

默认情况下子View的ViewGroup.drawChild绘制顺序和子View被添加的顺序一致,但是你也可以重载ViewGroup.getChildDrawingOrder()方法提供不同顺序。

draw整体执行流程:

Android图形系统篇总结摘要_第14张图片

 、forceLayout 、invalidate 、requestLayout简述

在之前分析的绘制流程中,我们或多或少都见过这个方法,他们到底是干什么的,下面做下简单说明:

View#invalidate( ) 和 View#postInvalidate( )

invalidate和postInvalidate:都是用来重绘View,区别就是invalidate只能在主线程中调用,postInvalidate可以在子线程中调用.

View#requestLayout

requestLayout: 当前view及其以上的viewGroup部分都重新走ViewRootImpl 重新绘制 ,分别重新onMeasure onLayout onDraw ,其中onDraw比较特殊,有内容变化才会触发。

最后一张图总结下invalidate/postInvalidate 和 requestLayout

Android图形系统篇总结摘要_第15张图片

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(为了保证重绘)

 Android图形系统(四)-Activity、Window、View关系总结

本篇文章对之前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);
...
}}

两张图总结下:

Android图形系统篇总结摘要_第16张图片

Android图形系统篇总结摘要_第17张图片

 setContentView流程

总结:
Activity主要作用还是生命周期的管理,Window是一个视图容器,将Activity与View解耦,WindowManager统一管理View。
所以, Activity与window的关联主要还是体现在生命周期的管理,和key touch事件回调上。 Window与View的关联体现在对View视图的处理上。

加餐:view处理IMS来的消息

参考:Android10_原理机制系列_事件传递机制 - 流浪_归家 - 博客园
Android图形系统篇总结摘要_第18张图片

WMS中Window获取事件和传递

消息通过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,注意两个地方 后面会讲到的:

  • inputChannel封装到mInputConsumer
  • NativeInputEventReceiver是LooperCallback的派生类,实现了handleEvent()方法。

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()。这里关注两个:

  • 从InputChannel读取信息,并处理保存事件到inputEvent
  • 回调java层的 InputEventReceiver.dispatchInputEvent

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); 传递下去.

事件在View中的传递

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过程。

3) 事件在View中传递处理

由于按键事件 和 触摸事件是 最常见的,这里都简单列举了下。

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相关=====

Android图形系统-Surface图形系统概览

文章从如下三个层次进行讲解.其中每一层之间的数据传递是使用Buffer(缓冲区)作为载体, 上层和framework间的buffer为图形缓冲区,framework与显示屏间的buffer是硬件帧缓冲区。

Android图形系统篇总结摘要_第19张图片

一、图形渲染流程

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. 获取Canvas来干绘制的活。
  2. 申请buffer,并把Canvas最终生产的图形、纹理数据放进去。

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的负载。

总结下两种合成方式:

Android图形系统篇总结摘要_第20张图片

opengl 或者硬件HWComposer合成.

经过上述分析,我们大概了解了整个显示数据的产生、传送、合成的过程以及相关类在这个过程中所起的作用,最后总结一张图形数据流:

 1.6 Screen显示

显示屏上的内容,是从硬件帧缓冲区读取的,大致读取过程为:从Buffer的起始地址开始,从上往下,从左往右扫描整个Buffer,将内容映射到显示屏上。

下图显示的是双缓冲:一个FrontBuffer用于提供屏幕显示内容,一个BackBuffer用于后台合成下一帧图形。

假设前一帧显示完毕,后一帧准备好了,屏幕将会开始读取下一帧的内容,也就是开始读取上图中的后缓冲区的内容.此时,前后缓冲区进行一次角色互换,之前的后缓冲区变为前缓冲区,进行图形的显示,之前的前缓冲区则变为后缓冲区,进行图形的合成。

最后通过官方给出的图了解下关键组件如何协同工作:

 总结下渲染Android应用视图的渲染流程:

测量流程用来确定视图大小,布局流程用来确定视图位置,绘制流程最终将视图绘制在应用窗口上.

Android应用程序窗口UI首先是使用Canvas通过Skia图形库API来绘制在一块画布上,实际地是通过Surface绘制在这块画布里面的一个图形缓冲区中.
这个图形缓冲区最终会通过layer的形式交给SurfaceFlinger来合成.
而合成后栅格化数据的操作交由HWComposer或OpenGL生成,即将这个图形缓冲区渲染到硬件帧缓冲区中,供屏幕显示.

二、CPU/GPU的合成帧率与Display的刷新率同步问题

接上节,我们已经知道系统层不断地合成显示内容到后缓冲区,屏幕消费前缓冲区的显示内容,两缓冲区再交换。那么两者的频率是怎样的呢?

我们先来了解两个概念:

屏幕刷新率(HZ):代表屏幕在一秒内刷新屏幕的次数,Android手机一般为60HZ(也就是1秒刷新60帧,大约16.67毫秒刷新1帧)。

系统帧速率(FPS):代表了系统在一秒内合成的帧数,该值的大小由系统算法和硬件决定。

屏幕刷新率决定了屏幕消费显示内容的速度,而系统帧速率则决定了生产显示内容的速度。这是一个典型的生产者消费者的问题。两个缓冲区的操作速率不一致,势必会出现同步问题,如何解决?

从Android4.1版本开始,Android对显示系统进行了重构,引入了三个核心元素:VSYNCTripple BufferChoreographer。来解决同步问题。下面分别来介绍:

这节主要是关于vysnc和choreography的介绍,内容和Android图形显示系统(一)_良秋的博客-CSDN博客_android 显示系统 一模一样

Android图形系统-app与SurfaceFlinger服务连接过程

先来总结下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服务通信:

  • 其成员变量mClient通过Binder调用Client服务,
  • 其成员变量mComposer经过Composer(位于SurfaceComposerClient.cpp文件),ComposerService对象,再通过Binder调用SurfaceFlinger。

也就是说只需要调用new SurfaceComposerClient()便建立应用程序跟SurfaceFlinger服务建立连接, 获取了其中两个Binder的代理类。每一个app在SurfaceFlinger中都有一个Client对象相对应。

Android图形系统篇总结摘要_第21张图片

这样,应用进程成功通过SurfaceComposerClient与SurfaceFlinger建立了连接 

Android图形系统(七)-app请求SurfaceFlinger创建Surface过程 

接上篇,WindowManager addView流程来:

ViewRootImpl走setView 我们已经讲了mWindowSession.addToDisplay这条线,app与SurfaceFlinger服务建立了连接,下面我们接着看另外一条线: requestLayout()。

前面我们讲了,requestLayout()开启了绘制流程,具体流程如下图所示:

Android图形系统篇总结摘要_第22张图片

 前三步不啰嗦了,不了解的可以去看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的实现过程,没必要掌握

总结:

  1. 每一个应用程序窗口都对应有两个Java层的Surface对象,其中一个是在WindowManagerService服务这一侧创建的,而另外一个是在应用程序进程这一侧创建的。在应用程序进程这一侧创建的ava层的Surface在C++层关联有一个Surface对象,用来绘制应用程序窗口的UI.
    在WindowManagerService服务这一侧创建的Java层的Surface对象在C++层关联有一个SurfaceControl对象,用来设置应用窗口的属性,例如,大小和位置等。
    为什么需要两个Surface,因为绘制应用程序窗口是独立的,由应用程序进程来完即可,而设置应用程序窗口的属性却需要全局考虑,即需要由WindowManagerService服务来统筹安排,两者必须结合考虑。

  2. 应用程序进程是UI绘制表面是通过两个Surface来描述,而在SurfaceFlinger服务内部使用的是一个Layer对象来描述。他们相互对应。

Android图形系统篇总结摘要_第23张图片

最后附上两张非常好的图,所谓一图胜千言可不是盖的:

Surface创建时序图:(仅仅用于参考,不需要掌握)

Android图形系统篇总结摘要_第24张图片

 Surface创建过程://这个图没有上图那么多细节,而且模块比较清晰

Android图形系统篇总结摘要_第25张图片

Android图形系统(八)-app与SurfaceFlinger共享UI元数据过程 

Android应用程序与SurfaceFlinger服务是运行在不同的进程中的,因此,它们采用Binder进程间通信机制来进行通信。

但是我们知道一个Android应用程序可能会有很多个窗口,而每一个窗口都有自己的UI元数据,因此,Android应用程序需要传递给SurfaceFlinger服务的UI元数据是相当可观的。
在这种情况下,通过Binder来在Android应用程序与SurfaceFlinger服务之间传递UI元数据是不合适的,因此这里选择了Android系统的匿名共享内存的方案。
在每一个Android应用程序与SurfaceFlinger服务之间的连接上加上一块用来传递UI元数据的匿名共享内存而这块区域被包装为SharedClient

Android图形系统篇总结摘要_第26张图片

在每一个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了。

Android图形系统篇总结摘要_第27张图片

下面我们再来了解下SharedBufferStack的结构:

Android图形系统篇总结摘要_第28张图片

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数据渲染在显示屏中。

Android图形系统篇总结摘要_第29张图片

另外想深入了解BufferQueue的生产者消费者模型,详细可以阅读下如下这篇博文,感觉还不错:https://blog.csdn.net/stn_lcd/article/details/73801313

Android图形系统篇总结摘要_第30张图片

//=下面是从view  draw surface到SF合成的数据流程=========

 Android图形系统-ViewCanvas与Surface的关系

我们已经分析了,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是用来传递绘制的元数据的。

Android图形系统篇总结摘要_第31张图片

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) )

Android图形系统篇总结摘要_第32张图片

总结: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的关系也就一目了然了:

  1. Surface通过dequeueBuffer流程(具体操作在此不多赘述)获取一块存放绘制数据的buffer。

  2. View 在onDraw的时候,通过传入的Canvas进行绘制。(这里只是一个绘制的入口而已,本文是针对requestLayout流程来讲述的,当然你单独用Canvas实现绘制也是一样的)。

  3. 调用java层的CanvasAPI,实际真正负责绘制工作的是底层的Skia引擎,这里核心类包括SKCanvas(画家)以及SKBitmap(画布),绘制好的内容放入Surface 通过dequeueBuffer获取到的GraphicBuffer。(也可以是opengl画图)

  4. 绘制完毕后,Surface通过queueBuffer将存放好绘制数据的buffer投递到队列中,并通知SurfaceFlinger消费。

Android图形系统(十)-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,则合成采用硬件.

在这里,有几个比较重要的对象需要介绍下:

  1. EGL:OpenGL是一个操作GPU的API,它通过驱动向GPU发送相关指令,控制图形渲染管线状态机的运行状态。但OpenGL需要本地视窗系统进行交互,这就需要一个中间控制层,最好与平台无关。EGL正是这个中间控制层,它作为OpenGL ES和本地窗口的桥梁,主要作用是:其主要作用是为OpenGL指令创建 Context 、绘制目标Surface 、配置Framebuffer属性、Swap提交绘制结果等。

  2. HWComposer: 硬件组合抽象层,介于SurfaceFlinger和HAL之间,具体到代码级别就是一个类,封装对于Hwcomposer HAL和Gralloc HAL的使用。 主要作用是:一方面处理部分SurfaceFlinger委托过来的合成工作,另一方面就是产生vsync信号

  3. EventThread: 它是Surfaceflinger中的一个线程 ,主要作用:接收VSync事件通知,并分发VSync通知给系统中的每一个感兴趣的注册者。
    VSync控制cpu,gpu同步。在GPU处理完成之后将同步信号发给上层,又实现上层view和下层同步.

SurfaceFlinger->FramebufferNativeWindow->设备符fbDev-->framebuffer(fb是本台无关的)->hw_module_t->gralloc library(平台相关)

二、SurfaceFlinger图层合成过程

2.1 什么是图层合成

图层合成就是把多个图层按既定的显示区域,展现到显示屏上。

例如Android手机的Launcher主界面图层合成如下:

Android图形系统篇总结摘要_第33张图片

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)

将图像传递到物理屏幕。

最后借用一张流程图做最后的总结:

Android图形系统篇总结摘要_第34张图片

Android图形系统十一-Choreographer 

这节的作用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线程上

Android图形系统篇总结摘要_第35张图片

三、Choreographer执行流程

Android图形系统篇总结摘要_第36张图片

mDisplayEventReceiver 对应的是FrameDisplayEventReceiver,它继承自 DisplayEventReceiver ,主要是用来接收同步脉冲信号 VSYNC.scheduleVsync()方法通过底层nativeScheduleVsync()向SurfaceFlinger服务注册,即在下一次脉冲接收后会调用DisplayEventReceiver的dispatchVsync()方法.这里类似于订阅者模式,但是每次调用nativeScheduleVsync()方法都有且只有一次dispatchVsync()方法回调.

然后再看看接收VSYNC信号:

 Android图形系统篇总结摘要_第37张图片

底层向应用层发送VSYNC信号,java层通过dispatchVsync()接收,最后回调在FrameDisplayEventReceiver的onVsync.
run方法被执行,所以doTraversal()被执行,开启View的绘制流程。

所以整个绘制过程总的流程如下所示:

Android图形系统篇总结摘要_第38张图片

简单总结:

  • Choreographer支持4种类型事件:输入、绘制、动画、提交,并通过postCallback在对应需要同步vsync进行刷新处进行注册,等待回调。
  • Choreographer监听底层Vsync信号,一旦接收到回调信号,则通过doFrame统一对java层4种类型事件进行回调。

Android图形系统十二-流畅度概念

一、Android流畅度概念

显示器刷新率(HZ):显示器物理刷新速率。
显卡合成帧速率 (FPS ):显卡一秒内能合成的帧数。

显示器依赖显卡提供的输入(显卡合成帧,显示器消费帧),典型的生产者消费者问题。那么自然会考虑到同步问题:

  • 如果显卡输入小于显示器的刷新率,将会有画面被复用,出现卡顿感。
  • 如果显卡输入大于显示器的刷新率,卡顿感是没有了,但是会有多出来的输入帧被浪费掉。

那么就引出了两个问题:

  • 每秒刷新多少帧画面,用户会感觉到流畅?
  • 如何保证显卡输出和屏幕刷新同步呢?
  1. 每秒刷新多少帧画面,用户会感觉到流畅?

由于人类眼睛的特殊生理结构,如果所看画面的帧率是高于24的话,则会认为是连贯的,这个现象称之为视觉暂留。一般来说,30fps是可以接受的。但是如果提升到60fps的话,可以明显提升交互感和逼真感,超过75fps则不容易察觉到有明显的流畅度提升了。另外,在分辨率固定的情况下,刷新率越高,对显卡的数据处理能力要求也越高,例如:当画面的分辨率是1024×768时,画面的刷新率要达到80帧/秒,那么显卡在一秒钟内需要处理的像素量就达到了“1024×768×80”。为了将资源利用达到最大化,Android系统将输出的帧率固定在了60fps。那么要求最少16.67ms要绘制出一帧并显示出来,这样用户将会感觉非常流畅。

  1. 如何保证显卡输出和屏幕刷新同步呢?

Android系统引入了垂直同步信号(VSync)来解决此问题。每16.6ms发送一次垂直同步信号,通知CPU与GPU在这个时间段内处理UI任务,只能保证整个流程步调一致而已。

讲到垂直同步,那么需要先简单交代下图形系统整个生产消费模型,简单示意如下:

在图形系统中,主要有两对生产者消费者模型:

应用层作为生产者,通过软件绘制(CPU绘制)/硬件绘制(GPU绘制)途径,分别由Skia/OpenGl绘图引擎绘制出UI图形数据,由GraphicBuffer作为数据载体,交由消费者SurfaceFlinger去把多层视图计算、裁剪、组合为最终视图,然后SurfaceFlinger作为生产者把最终的UI视图交由HWComposer或OpenGL合成最终的像素点数据,由FrameBuffer作为数据载体,供消费者显示器使用。

那么具体Vsync怎么同步呢?

  • Choreographer请求并接受Vsync,它负责同步应用层UI的绘制,包括动画、输入和绘制的时机都由它来统一调度触发时机。
  • SurfaceFlinger请求并接受Vsync,执行消费UI合成送显素材的操作,同时SurfaceFlinger把信号分发给

在第一次同步信号发出过程中,SurfaceFlinger轮空,因为还没有可消费的帧素材,第一轮主要由Choreographer通知注册监听的动画、输入和绘制执行响应操作,准备好第一帧,这一帧会在一下轮Vsync信号发出时被SurfaceFlinger消费,因此应用层的UI绘制是领先于SurfaceFlinger一轮的。

3 流畅度问题探讨

Vsync解决了同步问题,那么要保证流畅度,必须在16.6ms内完成UI任务。那么在哪些情况下无法保证绘制在16.6ms内完成呢?

1)绘制本身任务太重,导致耗时。
2)Handler更新UI,但是之前的消息任务耗时,导致当前UI绘制被delay。
3)当前时间内没有得到CPU调度。

总结:

Android图形系统篇总结摘要_第39张图片

Android图形系统篇总结摘要_第40张图片

你可能感兴趣的:(Android,android)