Android渲染原理art相关

android 沉淀 - 渲染原理

2019-07-31  本文已影响0人  前行的乌龟

ps:android 渲染啊我以为很简单呢,半天就能搞定,谁想居然有一大堆七八糟的名字,让我绕了很久…… 可是着实费了一番功夫,时间可没少花,这些东西其实基本和 PC 渲染原理差不多,做过 PC 客户端的同学学起 Android 来会轻松很多,所以我以前很好奇公司新来的搞 PC 客户端的同学为啥学起 android 来进度这么块呢,原来真是一法通万法通啊,原理都是通用的,只要理解了再学新的真是能省很大力气啊

本文我基本上都是讲讲原理,不怎么涉及代码层面,因为代码层面基本上都是 android 底层 C 的代码,我也讲不好,大家想看代码的话就去看我发的学习链接

大家看我文章看不懂的话,请看这篇入门文章,会好很多:


这些日子觉得自己的学习能力见涨,应该是俗称的开窍了,多一决定再次挑战这部分。期间我找了一堆资料,android 渲染涉及很多部分,包括:

说实话,啃下这些来真是不容易啊,我是看一点理解一点,然后又迷糊一点,再查资料搞懂区别,关联,这是费劲,就其原因是没有适当全面的入门资料,网上资料很多,但是很不好理解,知识点都是一块块割裂开的,把他们串起来太费劲了,但是这是所有显示设备显示的通用逻辑,我们在 android 这里学明白了,再学其他平台也是一样的

非常感谢几位同学的资料,非诚系统, 刚兴趣的推荐去看看:
夕月风 | ZHTo0 | Stan_Z | 老罗


纷乱的系统 API

系统的这些 API 才是系统运行机制的核心,熟悉他们的具体运行才能了解一个系统的大概。另外万变不离其宗,换个系统,这些很多都是换汤不换药,有助于我们熟悉新系统:


进一步理解系统 API

ServiceManage 比较特殊,一般不涉及到他,但是其在binder中办严重最重要的角色,学习ServiceManage最好是从binder入手,具体的请大家把上面binder那一节学习透彻

接下来我们从功能分层这个角度好好盘点一下这些系统 API,google 为什么要搞这么多角色出来。大家会发现 Google 在这里使用了大量的模板模式模板那是一层套一层,期中我们需要学明白2点:

这2点都是核心的知识,是可以作为我们的核心积累一直对我们提供帮助的,这些都是属于变化较慢但是非常基础重要的部分,大家学好在今后的学习中自然事半功倍,比如去学习 Flutter 就能 体会到了

我们来讲个故事

ps: 非常感谢重学安卓:Activity 的快乐你不懂这篇文章提供的思路

  1. 搬砖时代

android 一开始开发时就是块搬砖,就有个屏幕, 开发人员直接用 C++ 调HAL 硬件抽象层画界面

  1. Surface Flinger 出现

但是吧 C++ 也不是人人都会写的,广大开发者不会啊,怎么办,只能给开发这个提供工具啦,于是Surface Flinger 这个系统服务诞生啦,surface Flinger封装了那些直接操作硬件驱动的C++代码,这才让广大开发者开发 app 成为了可能

Surface Flinger的职责就是专门负责 UI 的渲染,人们想要在屏幕上渲染出什么内容,都可以通过Surface Flinger来间接地与屏幕打交道。这就好比你在电脑上排版好的文档,只需通过打印机驱动程序这个中介,就能帮助你将文档内容输出到纸上,至于内容本身究竟有些什么,这我不管,我只负责统一地、有序地将内容安排成输出设备能理解的方式,来实现输出

Surface Flinger的出现是什么思路,就是剥离具体功能,把一类功能专门抽象封装放在一起,方便调用,简化思路,

  1. Window 出现

得意于Surface Flinger,我们画界面可以不用管 C++ 代码了,但是一个 app 界面好多啊,不光有开发者自己 app 的界面,还有系统的界面,比如状态栏,底部按键导航栏都是系统的 UI,他们直接的显示,切换,动画好复杂的,而且每个界面里我都要写这些代码,不说写几遍的问题,但是写出来就好难啊,这就把广大开发者距之门外,除了个别技术大N 之外谁能开发的了 app 啊,这可不成啊,软件环境起不来是怎么占领市场,是要被别人干死的啊,所以啊 window 这个概念出现了,连带着产生了WindowManagerService,WindowManager,Window 这几个 API

其核心就是Window这个概念,我们可以把Window看作是一个空白的界面,界面具体什么样子有具体的页面实现,Window负责和Surface Flinger交互,我们在Window这里看到是模板,所以Window就是界面的抽象模板,这样就好理解多了

WindowManager也好理解,一个 app 范围内WindowManager的实现类是单例的,保存有app内所有的界面的根view,一个页面对应一个根view

WindowManagerService也不难理解,他是个系统进程,负责管理所有的WindowWindow也是份系统自己的还是用户app的,他们之间如何重叠显示,如何动画,都是WindowManagerService关心的,最典型的就是状态栏了,状态栏可是一个单独的系统Window,他和所有app页面的重叠显示都是靠WindowManagerService完成的,另外 android 的点击事件的传递也是依靠WindowManagerService实现的

  1. Activity 诞生

上文书我们有了Window了,终于可以轻松很多了,不用再一遍遍的写那些会绘制的底层代码了,但是随着 app 变得越来越复杂,页面数量也是大幅度增加啊, 这Window一多,相互之间的切换显示,生命周期同步,事件接受,view 视图树的绘制要一遍一遍的写真是麻烦啊,虽然我把相关功能抽象封装成功能累了,但是每次这次功能类代码还是要在写一遍,很麻烦的呀,更麻烦的是这部分逻辑代码真是频繁修改啊,每次改代码真是要死的心都有啦,要改的地方越来越多啊,天天不干别的事了就是改他们玩了。这样不行啊,我们像Window一样搞个模板出来啦,处理具体的 view 长啥样,其他的生命周期交互,和WindowManagerService通信,事件分发都封装到Activity这个模板里

  1. 总之

相由心生 境随心转,在系统 API 中,每个类必然有其扮演的角色,必然有需要其完成的任务,这些 API 角色很多,对于大家最不友好的一点就是命名问题,大家看名字看不懂啊,查字典看翻译也不知具体是干啥的,这就造成了学习上的困难,提高了系统复杂度。但是反过来大家再想想,在我们封装功能模块时,命名是不是要好好想想,符合中国人的语言环境,类要不要精简以下,做到以上那么我们的代码至少能好看不少,好理解不好,局座说过:好看的必然现金,这话放在编程领域也是通用的,好看的代码,能见名知意的,功能分离清晰的,模板抽象准确的其实就是大 N 级别的代码,要是我们再能把代码效率性能搞上去就是大神级别的代码,要是我们能有所创新,那我们自己就是业务的 NO1 了。不要妄自菲薄,随着积累加深和我们有意为之,其实达到大N级别是通过积累和努力能达到的,要是有点天赋在网上也是很可能的

这一切的基础都是积累,各种基础知识、套路、思路的积累,积累到了自然NB,所以大家多多写博客吧,写下来的才是自己的,才能告诉未来的自己我会什么


关于 window 和显示的问题再解释一波

做 android 的朋友都知道window就是干显示的活,至于如何显示的其实很门道的,很多人不清楚,但是本身也不复杂,了解清楚对于我们很有好处

这部分内容在学习 app 启动流程和 activity 启动流程时有详细涉及:

window是具体页面的模板抽象,WindowManagerService负责处理整个 android 系统中所有window的排版,Surface Flinger干的是滴啊勇硬件驱动显示的活

但是这里面其实还有一个surface的概念,surface可以看成一块显存,一个window窗口对应一块surface,这个surface的管理是Surface Flinger干的。每创建一个window窗口时,WindowManagerService都会给对应的window窗口找Surface Flinger申请一块surface显存出来,然后windowWindowManagerService共同持有和Surface FlingerIPC通信的ISurfacebinder对象,window里面的 view 通过 canvas 直接操作ISurface在这块显存中绘制图像,WindowManagerService则通过ISurface操作window之间位置变换和动画,surface之间也存在z轴这个概念,Surface Flinger16ms刷新通知来了之后把自己管理的所有surface按照z轴顺序生成最终的一帧纹理,然后交给 GPU 显示。surface的本质是矩阵,参考 bitmap 想想下。基本上 android 的显示核心就是这个流程,大家熟悉下

window的本质是什么呢?其实就是一块显示区域,在 Android 中就是绘制的画布:Surface当一块 Surface 显示在屏幕上时,就是用户所看到的窗口了。WindowManagerService 添加一个窗口的过程,其实就是 WindowManagerService 为其分配一块 Surface 的过程,一块块的 SurfaceWindowManagerService 的管理下有序的排列在屏幕上,Android 才得以呈现出多姿多彩的界面,根据对 Surface 的操作类型可以将 Android 的显示系统分为三个层次,如下图:


介绍几个概念

先对总体有个概念,那么接下来学习就会顺利很多,会减少你一些困扰,这是学习一个大系统的不二法门


概括下 android 渲染过程

DisplayList 大家有的没听说说,对于应用层来说,没有能力把所属 window 计算成用于显示的 bitmap 位图的,这是需要调用底层 2D 渲染引擎的(随着 android 版本变迁和硬件加速的加入,这个 2D 渲染引擎在不断变化),应用层的任务是把 2D 渲染任务根据 view 层级计算成任务队列交给渲染引擎。RenderNode 和 DisplayList 就组成了这个渲染任务队列:


Skia,OpenGL/ES 引擎

本节参考:Android 中的 skia 和 OpenGL ES

这里我们先看看 Skia,OpenGL/ES 这2个引擎,挺好理解的

Skia 和 OpenGL/ES 都是绘图引擎,在格栅化过程中执行任务,SurfaceFlinger 进程每个 window 对应的 surface 渲染完成后(实际是 layer 图层),操作 GPU 调用 OpenGL/ES API 压和 layer 图层生成最终的帧(bitmap)

1. Skia

Skia 是 Google 系的库,android,chrome 均使用的是 Skia 这个库来渲染 2D 图形,甚至 Flutter 都是用 Skia 在 IOS 平台上渲染图形的

Skia 是 C++ 层的代码我们平时接触不到,接触的都是 Skia 的 java 封装类,比如:canvas,bitmap,animation,shapes 这些都会追溯到 Skia 的 API。Skia 核心类是 canvas,bitmap 这2个类,那么很明显了 Skia 核心是提供位图操作和各种绘制 API 的功能库

Skia 在 android 中有2种使用方式:

2. OpenGL/ES

OpenGL/ES 是移动平台专用的 3D 渲染引擎,在开启硬件加速后也可以执行 2D 渲染任务。3D 渲染对性能要求较高,尤其是需要更多的内存,除了游戏之外一般很少在 android 平台上使用,在启动赢家加速后,有时会出现渲染出错,图片加载不出来的问题,其实就是因为 3D 渲染需要的内存较多。

android 平台专门提供了一个 view 进程 3D 绘制:GLSurfaceView,详细的大家自己找找,这里不涉及了


硬件加速

这个加速是指用 GPU 代替 CPU 执行渲染任务,加速渲染任务。那么 GPU 是代替 CPU 执行的哪块任务呢,上面我们说过了应用层会把 viewTree 计算成相应的 DisplayList 渲染任务队列,这个渲染队列是 CPU 在 UI 线程,使用 Skai 2D 渲染引擎执行的。硬件加速呢,是新开一个线程 RanderThread,在这里线程里使用 GPU 使用 OpenGL/ES 执行渲染队列任务

硬件加速的这个渲染引擎啊,随着版本的变化一直在进化:


硬件加速兼容性

4.X 开始 Android 默认开启硬件加速,但是硬件加速有部分操作是不支持的,尤其是自定义 view 这里有时我们会碰到一些:

还有些很摸不着头脑的问题,比如:

  1. 加载大图,硬件加速中 OpenGL 对于内存是有限制的,如果遇到了这个限制,Log 会提示这个问题:
  2. EidtText 重叠显示

硬件加速不支持的操作:

有些操作的效果在硬件加速之后会有变化:

所以使用硬件加速很苦恼,硬件加速带来的性能优势非常明显,系统都默认开始了,但是硬件加速在 android 平台上又有些兼容问题,所以我们必须对这块非常清楚才行

系统自带的 view 大家放心,是没有硬件加速问题的,但是自定义 view 时我们就要格外小心了,碰到问题我们可以对某个 vierw,或是某个 activity 关闭硬件加速,只要在适当的地方关闭硬件加速即可


硬件加速性能优势

恩,现在大家都知道了硬件加速有性能优势了,但是大家知道具体的性能优势在哪里吗?万一有面试官问呢?

硬件加速的性能优势主要来源自2点:

1. GPU 渲染效率比 CPU 高

这点是 GPU、CPU 自身架构决定的,CPU 计算单元少,逻辑控制单元多,为的是执行维度、类型复杂的各种任务,毕竟 CPU 是中枢嘛~。GPU 计算单元多,逻辑控制单元少,专注于图形绘制、渲染

大家看图就明白了:

ALU 是计算单元,剩下的不用说大家也能猜到,所以但是把绘图、渲染任务从 CPU 切到 GPU 都是性能上的巨大提升

2. 可以减少无关 view 的重绘

这要从 DisplayList 渲染任务队列说起了,我们看个例子:

比如:button 在一个 view 上面,此时我们调用 button.Invalidate 重绘

硬件加速对于 DisplayList 的优化不止减少重绘的 view 数量,甚至我们修改 view 的属性(非内容,比如文字就是内容)都不会重新绘制

还是举个例子:一个 LinearLayout 包含了一个 ListView 和 Button,listview 在 button的上面,此时我们修改 ListView 透明度,Listview.setAlpha(0.5f)

原始的 DisplayList 渲染任务队列是:

修改透明度之后,DisplayList 渲染任务队列是:

现在大家体会到了硬件加速带来的性能优势了吧,属性动画涉及的属性硬件加速都支持免重绘的,所以硬件加速对属性动画的优化非常大, 这也是 Goolge 推荐大家使用属性动画的原因

这些属性我还是列一下吧:

执行动画时可以强制开启硬件加速

        view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
        ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                view.setLayerType(View.LAYER_TYPE_NONE, null);
            }
        });
        animator.start();

硬件加速的控制

硬件加速在 Target API >= 14 时是默认开启的

可以在 4 个级别上控制硬件加速

    <application
        ...
        android:hardwareAccelerated="true"
       ...
    >
<activity
            android:name=".MainActivity"
            android:hardwareAccelerated="true">
     getWindow().setFlags(
                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
 view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

硬件加速设置的几个 Type:

检测硬件加速是否启动 API:

view.isHardwareAccelerated();
canvas.isHardwareAccelerated();

推荐使用Canvas.isHardwareAccelerated()来检测,因为View关联到一个硬件加速的window上,仍然可以使用非硬件加速的canvas绘制


android 双缓存,3缓存技术

android 可以用到双缓存(多缓存)的地方有4个,这里发散一下,但是主要的还是介绍 android 渲染机制

硬件显示设备在内核内存中有一个映射,一般叫硬件帧缓存区,我们主要把渲染好的位图放在帧缓存区里就能在屏幕显示出来,这个帧缓存区每隔 16ms 刷新一次

双缓存技术把硬件帧缓存区分成 2份:Frame Buffer、Back Buffer

实际上 LCD 对应的真正的硬件缓存去只有一个,就是 Frame Buffer,Back Buffer 时系统自身添加的没,为的是优化显示

2块硬件缓存去的交互逻辑很简单,android 系统先计算下一帧,把位图保存在 Back Buffer 里,然后在接到 VSYNC 同步信号后,把 Back Buffer 里的数据复制到 Frame Buffer 里去显示,然后再渲染下一帧

剩下用到双缓存的地方:

3缓存技术其实就是多了一个硬件帧缓存区:Triple Buffer

在 4.0 的时候,android 默认启动 3缓存技术。如果说 Frame Buffer储存当前帧,Back Buffer储存下一阵,那么Triple Buffer就是存储下下帧的,那么为啥要有Triple Buffer呢,听我说上一说

在计算下一帧时,有时候计算任务较大,在 16ms 内执行不完,那么在没有 Triple Buffer的时候,下一个 16ms 在继续计算Back Buffer帧之后会空闲。Triple Buffer的意思在于节约这些空闲时机,开始计算掉帧之后的下一帧,这样为了的是尽量保证下一帧不再掉帧

我们下面会学习 Choreographer 编舞者,Choreographer 会同步 LCD 硬件设备的 VSYNC 信号给 app 进程,也就是说 app 进程什么时候触发绘制任务是 Choreographer 说的算的,每 16ms Choreographer 才会触发绘制任务,那么 Triple Buffer 为啥就能不管 Choreographer 的调度而自省开始绘制呢,这不就乱了吗,要是这样的话 UI 线程会时时都在计算下下帧,那还有时间执行其他任务。所以 Triple Buffer 3缓存技术是有前置触发条件的,就是上一帧掉帧了,才会触发 Triple Buffer去计算下下帧,以便之后不再掉帧了。所以说在 Systrace 工具中,有时候我们看到的黄色F 的那一帧,在有 Triple Buffer 3缓存技术时不一定就表示这一帧就掉帧了,需要具体看执行的时间点

图解说明以下:

  1. 正常不掉帧绘制
  2. 掉帧之后下一个 16ms 大部分时间浪费掉了
  3. 有了 Triple Buffer 之后,前一帧要是掉帧了,那么会有优化,充分利用空闲时间绘制下下帧

我先说一下 surfaceflinger

surfaceflinger 简称 SF,是 android 图形渲染的核心,surfaceflinger 是个系统服务,运行在独立进程里。surfaceflinger 的作用是接受多个来源的图形显示数据,将他们合成,然后发送到显示设备。比如打开应用,常见的有三层显示,顶部的 statusbar 底部或者侧面的导航栏以及应用的界面,每个层是单独更新和渲染,这些界面都是有 surfaceflinger 合成一个刷新到硬件显示。

statusbar 状态栏,系统提供的底部导航栏(比如魅族底部的那一条),他们都不再 docverView 里,不是写在基类里的,而是由专门的系统进程维护的,可以看成别的 app 里面的 window,由 surfaceflinger 进程把这些合并在一起次啊可以看见,看下图:



看到没,壁纸也是由独立进程维护的,有自己的 window


渲染中各个角色

上面说了很多边边角角的东西,现在终于到了 android 渲染的核心啦,先来看看这段经典的叙述,基本上 android 渲染中的主要角色都有提及:

SurfaceFlinger在系统启动阶段作为系统服务被加载。应用程序中的每个窗口,对应本地代码中的Surface,而Surface又对应于SurfaceFlinger中的各个Layer,SurfaceFlinger的主要作用是为这些Layer申请内存,根据应用程序的请求管理这些Layer显示、隐藏、重画等操作,最终由SurfaceFlinger把所有的Layer组合到一起,显示到显示器上。当一个应用程序需要在一个Surface上进行画图操作时,首先要拿到这个Surface在内存中的起始地址,而这块内存是在SurfaceFlinger中分配的,因为SurfaceFlinger和应用程序并不是运行在同一个进程中,应用客户端(Surface)和服务端(SurfaceFlinger - Layer)之间使用匿名共享内存来传递和同步显示缓冲区

android 渲染其实就2个端:应用进程Surfaceflinger 进程,应用进程负责渲染 viewTree,把数据交给 Surfaceflinger,Surfaceflinger 负责图层(layer)压和。因为 UI 渲染数据量大,应用进程和Surfaceflinger 进程之间不适合使用 Binder 传递数据,而是采取共享内存的方式

这块匿名共享内存的使用是交给底层 Native 代码的,应用层是感受不到的,需要把共享内存和 Canvas 在 Native 底层的实现关联起来才能操作:

JAVA层要想在Surface上进行画图操作,必须要先把其中的一个缓冲区绑定到Canvas中,然后所有对该Canvas的画图操作最后都会画到该缓冲区内

在这2端负责管理这块共享内存的就是:surfacelayer了,下面会重点看看surfacelayer,看看他们的类结构和关联,那么渲染的基本逻辑很明确的表现出来了

Surfaceflinger 端显示缓存的管理

Surfaceflinger 服务是在独立的进程中,每当一个 app 进程与 Surfaceflinger 服务建立联系后,都会给这个 app 在 Surfaceflinger 端生成一个管理显示缓存的类:SharedClient

app 端每个 window 对应一个 surface(Surfaceflinger 显示缓存在 app 端的映射),每个 surface 在 Surfaceflinger 端的 SharedClient 中都会对应一个生成一个 SharedBufferStack 用于管理显示缓存。因为 app 端是可以有多个 window 的,所以 SharedClient 中的 SharedBufferStack 是队列结构,是复数的,一般 SharedClient 中最多可以有 31个 SharedBufferStack,看图: SharedBufferStack 代表每一个 surface(渲染平面)对应的显示缓存,但是 SharedBufferStack 中的缓存可以不是一块,也是一个队列,看图:

SharedBufferStack 采用缓存队列的方式存储显示数据,GraphicBuffer 才是具体一块显示缓存, SharedBufferStack 这里应该是为了性能考虑才采用的对来结构吧

app 端每次渲染后会先从 Surfaceflinger 端申请显示缓存,Surfaceflinger 一次会给 app 端2个 GraphicBuffer,为啥是2个呢,这里我没有找到确切的说法,我认为是为了处理 window 切换动画,因为在启动页面切换动画时,下一个 window 也需要参与显示,所有需要2块显示缓存 GraphicBuffer 去存储2个 window

但是话又说回来,新的页面的 window 自然也会去渲染,也会自己申请 surface 的,window 的变换动画由 WMS 控制,同步给 SF 的 layer 层,这么一想的话又说不通了,大家还是自己去找找吧,我是没搞清楚这块

Surfaceflinger 端 layer

一块显示缓存在 Surfaceflinger 端的表现就是 layer,layer 大家直接当图层看也行,反正在 Surfaceflinger 端 layer 就是管理显示缓存的顶层类了

Layer 对象的层级结构比较深,可以追溯出一层层父类来,这些父类提供了 Layer 的核心功能,来看看 Layer 的类图:


1346180533_7152.jpg

Surfaceflinger 端 layer 结构不难看,分配了2个职责,一个是联通共享缓存栈,一个是跨进程通讯,以便和 app 端直接通信

app 端 surface

surface 是 app 进程端的,也有叫:渲染平面的。surface 和 window 是一一对应的,另外有些特殊 view 也拥有自己的 surface,比如 surfaceView、textrueView

具体的解释大家看看下面的,非常贴切:

每个应用程序可能对应着一个或者多个图形界面,而每个界面我们就称之为一个surface ,或者说是window

每个surface 在屏幕上有它的位置,以及大小,然后每个surface 里面还有要显示的内容。这就要求我们需要一个结构来记录应用程序界面的位置,大小,以及一个buffer 来记录需要显示的内容,所以这就是我们 surface 的概念,surface 实际我们可以把它理解成一个容器,这个容器记录着应用程序界面的控制信息,比如说大小啊,位置啊,而它还有 buffer 来专门存储需要显示的内容。

各个surface 之间可能有重叠,我们可以想象在屏幕平面的垂直方向还有一个Z 轴,所有的surface 根据在Z 轴上的坐标来确定前后,这样就可以描述各个surface 之间的上下覆盖关系了,而这个在Z 轴上的顺序,图形上有个专业术语叫Z-order。

当图形(surface )重合的时候怎么处理,有些surface 还带有透明信息,这就用到 SurfaceFlinger 了。SurfaceFlinger 会把各个surface 组合(compose/merge) 成一个main Surface ,最后将Main Surface 的内容发送给FB/V4l2 Output ,这样屏幕上就能看到我们想要的效果。

有2个节点大家记一下:

为什么 app 端和 WMS 端都要有对应的 Surface 呢,app 端是负责渲染绘制图形界面的,WMS 端负责控制 surface 的大小、位置、动画,分工不同而已,有个图大家看一下:

注意: WMS 改变 Surface 的位置、大小时会影响 Surfaceflinger 合成 layer 的,layer 所处的位置大小变化,自然最终的图像也会变化的,这里 WMS 对 Surface 的修改是要通知 Surfaceflinger 的,Surfaceflinger 会根据变化重新计算每个 layer 可显示范围

可以看到对应显示缓存管理都放到 Surfaceflinger 进程了,WMS、app 端都是对应不同级别的 Binder 通信对象

关于 surface 就说这么多,看看类结构就知道大概的流程了,要分析代码的话都得深入系统层的 C 代码里,大家去看上面我列出的参考资料吧,那里全,我这里只是做个科普。有多大量,吃多少饭,我觉得我写到这个程度就差不多了,仔细分析 C 代码超出我的能力了


Surfaceflinger 捕捉硬件 VSYNC 信号

大家知道 Android 屏幕没 16ms 刷洗一帧,一直以来我都是认为是 android 系统控制的这个刷频率,知道近期学习后才知道真是大错特错。这个刷新频率不是软件实现的,而是由硬件控制的。大家都知道一加 pro7吧,他可是用了 90HZ 的屏幕,要不是硬件 LCD 来发送同步刷新信号的话,那么一加 pro7 换屏也不能从 60帧 提升到 90帧了

android LCD 硬件设备专门有一块控制屏幕刷新的电路,这块电路每 16ms 通过硬件驱动向系统发送一个刷新信号,一般大家都遵循 PC 上的传统管这个刷新信号叫:VSYNC 信号

android 系统把管理硬件缓存区和和控制图像渲染的工作交给了 Surfaceflinger 进程,那么可以肯定这个 VSYNC 信号肯定是 Surfaceflinger 进程来捕捉了,所以我们来看看 Surfaceflinger 进程

Surfaceflinger.init 函数初始化了硬件信号发生器对象 HWComposer,代码要分层嘛,Surfaceflinger 进程把硬件信号管理放到了 HWComposer 里


HWComposer 里最关键的就是往 LCD 硬件驱动上注册了同步信号监听 registerProcs

HWComposer::HWComposer(const sp<SurfaceFlinger>& flinger, EventHandler& handler)
    : mFlinger(flinger),
      mFbDev(0), mHwc(0), mNumDisplays(1),   //初始情况下mHwc是一个空指针
      mCBContext(new cb_context),            //这里新创建了一个cb_context对象
      mEventHandler(handler),
      mDebugForceFakeVSync(false)
{ 
    bool needVSyncThread = true; 
    .....
    // Note: some devices may insist that the FB HAL be opened before HWC. 尝试打开硬件HWC
    int fberr = loadFbHalModule();
    loadHwcModule();  //加载HWC模块,若打开成功,则mHwc将不为空
    .......
    if (mHwc) {
        ALOGI("Using %s version %u.%u", HWC_HARDWARE_COMPOSER,
              (hwcApiVersion(mHwc) >> 24) & 0xff,
              (hwcApiVersion(mHwc) >> 16) & 0xff);
        if (mHwc->registerProcs) {
            mCBContext->hwc = this;
            mCBContext->procs.invalidate = &hook_invalidate;
            mCBContext->procs.vsync = &hook_vsync;   //硬件产生中断,通过hook_vsync函数进行同步信号的通知
            if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1))
                mCBContext->procs.hotplug = &hook_hotplug;
            else
                mCBContext->procs.hotplug = NULL;
            memset(mCBContext->procs.zero, 0, sizeof(mCBContext->procs.zero));
            mHwc->registerProcs(mHwc, &mCBContext->procs);
        }

        // don't need a vsync thread if we have a hardware composer  存在硬件同步信号发生器,就不需要软件模拟线程
        needVSyncThread = false;
    }
}

HWComposer 接受到硬件同步信号后会回调自己的 hook_vsync方法,hook_vsync方法里又会回调 EventHandler.onVSyncReceived 方法,HWComposer 对象里的 EventHandler 是在构造函数里传进来的,看代码其实就是外层的 Surfaceflinger 自己

到这,Surfaceflinger 就实现监听硬件同步信号的任务,HWComposer 的具体代码大家自己去找,我这里不放了


Surfaceflinger 分发硬件 VSYNC 信号

上文说到 Surfaceflinger 实现了硬件同步信号监听,在硬件同步信号来了后会回调 EventHandler.onVSyncReceived 方法,这里我们来看看 Surfaceflinger 收到信号后怎么处理

我们先看 onVSyncReceived 这个函数,往 DispSync 里添加了一个同步信号

void SurfaceFlinger::onVSyncReceived(int type, nsecs_t timestamp) {
    bool needsHwVsync = false; 
    { // Scope for the lock
        Mutex::Autolock _l(mHWVsyncLock);
        if (type == 0 && mPrimaryHWVsyncEnabled) {
            needsHwVsync = mPrimaryDispSync.addResyncSample(timestamp);
        }
    } 
}

DispSync 是 Surfaceflinger 的成员变量,在 Surfaceflinger 初始化时就创建了,DispSync 核心就是创建并启动了一个 DispSyncThread 类型的线程

DispSync::DispSync() : mRefreshSkipCount(0), mThread(new DispSyncThread()) {
    mThread->run("DispSync", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE); 
    reset();
    beginResync(); .....
}

DispSyncThread 线程里有集合,保存监听 VSYNC 信号的具体任务,有队列来不停循环遍历执行任务,没任务时线程挂起,上面的 mPrimaryDispSync.addResyncSample 方法其实是唤醒线程,添加VSYNC 信号

  // 这是线程循环任务
   virtual bool threadLoop() {
        while (true) {
            Vector<CallbackInvocation> callbackInvocations; 
            nsecs_t targetTime = 0;
            { // Scope for lock
                Mutex::Autolock lock(mMutex); 
                bool isWakeup = false; 
                if (now < targetTime) {
                    err = mCond.waitRelative(mMutex, targetTime - now);
                } 
                now = systemTime(SYSTEM_TIME_MONOTONIC);  
                callbackInvocations = gatherCallbackInvocationsLocked(now);
            }
            if (callbackInvocations.size() > 0) {
                fireCallbackInvocations(callbackInvocations);
            }
        } 
        returnfalse;
    }
    // 遍历所有 callback 回调
    void fireCallbackInvocations(const Vector<CallbackInvocation>& callbacks) {
        for (size_t i = 0; i < callbacks.size(); i++) {
            callbacks[i].mCallback->onDispSyncEvent(callbacks[i].mEventTime);
        }
    }

Surfaceflinger 在接受硬件的 VSYNC 信号后,唤醒专门分发 VSYNC 信号的线程,在线程里面遍历所有关心 VSYNC 信号任务的监听器的 onDispSyncEvent 函数。这里 listener 监听器其实是别人进程通过 Binder 注册到 SurfaceFlinger.mPrimaryDispSync.mEventListeners 里的


app 端接受硬件同步信号

上面说到了 Surfaceflinger 里面专门有个线程 DispSyncThread 来分发 VSYNC 信号,那么只要 app 在合适的时候往 Surfaceflinger 里注册下监听 callback 就能准确的相应屏幕刷新任务了

在绘制 view 时,不管我们走的是 requestLayout,还是 view.invalidate,最终都是走到 ViewRootImpl.scheduleTraversals 方法

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

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

scheduleTraversals 方法干了2件事

  1. 先执行 postSyncBarrier 方法,给 UI 线程 loop 加了一个同步屏障,目的是阻止 handle 的同步消息执行,同步消息就是我们平常添加入 handle 的任务,这样就可以把 UI 线程腾出来,保证下一个 VSYNC 信号来时第一时间可以执行渲染页面的任务,记住这里
  2. mChoreographer.postCallback,给编舞者 Choreographer 添加一个任务,这个任务就是 doTraversal 渲染页面,包裹在一个 Runnable 里面,为什么要包一层 Runnable 呢,因为下一个 VSYNC 信号来后,Choreographer 会发送一个 handle 异步任务,message.post(runnable) 里的 runnable 就是我们刚刚添加的 runnable 任务。因为给 handle 设置同步屏障后,之后异步任务才能执行,这样才能保证页面渲染的及时性,后面会具体分析这里的,大家先记住结论
  3. doTraversal 方法中,及时接触了 handle 的同步屏障,要不 handle 的同步消息永远没有执行的机会,添加同步屏障后 handle 的运行机制是: 遍历执行异步任务,异步任务没有了线程会进入休眠状态,并不会执行同步任务

注意:Choreographer 是页面接收 Surfaceflinger 进程发送 VSYNC 信号的核心

我们每 mChoreographer.postCallback 一次,就会接收一次下一个 VSYNC 信号,记住只能接收一下,要是以后还要接收 VSYNC 信号,就得继续 mChoreographer.postCallback,所以重点就转移到 Choreographer 身上了

Choreographer 我们称为:编舞者,用来同步 LCD 硬件刷新频率,应用层可以及时第一时间相应屏幕刷新信号

Choreographer 运行机制
  1. Choreographer 和 ViewRootImpl 一一对应,有一个 ViewRootImpl 就有一个 Choreographer
public ViewRootImpl(Context context, Display display) {
    ...
    //获取Choreographer实例
    mChoreographer = Choreographer.getInstance();
    ...
}

public static Choreographer getInstance() {
    return sThreadInstance.get();
}

private static final ThreadLocal<Choreographer> sThreadInstance =
        new ThreadLocal<Choreographer>() {
    @Override
    protected Choreographer initialValue() {
        Looper looper = Looper.myLooper();
        if (looper == null) {
            throw new IllegalStateException("The current thread must have a looper!");
        }
        return new Choreographer(looper);
    }
}
  1. Choreographer 的核心是 mCallbackQueues 存储绘制任务的容器和 mDisplayEventReceiver 接收 VSYNC 信号的接收器,mDisplayEventReceiver
private Choreographer(Looper looper) {
   mLooper = looper;
   mHandler = new FrameHandler(looper);
   // 根据是否使用了VSYNC来创建一个FrameDisplayEventReceiver对象
   mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
   mLastFrameTimeNanos = Long.MIN_VALUE;//是指上一次帧绘制时间点
   mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());//帧间时长,一般等于16.7ms
   // CALLBACK_LAST + 1 = 4,创建一个容量为4的CallbackQueue数组,用来存放4种不同的Callback
   mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
   for (int i = 0; i <= CALLBACK_LAST; i++) {
       mCallbackQueues[i] = new CallbackQueue();
   }
}
  1. postCallback 最终会调用 postCallbackDelayedInternal,别看这个方法热闹,其实就是把传进来的 runnable 任务保存到 mCallbackQueues 队列里,然后执行 scheduleFrameLocked 去 Surfaceflinger 进程上注册监听任务
private void postCallbackDelayedInternal(int callbackType,
       Object action, Object token, long delayMillis) {
   synchronized (mLock) {
       // 当前时间
       final long now = SystemClock.uptimeMillis();
       // 回调执行时间,为当前时间加上延迟的时间
       final long dueTime = now + delayMillis;
       // obtainCallbackLocked(long dueTime, Object action, Object token)会将传入的3个参数转换为CallbackRecord(具体请看源码,非主要部分,此处略过),然后CallbackQueue根据回调类型将CallbackRecord添加到链表上。
       mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
       if (dueTime <= now) {
           // 如果delayMillis=0的话,dueTime=now,则会马上执行
           scheduleFrameLocked(now);
       } else {
           // 如果dueTime>now,则发送一个what为MSG_DO_SCHEDULE_CALLBACK类型的定时消息,等时间到了再处理,其最终处理也是执行scheduleFrameLocked(long now)方法
           Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
           msg.arg1 = callbackType;
           msg.setAsynchronous(true);
           mHandler.sendMessageAtTime(msg, dueTime);
       }
   }
}
  1. scheduleFrameLocked 会执行 scheduleVsyncLocked,再执行 scheduleVsync。在 scheduleVsync 内部会通过 nativeScheduleVsync 在 Native 代码层上去 Surfaceflinger 进程上注册监听任务,这种监听任务只能监听一次
private void scheduleVsyncLocked() {
    mDisplayEventReceiver.scheduleVsync();
}
public void scheduleVsync() {
    if (mReceiverPtr == 0) {
        Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                + "receiver has already been disposed.");
    } else {
        nativeScheduleVsync(mReceiverPtr);
    }
}

中断一下,这是别人写的,大家先看看,略顺了好往下看

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

至于 nativeScheduleVsync 如何与 SurfaceFlinger 建立联系,我看老罗的说法:SurfaceFlinger 进程有一个 EventThread 线程,该线程接受到 SurfaceFlinger 第一次发射的 VSYNC 信号再进行2次发射。每个 app 进程与 SurfaceFlinger 进程保持一个 socket 通信链接,类型为 conntation 然后保存到 EventThread 线程的指定容器内。这里我只是看到老罗这么说,其然的解释我没找到,先姑且这么认为

  1. FrameDisplayEventReceiver onVsync 直接接受到 SurfaceFlinger 发送过来的 VSYNC 信号,然后向主线程 handle 发送一个异步任务,异步任务里执行的是下面的 run 方法
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
    private boolean mHavePendingVsync;
    private long mTimestampNanos;
    private int mFrame;
    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
        //忽略来自第二显示屏的Vsync
        if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
            scheduleVsync();
            return;
        }
        ...
        mTimestampNanos = timestampNanos;
        mFrame = frame;
        //该消息的callback为当前对象FrameDisplayEventReceiver
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        //此处mHandler为FrameHandler
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }
    @Override
    public void run() {
        mHavePendingVsync = false;
        doFrame(mTimestampNanos, mFrame); 
    }
}

我描述不清的话,大家看别人的解释

可见onVsync()过程是通过FrameHandler向主线程Looper发送了一个自带callback的消息 callback为FrameDisplayEventReceiver。 当主线程Looper执行到该消息时,则调用FrameDisplayEventReceiver.run()方法,紧接着便是调用doFrame。

  1. doFrame 就是把之前存进来的 callback 都跑一遍
void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        if (!mFrameScheduled) {
            return; // mFrameScheduled=false,则直接返回。
        }
        long intendedFrameTimeNanos = frameTimeNanos; //原本计划的绘帧时间点
        startNanos = System.nanoTime();//保存起始时间
        //由于Vsync事件处理采用的是异步方式,因此这里计算消息发送与函数调用开始之间所花费的时间
        final long jitterNanos = startNanos - frameTimeNanos;
        //如果线程处理该消息的时间超过了屏幕刷新周期
        if (jitterNanos >= mFrameIntervalNanos) {
            //计算函数调用期间所错过的帧数
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
            //当掉帧个数超过30,则输出相应log
            if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                Log.i(TAG, "Skipped " + skippedFrames + " frames! "
                        + "The application may be doing too much work on its main thread.");
            }
            final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
            frameTimeNanos = startNanos - lastFrameOffset; //对齐帧的时间间隔
        }
       //如果frameTimeNanos小于一个屏幕刷新周期,则重新请求VSync信号
        if (frameTimeNanos < mLastFrameTimeNanos) {
            scheduleVsyncLocked();
            return;
        }
        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
        mFrameScheduled = false;
        mLastFrameTimeNanos = frameTimeNanos;
    }
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        //分别回调CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL事件
        mFrameInfo.markInputHandlingStart();
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
        mFrameInfo.markAnimationsStart();
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
        mFrameInfo.markPerformTraversalsStart();
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}
  1. 这样经 Choreographer 调度之后,最终又回到了 ViewRootImpl.doTraversal,在这里先解除了 handle 的同步屏障,然后进行具体的绘制任务

好了到这里 Choreographer 同步 VSYNC 信号的流程就过了一遍,大家几个大概就行,记代码也没有意义


Surfaceflinger 线程模型

前面可是说了 Surfaceflinger 不少东西了,但是 Surfaceflinger 还是要再撸撸的,谁让 Surfaceflinger
干的事多呢,多就复杂了,越复杂越不好搞,越容易理解错误

Surfaceflinger 里面一共有3个线程和一个线程池:

看个图好理解一些:

代码分析大家去看: Android垂直同步信号VSync的产生及传播结构详解


app EventThread

上文书这个名为 app 的 EventThread 线程负责接收 Surfaceflinger 进程中 DispSyncThread 线程赚发的来自硬件层的 VSYNC 信号,进行二次转发。为啥需要二次转发,我认为应该是考虑必须要第一时间相应硬件层 VSYNC 信号,这个线程不能干别的,只干一件事:就是信号转发给二道贩子。二道贩子拿到信号之后怎么处理都不干扰接收信号。

app 层的 Choreographer 编舞者是通过 DisplayEventReceiver.nativeScheduleVsync 方法通过底层到 Surfaceflinger 进程上注册 callback 回调,这个 callback 啥也不干也是就接收 VSYNC 信号,保存 callback 的活自然落到这个 app EventThread 身上了

具体的过程大家看:EventThread 分析 这篇分析吧

总之每个 app 在 DisplayEventReceiver.nativeScheduleVsync 后会与app EventThread 建立一个 Connection,这个 Connection 是 socket 链接,但后再把这个 Connection 包装成 callbacj 注册到
DispSyncThread VSYNC 监听线程上



SF EventThread

SF EventThread 可以看成是 Surfaceflinger 的 Ui线程了,进行 layer 合成工作,具体的内容是很复杂的,这里简单介绍下流程

SF EventThread 线程内部一样是 handle 队列, 等待 invalidate 重绘消息,这个通知来自其进程,可以是 app 进程,也可以是 WMS 进程。app 进程渲染完自己的 surface 后会通知 Surfaceflinger 进程,这个通知就是一个 invalidate 消息。WMS 修改 window 的大小,位置后也会通知 Surfaceflinger 进程,同样这也是一个 invalidate 消息

SF EventThread 线程接到消息后会进行以下操作:

这里我就写这么多了,详细的大家应该去看看 C 层的代码


Surfaceflinger layer 合成方式

前面常常说图层合成,其实 layer 图层合成有2种方式,目前 android 设备用的都是同一种方式:Client 离屏合成,但是未来不排除出现另一种图层合成方式,这里算是增加大家认识

目前 SurfaceFlinger 支持两种合成方式,SurfaceFlinger 在收集可见层的所有缓冲区之后,便会询问 Hardware Composer 应如何进行合成:

这2种 layer 合成方式本质上是有没有专用的硬件设备,大家想啊,要合成 layer 需要有 GPU 类似的设备去调用 openGL API,这得给 LCD 增加多少成本啊,所以目前 android 设备中没有采用这种合成方式的,但是也不能排除没有,我觉得那些简单的户外广告机说不准就是采用这种合成方式


handler 同步屏障

在学习 Choreographer 编舞者的时候,我们看到了每次绘制 viewTree 都会先给 handler 添加同步屏障,以阻塞同步任务,保证能第一时间接收 SF 进程发送过来的 VSYNC 信号

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

那么大家有没有想过为什么?万事问个为什么其实是我们成长的标志,标致我们不在满足于表面的 API 了,而是去耐心思考期中的运行规律,原理,这是非常重要的,原理出处通用,差别是平台不同,实现不同

先看看 postSyncBarrier 方法:

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

postSyncBarrier 大家看懂了没,handler 模型中存储 message 消息的是 loop 里面 MessageQueue,MessageQueue 里面有个成员变量 mMessages,他是最终的数据容器,采用链表结构,以前我还看不懂,但是在这次研究同步屏障时终于搞懂了

大家知道了 postSyncBarrier 中出现的 mMessages 是个链表,也知道链表的结构是 next 一级级的指向下一个数据,那么这个方法其实就好懂了

其实就是把 tager == null 的消息放到链表最后,tager == null 的 message 其实是表示开始忽略同步消息,专门获取异步消息,这样做的目的是给 handler 加个优先级,放到队尾是为了不影响前面同步消息的执行

添加了 tager == null 的标记,那么下一步的重点就是获取消息的方法了 next(),我们正常 sendMessage 发送消息时,API 都会给 message 添加 tager 的,tager 就是 handle,一遍后面执行

Message next() {
    //...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        //...

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {//碰到同步屏障
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                // do while循环遍历消息链表
                // 跳出循环时,msg指向离表头最近的一个异步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //...
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        //将msg从消息链表中移除
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    //返回异步消息
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            //...
        }

        //...
    }
}

next 方法中同步屏障的重点就在这里

if (msg != null && msg.target == null) {//碰到同步屏障
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                // do while循环遍历消息链表
                // 跳出循环时,msg指向离表头最近的一个异步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());

msg.target == null 标致启动同步屏障,do while 循环大家仔细看是把 链表队列中的 msg.isAsynchronous == true 的 message 消息拿出来,不是的就不拿,有就执行,没有就会阻塞当前线程,msg.isAsynchronous == true 的 message 在哪里发出来的,这个 Asynchronous 标志位就是表示该消息是异步的

可能有朋友会问,为什么我在启动同步屏障之后添加同步消息都不会执行啊,大家继续看上面的代码,链表永远不会消费碰到的第一个 tager == null 的消息,在首次 tager == null 时会开始 do while 去继续向后寻找 tager == null 的 message,有就拿出来消费,没有的话阻塞线程,并不会返回第一个 tager == null,有这个 tager == null 的 message 卡着,同步消息永远不会执行。取消同步屏障就是把这个永远占位的 tager == null 的 message 拿走

这个消息 msg.isAsynchronous == true 是 Choreographer 的 VSYNC 接收器在接收到信号时发射的,这下大家知道了吧,这个消息里会解除同步屏障

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
    private boolean mHavePendingVsync;
    private long mTimestampNanos;
    private int mFrame;
    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
        //忽略来自第二显示屏的Vsync
        if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
            scheduleVsync();
            return;
        }
        ...
        mTimestampNanos = timestampNanos;
        mFrame = frame;
        //该消息的callback为当前对象FrameDisplayEventReceiver
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        //此处mHandler为FrameHandler
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }
    @Override
    public void run() {
        mHavePendingVsync = false;
        doFrame(mTimestampNanos, mFrame); 
    }
}

OK,同步屏障就是个样子,所谓的同步与异步在这里其实名不副实,只是表示一个优先级罢了, handler 把他们放在一起,逻辑上非常复杂,非常不容易理解,大家多看看,这也是为什么大公司强调算法的原因,学习源码,这样的算法随处可见


其他一些概念

材质和纹理都只是颜色,但是侧重不同

上一篇 下一篇

猜你喜欢

热点阅读