关于运用开发工程师来说,虽然咱们不需求写操作体系代码,可是了解View终究是怎样显现到屏幕上仍是十分有必要的

本篇是Android图形系列的第三篇文章,在之前的两篇文章平别离介绍了屏幕的“显现原理”和屏幕的“改写原理”,今日咱们来一同学习Android体系的图形架构规划,聊一聊输送到屏幕的画面数据是怎样发生的

本文的方针是期望读者朋友树立一个Android图形子体系的结构,因而,文中不会包括太多的办法调用链以及代码逻辑,非Android开发工程师也可以放心食用

前排提示:全文近2万字,建议阅览时长30分钟

android_graphic_v3_overview.jpg

一、开篇

“当咱们点开‘微信’这个运用后,它是怎样在屏幕上显现出来的?”

这是一个十分杂乱的问题,它的背面包括了由厂商驱动、Linux操作体系、HAL硬件笼统层和Android Framework结构层一同组成的一套十分庞大的Android图形子体系

想要给出这个问题的答案,就必须对Android图形子体系背面的运转流程有所了解

今日,咱们从知道Android设备中的硬件开端,自下而上

一同来看看庞大的Android图形子体系是怎样组成起来的

1、硬件驱动

雷总曾经说过,再杂乱的体系规划,也离不开硬件的支撑

在文章的最初,咱们就先来了解一下:支撑运用程序绘图的硬件有哪些?

android_graphic_v3_mi10_dismantle.png

图片来历:www.ednchina.com/technews/12…

上图是一张小米11的拆解图

从左到右别离是:屏幕、相机模组、主板IC以及手机外壳

小米11运用的是高通的”骁龙888“处理器,内存运用的是来自镁光的LPDDR5 8GB芯片(下图中1/2部分,处理器和内存运用堆叠封装,俯视图它俩是叠在一同的

android_graphic_v3_mi10_mainboard.png

图片来历:www.laoyaoba.com/n/780219

剩下按序号别离是:海力士128GB闪存芯片、高通的射频收发芯片、高通WiFi6/BT芯片、两颗高通的快充芯片、伏达的无线充电芯片等等,和本篇文章关系不大

回到小米11的SOC处理器部分(图1/2)

高通骁龙888(见下图)内部集成了Kryo 680 CPUAdreno 660 GPUSpectra 580 ISPX60 5G等多个处理器芯片

android_graphic_v3_snapdragon_888.jpeg

图片来历:www.dpreview.com/news/296919…

在骁龙888封装的很多芯片中,Adreno 660 GPU模块(见下图)是咱们需求关怀的重点

由于它封装了咱们在图形体系中常常运用到的两块芯片:

GPU(Graphics Processing Unit)和DPU(Display Processing Unit)

android_graphic_v3_adreno660.jpeg

图片来历:www.dpreview.com/news/296919…

咱们都知道,一幅图画的显现必需求经过烘托组成送显这三个阶段,才能展现给用户

GPU和DPU便是用于供给给运用程序烘托和组成才能的硬件设备,其间:

  • GPU芯片担任履行图形显现流程中的“烘托”作业

  • DPU芯片担任履行图形显现流程中的“组成”作业

接下来的章节中,咱们从驱动程序的角度来聊聊什么是烘托,以及什么是组成?

图形烘托驱动

1. 什么是烘托

众所周知,烘托作业是由GPU担任,那么在聊GPU之前首先得知道什么是“烘托”?咱们来举个比方感受一下

在体系中恳求一块10*10巨细的图层内存,履行下列几条指令:

1、把图层布景这玩意染成绿的

2、从左上(0,0)到右下(10,10)画一条宽度为1色彩为赤色的直线

3、以坐标点(5,5)为中心,画一个半径为3的实心圆,色彩要蓝色

好了,接下来操作体系会把这些“绘图指令”同步给GPU去履行烘托使命

android_graphic_v3_gpu_draw.png

图片来历:自己画的

GPU完结烘托作业后,咱们将会得到一个巨细为10*10二维数组,数组中的每个元素都保存着坐标点的色彩信息、深度信息

对应着将来要显现到屏幕上的一个个像素点

把绘图指令转化为二维像素数组的进程,就叫做“烘托”

本章节举了一个很简略的2D绘图的小比方,首要目的是让咱们了解烘托作业是做什么的

图画烘托是一个十分杂乱的论题,关于烘托完结原理可以点击[这儿]

2. 什么是GPU

GPU全称是GraphicProcessing Unit,中文是图形处理器,其最大的作用便是进行各种制作核算机图形所需的运算,包括顶点设置、光影、像素操作等

也便是用来履行咱们开发者调用的一个个的“绘图指令”

所以,GPU实践上是多组图形函数API的调集,这些函数由硬件驱动完结

硬件厂商们遵从着不同协议的开发标准,比方OpenGL ES、Vulkan等等

android_graphic_v3_adreno660_specifications.png

图片来历:chiptechie.com/mobile-gpu/…

上图是Adreno 660 GPU的介绍页面,可以看到小米11运用的这块芯片支撑OpenGL ES 3.2版别、OpenCL 2.0版别和Vulkan 1.1版别

别的,660还支撑了微软家的DirectX,这就意味着在安装了Windows ARM版的小米11中,运用程序也可以运用骁龙GPU来加速图形的烘托

更多关于GPU的介绍请点击[这儿]

图形组成驱动

图形烘托阶段完毕今后,接下来就到了图形的组成阶段

图形组成咱们可以简略了解为Photoshop中的图层组成:

每个图层都有不同的内容,点击兼并图层今后电脑会核算堆叠区域的像素改变,终究把多个图层兼并为一个图层

这么说或许仍是有点笼统,接下来咱们经过一个比方来协助了解什么是“组成”?

1. 什么是组成

这是一张launcher桌面的截屏,它是由“壁纸”、“顶部的状况栏”、“桌面的运用列表”以及“底部导航栏”这4个图层组成

android_graphic_v3_hwc_finally.png

图片来历:blog.zhoujinjian.cn/posts/20210…

1、壁纸图层:

android_graphic_v3_hwc_wallpaper.png

图片来历:blog.zhoujinjian.cn/posts/20210…

2、顶部状况栏图层(很小的一个横条):

android_graphic_v3_hwc_statusbar.png

图片来历:blog.zhoujinjian.cn/posts/20210…

3、桌面运用列表:

android_graphic_v3_hwc_launcher.png

图片来历:blog.zhoujinjian.cn/posts/20210…

4、底部导航栏:

android_graphic_v3_hwc_navigationbar.png

图片来历:blog.zhoujinjian.cn/posts/20210…

这其间的每个图层,都是由上一步的GPU烘托出来的的作用

每个图层烘托完结今后,理论上可以直接送到屏幕上去显现了,可是

大多数状况下屏幕上都不止一个图层

一旦图层与图层之间发生堆叠(比方launcher的状况栏、运用列表和导航栏这3个图层都是叠加在壁纸图层的上面),堆叠部分的像素色彩就需求从头核算

将多个图层兼并成一个图层的进程,被称为“组成”

2. 什么是DPU

组成作业的本质是履行核算“脏区域”、格局转换、处理缩放等操作,这些使命咱们也可以调用GPU的接口来完结

当咱们认真观察组成的流程会发现,在履行图层组成的进程中是不需求3D操作的,由于早在“图层烘托”那一步GPU就完结了一切的3D处理的作业

这样的话,咱们只需求为组成流程独自装备一块2D烘托引擎就OK了

现在在绝大多数Andorid设备中,承当这一职责的便是DPU芯片了

DPU作为图形硬件的一部分,一般被封装在GPU模块傍边,最首要的功用是将GPU烘托完结的图层输出到屏幕

兼任了组成功用今后,关于图层堆叠的部分,DPU会自动核算出“脏区域”并更新像素色彩改变

不过,DPU虽然可以履行组成作业,但它有组成数量的限制

如下图,Arm Mali-DP550这款DPU最多可以支撑7层的组成使命

android_graphic_v3_mali_dp550.jpg

图片来历:community.arm.com/cn/f/discus…

3. 什么是HWC

上一节聊DPU的时分咱们说到了,假如想把组成流程独立出来,只需求独自装备一块2D烘托芯片就行了

厂商可以挑选将组成作业放在DPU中,也可以挑选在板子上加一块2D烘托芯片,将组成作业放在这块芯片中

抠门一点的厂商,可以挑选不独自装备组成芯片,只运用GPU进行组成

这就需求一个标准把组成作业笼统成一个接口,由厂商自由挑选组成方案

Hardware Composer便是专门用来定义组成作业的笼统接口,它是Android Hardware Abstraction Layer(HAL)硬件笼统层的成员之一

在HWC中,厂商运用的是DPU仍是其他的2D烘托芯片不重要,只需求完结HWC的接口即可

咱们来看Google官网关于HWC的定义:

android_graphic_v3_developer_hwc.png

图片来历:source.android.com/devices/gra…

《每个字都知道便是不知道在说什么》系列,我把这段话依照标号翻译一下:

  1. 用DPU做组成比GPU要高效

  2. 第二点比较重要,里边包括了hwc的履行逻辑,大致流程是这样:

    sf进程:有6个烘托好的图层过来了,我悉数塞给你,你自个儿处理完去送显?

    hwc:不行,我最多只能处理4个图层的兼并作业,剩下两个图层我标记为GPU组成了,你处理一下

    sf进程:调用GPU完结别的2个图层的组成作业,并将兼并后的图层交给hwc

  3. 第三点我不是很了解什么意思,由于理论上假如屏幕内容没有发生改变,sf不应该走组成流程,

  4. 超出hwc才能的图层会调用GPU组成,假如运用的图层太多会对功用发生影响,比方APP弹窗过多,每个Dialog都算一个图层

关于第二点咱们可以运用adb shell dumpsys SurfaceFlinger指令查看当时图层的组成办法,DEVICE表明HWC组成,CLIENT表明GPU组成

android_graphic_v3_hwc_pixel3.jpg

图片来历:自己截的

别的,由于绝大多数状况下hwc的完结者是DPU,所以除了组成作业外,hwc模块还担任送显以及发送VSync信号

好了,hwc的部分到这儿先告一段落,咱们来总结一下“硬件驱动”末节的内容

本章节的重点是了解什么是烘托和组成以及烘托/组成运用的硬件设备是什么,烘托和组成之间的差异:

  • 烘托,重视的是单个图层的内容,在当时图层的坐标系中,每个坐标点应该显现什么色彩
  • 组成,重视的是多个图层的内容,将多个图层从头核算后得到一个图层,参阅ps的兼并图层功用

烘托、组成这两个阶段所需的硬件部分现已介绍完结,接下来咱们一同看看Google为图形体系预备了哪些软件组件库

2、Google组件库

Andorid供给的初级别组件库更多是操控图形内存的流程以及内存结构的封装

比方CPU、GPU和HWC要同享同一块内存,那就需求一种格局让它们都能识别这块内存

别的,还需求一种机制确保数据的安全,防止发生某个硬件在运用进程中数据被其他硬件篡改的状况

图形体系中包括了[libui.so]和[libgui.so]两个初级别组件库,接下来一同来看看这两个库里边别离有什么

libui组件库

1. 什么是GraphicBuffer

GraphicBuffer是整个图形体系的中心,所以的烘托操作都将在此方针上进行,包括同步给GPU和HWC

每当运用有显现需求时,运用会向体系恳求一块GraphicBuffer内存,这块内存将会同享给GPU用于履行烘托作业,接着会同步给HWC用于组成和显现

咱们可以把每一个GraphicBuffer方针看做是一个个烘托完结的图层,对应1.1.2.1末节中Launcher的各个图层

更多关于GraphicBuffer的介绍请点击[这儿]

2. 什么是Fence机制

一个GraphicBuffer方针完好的生命周期大概是这样:

  • 烘托阶段:运用有绘图需求了,由GPU分配一块内存给运用,运用调用GPU履行绘图,此刻运用者是GPU
  • 组成阶段:GPU烘托完结后将图层传递给sf进程,sf进程决议由谁来组成,hwc或者GPU

    • 假如运用GPU组成,那么此刻buffer的运用者依旧是GPU
    • 假如运用hwc组成,那么此刻buffer的运用者是hwc
  • 显现阶段:一切的buffer在此阶段的运用者都是hwc,由于hwc操控着显现芯片

从生命周期可以看出GraphicBuffer方针在流转的进程中,会被GPUCPUDPU三个不同的硬件拜访

假如同一块内存可以被多个硬件设备拜访,就需求一个同步机制

在Android图形体系中,Fence机制便是用来确保跨硬件拜访时的数据安全

Fence的底层逻辑可以参阅Java的synchronized互斥锁机制,咱们也可以把Fence了解为一把硬件的互斥锁

每个需求拜访GraphicBuffer的人物,在运用前都要检查这把锁是否signaled了才能进行安全的操作,不然就要等候

  • active状况,该GraphicBuffer正在被占用
  • signaled状况,标明不再操控buffer

更多关于Fence同步机制的介绍请点击[这儿]

3. 什么是Gralloc

移动设备的内部空间寸土寸金,不太或许像PC相同给GPU独自配个显存,因而移动端的”图形内存”都被分配在运转内存

为了防止图形内存被乱用,Android笼统出Gralloc模块,规定了一切图形内存的恳求与释放都需求经过该模块来操作,以此来标准图形内存的运用

更多关于Gralloc机制的介绍请点击[这儿]

留意:Fence机制和Gralloc机制并不属于libui组件库,把它俩放到libui库展现是由于GraphicBuffer需求它们

libgui组件库

俗话说,越挨近上层事务规划上就越杂乱

libgui库虽说还没到事务层,但它里边的组件大多是对GraphicBuffer方针的封装,以满足不同的事务需求

1. 什么是BufferQueue

在Android 4.1(Project Butter)中引进了BufferQueue,它是黄油方案中“Triple Buffer”的履行者

黄油方案的重点是“Drawing with VSync”,也便是把VSync信号同步给APP进程

这样做的目的是将烘托和组成分红两步来履行,减少一个VSync周期内的使命量,以下降丢帧发生的概率

回到本章节的主题:BufferQueue

从称号就可以看出来,它是一个封装了GraphicBuffer的行列,BufferQueue对外供给了GraphicBuffer方针出列/入列的接口

别的,BufferQueue还为每个GraphicBuffer方针包装了几种不同的状况,它们别离是:

  • FREE:搁置状况,任何进程都可以获取该buffer进行操作,一般表明为APP进程可以恳求运用的内存
  • DEQUEUED:出列状况,一般是APP进程在绘图,运用者是GPU
  • QUEUED:入列状况,表明APP绘图现已完结,等候从行列取出履行下一步组成,没有运用者
  • ACQUIRED:确认状况,一般表明sf进程从行列取出,正在做组成作业,此刻运用者或许是hwc也有或许是GPU
  • SHARED:同享状况,7.0版别参加的新状况,没找到相关介绍材料,组成作业完结今后同享给录屏软件?

规划上,BufferQueue运用了[生产者/顾客形式],绝大多数的状况下,APP作为GraphicBuffer的生产者,sf进程作为GraphicBuffer的顾客,它们俩一同操作一个buffer行列

简略描绘一下生产者顾客操作行列时状况转换的进程:

生产者:APP进程

1、producer->dequeueBuffer()

从行列取出一个状况为“FREE”的buffer,此刻该buffer状况改变为:FREE->DEQUEUED

2、producer->queueBuffer()

将烘托完结的buffer入列,此刻该buffer状况改变为:DEQUEUED->QUEUED

顾客:sf进程

1、consumer->acquireBuffer()

从行列中取出一个状况为“QUEUED”的烘托完的buffer预备去组成送显,此刻该buffer的状况改变为:QUEUED->ACQUIRED

2、consumer->releaseBuffer()

buffer内容现已显现过了,可以从头入列给APP运用了,此刻该buffer的状况改变为:ACQUIRED->FREE

每个Buffer的终身,便是在不断地循环FREE->DEQUEUED->QUEUED->ACQUIRED->FREE这个进程,这中间有任何一个环节呈现推迟,反应到屏幕上便是运用呈现了卡顿

更多关于BufferQueue的介绍请点击[这儿]

阐明:BufferQueue中心代码由BufferQueueCore、BufferQueueProducer、BufferQueueConsumer这3个类组成

为了防止引进过多的人物导致文章的阅览体验下降,在后续的章节中,我会将这几个类统称为BufferQueue,包括接下来会呈现的Surface、EventThread都是如此,只保存骨干

2. 什么是Surface

在介绍libui库的时,咱们说到了GraphicBuffer是整个图形体系的中心

但关于开发者来说,特别是运用开发者,Surface才是咱们看得见摸得着的中心类,运用中一切的绘图操作终究都是在Surface中履行的

Surface作为图画的生产者,持有BufferQueue的引证,而且封装了出列和入列两个办法

接下来咱们经过一个比方来了解下日常开发中是怎样调用到BufferQueue的:

咱们都知道Android支撑2D绘图,API运用的是[Canvas],也支撑3D绘图,API运用的是[OpenGL ES]

咱们以2D绘图的流程来举例:

1、需求显现图形时,首先创立一个Surface方针

2、调用Surface#lockCanvas()获取Canvas方针

3、调用Canvas的draw最初的函数履行一系列的绘图操作

4、调用Surface#unlockCanvasAndPost()将制作完结的图层提交,等候下一步组成显现

第二步的[lockCanvas()]办法回来了Canvas方针供开发者绘图运用,其内部就调用了BufferQueue#dequeueBuffer()恳求一块图形buffer,后续一切的绘图成果都会写入这块内存中

第四步的[unlockCanvasAndPost()]办法内部调用了BufferQueue#queueBuffer()办法将制作完结的Buffer入列,等候sf进程鄙人一次同步信号周期组成并完结送显

上面是运用Java开发的制作流程,Surface除了给Java层供给绘图接口外,它仍是ANativeWindow的完结类

ANativeWindow是用于供给给C/C++层开发的接口,和运用开发关系不大,点击[这儿]了解一下它们之间的差异即可

简略来说,ANativeWindowSurface相同,都封装了Buffer的出列和入列办法

3. 什么是DisplayEventReceiver

DisplayEventReceiver方针看起来有点面生,但说到Choreographer我信任大部分读者应该都知道是干什么的

DisplayEventReceiverChoreographer都是在黄油方案参加的新成员,它俩是一一对应的关系(DisplayEventReceiver是Choreographer中的一个成员变量)

简略来说,DisplayEventReceiverChoreographer方针具有了感知VSync信号的才能,了解即可

关于DisplayEventReceiver更多细节请点击这儿

3、小结

呼~

总算把硬件驱动和Google组件库介绍完了,用一张图来总结本章节内容:

android_graphic_v3_static_overview.jpg

图片来历:自己画的

最底层的硬件驱动由OEM厂商供给,也便是高通、ARM这些SOC厂商

假如设备中没有GPU/DPU这些硬件也没关系,Google为一切的驱动(包括HAL层)都供给了默认完结,也便是CPU完结,这也是为什么虚拟机能运转的原因

再往上是Google供给的组件库,里边都是一些常用的类,其间大部分红员咱们都仍是要了解它们各自是做什么的,由于只要把这些根底概念了解清楚,才能在后续阅览源码的进程中做到有的放矢

好了,Android图形体系的静态部分聊完了,接下来咱们进入动态部分的内容

二、恳求VSync信号

厂商驱动库和Google组件库作为Android图形体系的柱石,为整个图形体系供给了强有力的支撑

在Android图形体系的动态部分,SurfaceFlinger作为体系的中心进程,在整个图形体系中起承上启下的作用

为图形体系服务的别的还有SystemServer进程,它和SurfaceFlinger进程一同支撑着整个体系的运转,其间:

  • SurfaceFlinger进程担任承受来自APP进程的图形数据,调用hwc进行组成并完结终究的送显
  • SystemServer进程担任办理有哪些APP进程可以进行绘图操作以及各个图层的优先级

SurfaceFlinger进程和SystemServer进程都是在VSync信号的唆使下进行作业,在接下来的章节中咱们将会剖析这两大体系进程别离在VSync信号到来时做了哪些作业

不过,在此之前,咱们需求先知道它们是怎样恳求VSync信号的?

全文基于Android 7.1.2版别

1、发动surface_flinger进程

Android 7.0今后对init.rc脚本进行了重构,sf进程的发动从init.rc文件装备到了surfaceflinger.rc文件,依旧由init进程拉起

先来看main_surfaceflinger.cpp的发动函数:

/frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp
int main(int, char**) {
    //创立sf方针
    sp<SurfaceFlinger> flinger = new SurfaceFlinger();
    //调用init办法进行初始化
    flinger->init();
    //注册sf服务到servicemanager
    sp<IServiceManager> sm(defaultServiceManager());
    sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false);
    //注册gpu服务到servicemanager
    sp<GpuService> gpuservice = new GpuService();
    sm->addService(String16(GpuService::SERVICE_NAME), gpuservice, false);
    //调用run办法,进入休眠
    flinger->run();
    return 0;
}

main_surfaceflinger的进口函数的首要做了3件事:

  1. 创立flinger方针并调用init()办法履行初始化作业
  2. 注册sf服务和gpu服务到servicemanager
  3. 调用run()办法进入休眠

首要的作业是在flinger方针中的init()函数中完结的

咱们持续向下跟SurfaceFlinger#init()函数:

/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::onFirstRef()
{
    mEventQueue.init(this);
}
void SurfaceFlinger::init() {
    {
        sp<VSyncSource> VSyncSrc = new DispSyncSource(&mPrimaryDispSync,
                VSyncPhaseOffsetNs, true, "app");
        mEventThread = new EventThread(VSyncSrc, *this);
        sp<VSyncSource> sfVSyncSrc = new DispSyncSource(&mPrimaryDispSync,
                sfVSyncPhaseOffsetNs, true, "sf");
        mSFEventThread = new EventThread(sfVSyncSrc, *this);
        mEventQueue.setEventThread(mSFEventThread);
    }
    mHwc = new HWComposer(this);
    mHwc->setEventHandler(static_cast<HWComposer::EventHandler*>(this));
    mEventControlThread = new EventControlThread(this);
    mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY);
}

init()函数初始化流程稍微有点长,咱们一步步拆开来看

初始化音讯行列

//运用RefBase初次引证机制来做一些初始化作业,这儿是初始化音讯机制
//音讯行列在sf进程中总共供给两个功用
//1. 履行sf进程恳求VSync的作业
//2. VSync-sf信号到来后,履行组成作业
void SurfaceFlinger::onFirstRef()
{
    mEventQueue.init(this);
}

在SurfaceFlinger中,运用了RefBase初次引证机制来做一些初始化作业,这儿是初始化音讯行列

sf进程中的音讯行列总共担任处理两种类型的音讯:

  • INVALIDATE:处理Layer特点改变以及buffer的更新
  • REFRESH:监听到VSync信号,履行组成作业

在接下来的文章剖析中咱们会发现,sf进程的终身都将围绕着这两件事打开

发动作业分发线程

/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
//初始化-只截取了和VSync信号和图形组成有关的部分代码
void SurfaceFlinger::init() {
    {
        // start the EventThread
        //发动作业分发线程,供给给APP进程注册作业回调
        //VSyncPhaseOffsetNs用来操控APP进程的偏移量
        sp<VSyncSource> VSyncSrc = new DispSyncSource(&mPrimaryDispSync,
                VSyncPhaseOffsetNs, true, "app");
        mEventThread = new EventThread(VSyncSrc, *this);
	//又发动作业分发线程,供给给sf进程注册作业回调
      	//sfVSyncPhaseOffsetNs用来操控sf进程的偏移量
        sp<VSyncSource> sfVSyncSrc = new DispSyncSource(&mPrimaryDispSync,
                sfVSyncPhaseOffsetNs, true, "sf");
        mSFEventThread = new EventThread(sfVSyncSrc, *this);
        mEventQueue.setEventThread(mSFEventThread);
    }
    //用于操控硬件VSync开关状况
    mEventControlThread = new EventControlThread(this);
    mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY);
}

init()函数中第二步是发动作业分发线程

总共发动了3个线程,其间:

  • mEventThread用于给APP进程供给VSync信号监听,一般称为VSYNC-app
  • mSFEventThread用于给sf进供给VSync信号监听,一般称为VSYNC-sf
  • mEventControlThread是总开关,用于操控硬件VSync信号的敞开与封闭

1、2线程别离是“APP进程”和“sf进程的”VSync作业分发线程

把APP进程和sf进程的VSync分隔办理的优点是:下降操控延时

什么是“下降操控延时”?

咱们先来看看正常的显现流程:

VSync1:APP进程开端烘托,烘托完结后入列等候组成

VSync2:sf查找一切烘托完结的图层,调用hwc组成,组成完结调用drm/fb显现结构送显,等候显现

VSync3:为了防止画面撕裂,显现结构相同等候笔直同步信号到来时才切换framebuffer,此刻用户看到更新的画面

一幅画面最起码要经过2个VSync周期(烘托、组成),在第3个VSync信号到来后才能展现给用户

假如是60HZ的屏幕,用户从按下按钮到到看到画面更新,最快要16.67ms*2 = 33.34ms

现在假设有这么个状况:我自己造了个硬件十分十分牛逼,再杂乱的画面烘托也只需1ms,组成也只需1ms

体系层面有没有一种机制能让用户更快的看到画面更新呢?

VSync offset:有!

1. VSync offset

回头来看init()函数,在创立“APP进程”和“sf进程”的DispSyncSource方针时,别离传入了VSyncPhaseOffsetNssfVSyncPhaseOffsetNs两个变量

void SurfaceFlinger::init() {
  //VSyncPhaseOffsetNs - i'm here
  sp<VSyncSource> VSyncSrc = new DispSyncSource(&mPrimaryDispSync,
             VSyncPhaseOffsetNs, true, "app");
  //sfVSyncPhaseOffsetNs
  sp<VSyncSource> sfVSyncSrc = new DispSyncSource(&mPrimaryDispSync,
             sfVSyncPhaseOffsetNs, true, "sf");
}
  • VSyncPhaseOffsetNs用来操控APP进程的偏移量

  • sfVSyncPhaseOffsetNs用来操控sf进程的偏移量

咱们知道硬件笔直同步信号的发送周期是固定的

已然咱们都在自己的进程里等候着VSync信号的到来,然后各司其职做自己的作业

那咱们经过更改偏移量的办法把“APP进程”和“sf进程”接纳到VSync信号的时刻错开

就可以完结:在一个硬件VSync信号周期内完结“烘托”和“组成”两件事,具体方案如下:

  • VSyncPhaseOffsetNs = 0,硬件VSync发生后,直接转发给app进程,让它开端制作
  • sfVSyncPhaseOffsetNs ≥1,硬件VSync发生后,推迟几毫秒再转发给sf进程,由于app现已烘托完结,sf组成刚刚烘托的图层

好了,在一个硬件VSync周期16ms)内“烘托”和“组成”的作业都现已完结了,而且由于GPU功用过于牛逼,间隔下次硬件VSync信号发送乃至还有14ms~

等下一次硬件VSync信号到来时,显现结构完结画面切换

和之前的方案比,相同是60HZ的屏幕

用户从按下按钮到到看到画面更新,只需求等候1个VSync信号周期,也便是16.67ms

2. DispSync模型

还记得init()函数中发动的第3个线程吗?称号叫:mEventControlThread

/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
class SurfaceFlinger {
    void SurfaceFlinger::init() {
        //用于操控硬件VSync开关状况
        mEventControlThread = new EventControlThread(this);
        mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY);
    }
    //接纳来自hwc的硬件VSync信号
    void SurfaceFlinger::onVSyncReceived(int32_t type, nsecs_t timestamp) {
        bool needsHwVSync = false;
        needsHwVSync = mPrimaryDispSync.addResyncSample(timestamp);
        if (needsHwVSync) enableHardwareVSync();
        else disableHardwareVSync(false);
    }
    //启用硬件VSync信号
    void SurfaceFlinger::enableHardwareVSync() {
        mEventControlThread->setVSyncEnabled(true);
    }
    //封闭硬件VSync信号
    void SurfaceFlinger::disableHardwareVSync(bool makeUnavailable) {
       mEventControlThread->setVSyncEnabled(false);
    }
}

mEventControlThread线程初始化之后被DispSync持有,用来启用和封闭硬件VSync的功用

sf进程注册完VSync的回调今后,VSync信号发生时,onVSyncReceived()将会收到回调

可是

sf进程并没有马上进行VSync信号分发,而是转交给DispSync去处理

在Android图形体系中,不论是硬件发生仍是软件模仿的VSync信号,终究都交由DispSync来办理

VSync offset可以操控偏移量的背面便是DispSync模型

DispSync操控着这个体系的VSync信号出口,除了调整偏移量外,它的内部还有个猜测机制

当承受到的硬件VSync信号量足够大时,DispSync会经过mEventControlThread封闭硬件VSync开关,经过猜测的成果模仿VSync信号的发生,发送到app进程和sf进程

ps:我个人对DispSync模型依然有疑问,这块了解的或许不太对,所以不敢妄下定论,建议阅览以下几篇文章进行学习:

《Analyze AOSP VSync model》、《DispSync解析》、《Android DispSync 详解》、《Android R VSync相关梳理》、《Android SurfaceFlinger SW VSync模型》

初始化HWComposer

/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::init() {
    //初始化HWC方针,加载hwcomposer.so的动作在HWComposer的初始化函数中
    mHwc = new HWComposer(this);
    //将自己注册到hwc的回调函数中,其内部别离调用registerHotplugCallback、registerRefreshCallback、registerVSyncCallback三个回调办法
    mHwc->setEventHandler(static_cast<HWComposer::EventHandler*>(this));
}

前面咱们说过sf进程的终身,便是在做“恳求VSync”和“履行组成作业”这两件事

而HWComposer便是完结这两件事的要害,不论是承受硬件的VSync信号,仍是完结图层组成作业,都和它有关

所以,HWComposer方针可以说是sf进程中的头号中心人物!

HWComposer在sf进程初始化阶段总共做了两件事:

  1. 创立HWC方针,保存到mHwc成员变量

    留意,这儿创立的是标准的HWComposer方针,在HWComposer构造函数中,调用了内部的[loadHwcModule()]办法来加载厂测供给的so库

  2. 注册VSync回调

    HWC方针创立完今后,第二步就调用了setEventHandler将自己注册到VSync信号监听

    假如VSync信号发生改变,终究会调用到sf进程的[onVSyncReceived()]办法

进入睡觉 等候唤醒

/frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp
int main(int, char**) {
    flinger->run();
}  
/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp  
void SurfaceFlinger::run() {
    do {
        waitForEvent();
    } while (true);
}

在完结一切初始化作业后,sf进程进入睡觉状况,等候音讯唤醒

总结一下,sf进程在图形处理相关方面总共做了两件事:

  1. 发动作业分发线程,内部由DispSync模型完结
  2. 初始化HWC方针,加载so库,注册VSync回调

2、发动system_server进程

[system_server进程中运转着AMS、WMS等常见服务,这些服务都是由java代码完结的,需求一个Java的运转环境

所以SystemServer进程必需求比及Zygote进程创立完DVM/ART虚拟机今后,再由Zygote进程fork而来:

/frameworks/base/services/java/com/android/server/SystemServer.java
class SystemServer {
    public static void main(String[] args) {
        new SystemServer().run();
    }
    private void run() {
      	startBootstrapServices();
    	startOtherServices();
    }

Zygote进程是怎样发动并拉起system_server进程这儿不关怀,咱们来重视在SystemServer的run()函数中发动了两个服务:AMS和WMS

初始化ActivityManagerService

ActivityManagerService是Android体系最为中心的服务之一,担任组件(首要是Activity)的发动、切换、调度作业,来看AMS的发动流程

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
class ActivityManagerService {
    //将自己注册到servicemanager中去
    public void setSystemProcess() {
        ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
    }
    //发动launcher桌面
    public void systemReady() {
        //aosp版别不同代码也不同,在7.0中终究调用startHomeActivityLocked()办法唤起launcher
    }
}

AMS在SystemServer进程发动进程中做了两件事:

  1. 将自己注册到servicemanager中去
  2. 发动launcher桌面

在Android图形体系中,每个Activity页面都可以看做是一个Window方针,所以Activity的生命周期也会影响Window方针的状况

咱们来考虑一个问题:当咱们发动一个透明布景的Activity页面或者是Dialog主题的Activity页面,原先的Activity会不会履行onStop()回调?

答案是:不会

假如AMS触发了Activity#onStop()回调办法,那么一同也会调用WindowManagerGlobal#setStoppedState()来告诉Window方针,此刻Window会中止制作

这并不是用户期望看到的成果

初始化WindowManagerService

WindowManagerService初始化作业在AMS之后

WMS的发动函数十分简略,在SystemServer中创立完结今后就直接注册到了servicemanager

/frameworks/base/services/java/com/android/server/SystemServer.java
class SystemServer {
    //发动WMS
    private void startOtherServices() {
      	WindowManagerService wm = WindowManagerService.main();
      	//将wms注册到servicemanager
      	ServiceManager.addService(Context.WINDOW_SERVICE, wm);
    }
}
/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
class WindowManagerService {
    public static WindowManagerService main(){
        return new WindowManagerService();
    }
}

WMS发动完结今后,最重要的使命便是等候处理来自各个进程创立/更新/毁掉Window的作业

在运用开发中,除了Activity对应一个Window窗口外,每个ToastDialog也都是一个Window窗口

Android中将窗口的类型分为三种:

  • Application Window(运用窗口):对应的是Activity
  • Sub Window(子窗口):对应Dialog,特点是必需求依靠父窗口才能显现
  • System Window(体系窗口):对应Toast、视频通话小窗口、视频小窗播放等

Looper.loop()

将一系列服务发动成功后,SystemServer调用Looper#loop()办法进入循环,确保system_server进程不退出

/frameworks/base/services/java/com/android/server/SystemServer.java
class SystemServer {
    private void run() {
      	// Loop forever.
        Looper.loop();
    }
}

3、发动app进程

SystemServer进程发动完毕后,接下来就该轮到APP进程了

APP进程和SystemServer进程相同,都是从Zygote进程fork而来

发动APP进程的代码稍微有些长,有时刻的同学建议点击[这儿]打开源码对照着看作用更佳

整个发动进程大致可以分为三步:

  1. 创立Activity并调用setContentView()绑定视图
  2. 恳求VSync信号
  3. 进入睡觉 等候VSync信号唤醒

咱们先来看第一步:创立Activity并调用setContentView()绑定视图

创立Activity

在Android开发中,首要使命肯定是设置视图,而设置视图不外乎于两种办法:xml文件和Java编码

不论运用哪种办法,都需求调用setContentView()办法将视图绑定到Activity傍边

然后再经过一系列的办法调用,终究将会履行到ViewRootImpl.setView()办法将视图同步给WMS

创立Activity的进程中要依赖三个要害的方针:Window、DecorView、ViewRootImpl

咱们可以依照这三个方针把创立Activity也分红了三个阶段:

1、Window方针的创立

2、DecorView的创立

3、ViewRootImpl的创立

1. Window的创立

/frameworks/base/core/java/android/app/ActivityThread.java
class ActivityThread {
    //zygote进程fork成功后调用进口函数
    void main(){
        attach();//attach办法和ams树立连接,供给给ams操控四大组件的句柄
    }
    //分两步解说更容易了解
    //1. 不论是从桌面点击图标进入仍是adb指令发动,终究都交由ams发送发动恳求给zygote进程,接着zygote孵化出该APP进程调用main办法
    //2. APP进程发动将创立ApplicationThread方针,并发起IPC把此方针传递给ams,尔后四大组件相关回到都将有ApplicationThread方针担任,终究转发给H类履行
    void attach() {
        //获取ams署理并将ApplicationThread将给ams,这个方针今后将是ams的传声筒
        IActivityManager mgr = ActivityManagerNative.getDefault();
        mgr.attachApplication(new ApplicationThread());
    }
    //ApplicationThreadNative封装一系列的关于四大组件回调办法的跨进程通讯指令
    //ApplicationThread方针一切操作几乎都由AMS发起调用
    class ApplicationThread extends ApplicationThreadNative {
        void scheduleLaunchActivity(){
            handleMessage(LAUNCH_ACTIVITY);
        }
    }
    class H extends Handler {
        //转发来自ApplicationThread的音讯
        void handleMessage(Message msg) {
            case LAUNCH_ACTIVITY::handleLaunchActivity();
            case RESUME_ACTIVITY::handleResumeActivity();
        }
        //转发来自handleMessage的音讯
        void handleLaunchActivity(){
            performLaunchActivity();
        }
        //履行创立Activity方针并回调生命周期
        Activity performLaunchActivity(){
            Activity activity = new Activity();
            activity.attach();//回调attach
            return activity;
        }
    }
}
/frameworks/base/core/java/android/app/Activity.java
class Activity {
    Window mWindow;//Activity初次被创立调用attach()办法时同步创立,创立动作在Activity
    WindowManager  mWindowManager;//在attach办法中被创立
    //1. 创立PhoneWindow保存到变量mWindow,此刻的Window还没有View视图
    //2. 获取wms署理方针,塞到刚刚创立的window方针傍边,一同保存到本地mWindowManager变量
    void attach(Window window){
        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowManager(getSystemService(Context.WINDOW_SERVICE));
        mWindowManager = mWindow.getWindowManager();//获取WindowManager动作在Activity中,获取完结接着设置给自己的局部变量
    }
}
  1. ActivityThread#main()函数履行attach()办法履行初始化
  2. attach()创立ApplicationThread,并跨进程传递给AMS办理
  3. AMS经过ApplicationThread告诉ActivityThread创立Activity
  4. performLaunchActivity()办法中,创立Activity实例方针,并调用Activity#attach()办法
  5. Activity#attach()创立Window方针

ApplicationThread作为AMS操控手柄,承受到发动Activity的指令后会转发到H#handleMessage()办法,终究由ActivityThread#handleMessage()办法履行来创立Activity方针

Activity方针创立完结今后,紧接着就会调用Activity的attach()办法

在Activity的attach()办法完结的作业中,最重要的便是:创立了类型为PhoneWindow的Window实例方针

2. DecorView的创立

在ActivityThread中,调用完attach()办法后紧接着就会调用Activity#onCreate()办法

开发者一般会在onCreate()办法中调用setContentView()来设置视图文件

这就进入到第二个阶段:DecorView的创立

/frameworks/base/core/java/android/app/ActivityThread.java
class ActivityThread {
  //履行创立Activity方针并回调生命周期
  Activity performLaunchActivity(){
      Activity activity = new Activity();
      activity.attach();//回调attach
      activity.onCreate();//回调Activity
      return activity;
  }
}
/frameworks/base/core/java/android/app/Activity.java
class Activity {
    Window mWindow;//Activity初次被创立调用attach()办法时同步创立,创立动作在Activity
    WindowManager  mWindowManager;//在attach办法中被创立
    //第二阶段开端:加载视图文件并绑定到DecorView
    void setContentView(View view) {
        mWindow.setContentView(view);
    }
    //第二阶段现已完结,预备进入第三阶段
    void onContentChanged(){
    }
}
/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
class PhoneWindow extends Window {
    DecorView mDecor;
    ViewGroup mContentParent;
    //1. 创立DecorView方针
    //2. 将开发者设置的视图文件作为子View增加到mContentParent
    //3. 告诉Activity中onContentChanged办法
    void setContentView(View view) {
        mDecor = generateDecor();
        mContentParent = generateLayout();//看generateLayout办法的注释
        mContentParent.addView(view);//将开发者设置的视图增加为子View
        getCallback().onContentChanged();//回调Activity中onContentChanged()办法
    }
    //创立一个空的DecorView,也便是FrameLayout,里边啥也没有
    void generateDecor() {
        return new DecorView(this);
    }
    //1. 依据不同主题设置不同布局文件,加载该布局文件并设置成DecorView的子View
    //2. 回来子View中id为content的ViewGroup,一般仍是个FrameLayout
    void generateLayout() {
        //加载不同的theme主题的布局文件,比方咱们在xml中指定android:theme=@style/NoActionBar
        View root = inflater.inflate(layoutResource);
        //将上一步解析的视图作为根布局增加到DecorView,常见的比方笔直方向的LinearLayout,这样布局DecorView
        mDecor.addView(root);
        //找到用来装用户视图的ViewGroup,一般仍是个FrameLayout
        ViewGroup contentParent = mDecor.findViewById(R.id.content);
        return contentParent;
    }
}
  1. Activity履行onCreate()生命周期,调用setContentView()加载视图文件
  2. setContentView()办法内部调用了刚刚创立的Window方针的setContentView()办法,并将视图传递曩昔
  3. Window#setContentView()中,创立了顶级视图DecorView方针
  4. 为Activity寻找对应的主题文件,解析并增加为DecorView的子View,也便是mContentParent
  5. 将开发者指定的视图增加到mContentParent

咱们都知道DecorView是顶级View,而且它本身是个FrameLayout,所以在mWindow#setContentView()中第一步便是将DecorView方针创立出来

创立完DecorView后,接着会加载Activity运用的主题文件,而且将该主题作为子View增加到DecorView,这个子View便是mContentParent

有了mContentParent,最后才是将咱们设置的视图增加为mContentParent子View

稍微有点绕,简略来说DecorView作为一个FrameLayout,一般内部会再包括一个子View:mContentParent

mContentParent中,会有一个名为R.id.content的FrameLayout,这个里边才是包括咱们设置的视图

void setContentView(View view) {
    //<FrameLayout>//DecorView的根布局
    //  <LinearLayout>//开发者设置带有ActionBar的主题,留意,这儿的视图可变的,依据主题来挑选不同的视图
    //      <ActionBar/>
    //      <FrameLayout
    //      android:id="@android:id/content"/>//这个FrameLayout才是终究包括开发者在setContentView中设置的布局
    //  </LinearLayout>
    //</FrameLayout>
    mDecor = generateDecor();
    mContentParent = generateLayout();//看generateLayout办法的注释
    mContentParent.addView(view);//将开发者设置的视图增加为子View
    getCallback().onContentChanged();//回调Activity中onContentChanged()办法
}

把开发者设置的视图增加为子View的下一步是回调Activity中onContentChanged()办法

当咱们在Activity收到onContentChanged()回调的这一刻,阐明DecorView现已创立完结

3. ViewRootImpl的创立

第一阶段和第二阶段别离让咱们具有了一个Window方针和一个DecorView方针

Window方针中包括了DecorView方针,DecorView包括了咱们设置的视图文件

接下来的使命便是把该Window方针传递给WMS

和前两个阶段不同的是,第三阶段是在Activity的onResume()回调中被触发的

在ActivityThread告诉完onResume()的下一步调用了makeVisible()办法

makeVisible()办法中,将会调用WindowManager#addView(mDecor)将视图传递给WMS

/frameworks/base/core/java/android/app/ActivityThread.java
class ActivityThread {
  void handleResumeActivity(){
      activity.makeVisible();//开端履行第三个阶段
  }
}
/frameworks/base/core/java/android/app/Activity.java
class Activity {
    //第三阶段开端:将视图传递给wms
    //makeVisible()在ActivityThread.H.handleResumeActivity()办法中被调用
    void makeVisible() {
        mWindowManager.addView(mDecor);
    }
}
//WindowManager的终究完结
/frameworks/base/core/java/android/view/WindowManagerImpl.java
public class WindowManagerImpl implements WindowManager {
    WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    void addView(View decorView) {
        mGlobal.addView(decorView);
    }
}
//大局单例,和WMS树立连接通讯,也是APP进程中,一切窗口实践的办理者
//内部mViews和mRoots变量保存着一切创立的Activity对应的View和ViewRootImpl
/frameworks/base/core/java/android/view/WindowManagerGlobal.java
class WindowManagerGlobal {
	List<View> mViews;
	List<ViewRootImpl> mRoots;
	void addView(View decorView){
		ViewRootImpl root = new ViewRootImpl(decorView);
		mViews.add(decorView);
		mRoots.add(root);
		// do this last because it fires off messages to start doing things
		root.setView(view);
	}
}
//对应一个Activity,关于视图的作业触发都在此
//1. Choreographer让它可以感知作业
//2. 保存DecorView让它可以在作业来暂时操控视图
//3. Surface让它具有绘图的才能
/frameworks/base/core/java/android/view/ViewRootImpl.java
class ViewRootImpl {
    Choreographer mChoreographer;//构造函数中被创立
    View mView;//保存DecorView
    final Surface mSurface = new Surface();
    public ViewRootImpl(){
        //可以感知VSync的原因可以追溯到libgui库中的DisplayEventReceiver类
        mChoreographer = Choreographer.getInstance();
    }
    //1. 恳求VSync信号,等候VSync降临后绘图
    //2. 创立binder署理方针传递给wms,尔后wms将经过此署理方针来告诉APP进程应该做什么事
    void setView(View decorView){
        mView = decorView;//将DecorView保存到ViewRootImpl的成员变量mView中
        requestLayout();//恳求VSync信号
        //经过binder向wms增加窗口
      	res = mWindowSession.addToDisplay();
    }
}
  1. Activity履行完onResume()今后,马上调用makeVisible()办法预备将视图传递给WMS
  2. makeVisible()办法中,调用WindowManager#addView()
  3. 经过层层调用,终究由进程单例WindowManagerGlobal方针履行addView()办法
  4. WindowManagerGlobal#addView()中,创立了本章节主角:ViewRootImpl
  5. ViewRootImpl经过binder向wms增加窗口

WindowManagerImpl是WindowManager的终究完结类,它会调用到WindowManagerGlobal#addView()办法

而WindowManagerGlobal是大局单例,每个进程有且只要一个,也便是说

一切的Activity对应的Window都由WindowManagerGlobal进行办理

因而,WindowManagerGlobal会有两个要害调集:mViewsmRoots

mViews是保管着的DecorView的调集,mRoots是保管着ViewRootImpl的调集

保管着ViewRootImpl的调集?

DecorView是每个Activity的跟视图,ViewRootImpl是什么?

//对应一个Activity,关于视图的作业触发都在此
//1. Choreographer让它可以感知作业
//2. 保存DecorView让它可以在作业来暂时操控视图
//3. Surface让它具有绘图的才能
/frameworks/base/core/java/android/view/ViewRootImpl.java
class ViewRootImpl {
    Choreographer mChoreographer;//构造函数中被创立
    View mView;//保存DecorView
    final Surface mSurface = new Surface();
    public ViewRootImpl(){
        //可以感知VSync的原因可以追溯到libgui库中的DisplayEventReceiver类
        mChoreographer = Choreographer.getInstance();
    }
}

看看ViewRootImpl类中的三个成员变量:

  • mView:DecorView让ViewRootImpl可以在作业来暂时操控视图
  • mSurface:让Activity具有绘图的才能
  • mChoreographer:让ViewRootImpl可以监听VSync信号

王炸!!!一个类几乎集齐了一切与视图相关的成员

咱们持续往下跟:

/frameworks/base/core/java/android/view/WindowManagerGlobal.java
class WindowManagerGlobal {
  List<ViewRootImpl> mRoots;
	void addView(View decorView){
            ViewRootImpl root = new ViewRootImpl(decorView);
            root.setView(view);
	}
}
/frameworks/base/core/java/android/view/ViewRootImpl.java
class ViewRootImpl {
    //1. 恳求VSync信号,等候VSync降临后绘图
    //2. 创立binder署理方针传递给wms,尔后wms将经过此署理方针来告诉APP进程应该做什么事
    void setView(View decorView){
        mView = decorView;//将DecorView保存到ViewRootImpl的成员变量mView中
        requestLayout();//恳求VSync信号
        //经过binder向wms增加窗口,这个办法背面又是一大堆办法调用,这儿不打开讨论
       res = mWindowSession.addToDisplay();
    }
}

WindowManagerGlobal创立了ViewRootImpl方针后,把它保存到本地调集mRoots

接着调用ViewRootImpl#setView()办法增加视图,setView()中又调用addToDisplay()办法经过binder向WMS增加窗口

至此,Window方针的创立、DecorView的创立和ViewRootImpl的创立这三大阶段悉数完结

咱们来总结一下在创立Activity阶段发生的作业

android_graphic_v3_create_activity.jpg

图片来历:自己画的

  • Activity中,创立PhoneWindow类型的Window方针
  • Window中,创立DecorView方针,绑定setContentView传入的视图文件
  • 调用WindowManager增加视图,预备把视图绑定到WMS
  • 创立ViewRootImpl作为终究的履行者,将视图增加WMS

恳求VSync信号

在视图相关的方针创立就绪后,APP开端正式恳求VSync信号

初次恳求VSync信号的动作是在ViewRootImpl#setView()办法中触发的

/frameworks/base/core/java/android/view/ViewRootImpl.java
class ViewRootImpl {
  void setView(View decorView){
    requestLayout();//恳求VSync信号
  }
  void requestLayout() {
      scheduleTraversals();
  }
  //恳求VSync,等候改写
  void scheduleTraversals() {
      //1. 发送同步屏障音讯的含义在于,确保VSync信号到来时,第一时刻履行ViewRootImpl.doTraversal()办法
      mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//创立一个同步屏障(详见Android音讯机制)
    	//2. 发送一条异步音讯,mTraversalRunnable是处理这条音讯的回调
      mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

setView()办法中调用了requestLayout()办法,接着调用scheduleTraversals()办法进行VSync信号的恳求

咱们在开发进程中调用的View#invalidate()/requestLayout()这两个办法,终究也都会调用到ViewRootImpl#scheduleTraversals()办法恳求VSync信号

咱们来看看scheduleTraversals()里边都做了些什么?

1、向主线程发送一个同步屏障音讯

2、经过Choreographer提交类型为CALLBACK_TRAVERSAL的Runnable

一是向主线程中的音讯行列发送一条同步音讯,发送同步屏障音讯的含义在于,确保VSync信号到来时,第一时刻履行ViewRootImpl#doTraversal()办法

二是提交了mTraversalRunnable,假如VSync信号到来后将会被履行,mTraversalRunnable内部封装的是doTraversal()办法用于履行制作流程

进入睡觉 等候唤醒

当恳求信号宣布今后,APP进程就进入等候状况,等候VSync发生

/frameworks/base/core/java/android/app/ActivityThread.java
class ActivityThread {
    //zygote进程fork成功后调用进口函数
    void main(){
        Looper.loop();
    }
}

Choreographer运转在主线程,也便是ActivityThread地点的线程,它们共用一个音讯行列

所以,VSync恳求终究阻塞在ActivityThread#main()办法中

三、处理VSync信号

万事俱备,只欠东风

APP进程等候着VSync信号的到来,SF进程则需求再等一段时刻

1、APP进程:制作三部曲

VSync以异步音讯的身份被发送到主线程音讯行列,该音讯处理者为Choreographer中的mHandler方针

经过处理今后,终究会调用到ViewRootImpl#doTraversal()办法履行制作,也便是View制作三部曲:measure、layout、draw

/frameworks/base/core/java/android/view/ViewRootImpl.java
class ViewRootImpl {
    View mView;//保存DecorView
    //开端制作
    void doTraversal() {
      mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);//移除同步屏障
      performTraversals();//View的制作起点
    }
    //绘图三部曲
    void performTraversals(){
        relayoutWindow();//向sf正式恳求surface,在进入绘图之前为APP进程预备好一块surface内存
        performMeasure();//Ask host how big it wants to be
        performLayout();
        performDraw();
    }
    void performMeasure(){
        mView.measure();
    }
    void performLayout(){
        mView.layout();
    }
    void performDraw(){
        mView.draw();
    }
    //创立surface
    int relayoutWindow(){
    }
}

在开端绘图之前,ViewRootImpl还做了两件事

一是移除同步屏障音讯,View的制作流程履行完毕后,让咱们开发者post的音讯得以履行

二是调用了relayoutWindow()办法,向sf正式恳求Surface,在进入绘图之前为APP进程预备好一块Surface内存

好了,接下来正式履行绘图流程

View#onMeasure()

performMeasure()办法中调用了View#measure()

ViewRootImpl成员变量mView保存的是DecorView方针,所以,measure()办法将会从跟视图一层层向下遍历

/frameworks/base/core/java/android/view/ViewRootImpl.java
class ViewRootImpl {
    View mView;//保存DecorView
    void performMeasure(){
        mView.measure();
    }
}

在遍历整个View树的进程中,会呈现屡次遍历才能确认View巨细的状况,特别关于ViewGoup来说,取决于丈量形式LayoutParams装备等参数

在本章节咱们需求了解:measure()办法是为了核算每一个View需求的巨细,measure()办法履行完结今后,各个View的巨细也都确认了

View的制作我计划另起一篇文章介绍,所以关于onMeasure()细节部分,比方测验形式touchMode以及焦点的处理等,这儿暂时不打开,之后的layoutdraw也都会一笔带过

View#onLayout()

performLayout()办法中调用了View#layout()

/frameworks/base/core/java/android/view/ViewRootImpl.java
class ViewRootImpl {
    View mView;//保存DecorView
    void performLayout(){
        mView.layout();
    }
}

上一末节measure履行完今后,确认了各个View的巨细

在本章节中,layout()办法是为了核算每一个View的方位,layout()办法履行完结今后,各个View的方位也都确认了

View#onDraw()

performDraw()办法中调用了View#draw()

draw是终究制作的阶段,在View体系中,一切的绘图操作都在draw阶段得到履行

/frameworks/base/core/java/android/view/ViewRootImpl.java
class ViewRootImpl {
    View mView;//保存DecorView
    void performDraw(){
        mView.draw();
    }
}

在履行View#draw()时,逻辑会和measurelayout进程有一点点不相同

measure进程和layout进程都是发生在CPU,draw不同,假如敞开硬件加速,那么draw的进程发生在GPU

而且,Android 5.0版别参加了RenderThread,敞开硬件加速后,在履行draw的进程中,Android将独自发动一个烘托线程来履行制作使命

不过关于开发者来说,这一切是无感的

/frameworks/base/core/java/android/view/View.java
class View {
    /**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
     *
     * This is where the View specializes rendering behavior based on layer type,
     * and hardware acceleration.
     */
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
        /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
         *
         * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
         * HW accelerated, it can't handle drawing RenderNodes.
         */
    }
}

关于RenderThread材料并不多,咱们只能从源码注释中看看能否得到一些有用的信息,如上文

假如启用硬件加速,此刻每一步绘图操作都将以RenderNode的形式保存到DisplayList

然后同步给RenderThread履行实践的烘托作业

假如封闭硬件加速,在onDraw()阶段View将会直接调用Canvas API画图,此刻的烘托作业履行在UI线程

启用烘托线程的优点网上却是有现成的材料,我给咱们念一念:

  1. 显现列表可以依据需求制作任意屡次,无需进一步与事务逻辑交互
  2. 可以对整个列表进行某些操作(如平移、缩放等),而无需从头宣布任何绘图操作
  3. 一旦知道一切的绘图操作,就可以对其进行优化:例如,一切文本在或许的状况下一次制作在一同
  4. 显现列表的处理或许会被分发到另一个线程中履行

来自于:《Understanding the RenderThread》

我个人了解下来独自启用烘托线程有两大优点:

一是去除重复绘图指令,比方屡次setText只保存最后一次

二是减轻UI线程压力,一旦绘图指令搜集完结,就可以同步给烘托线程履行,这样主线程剩下的时刻就可以用来履行其他的音讯,比方咱们post的音讯

不过,不论有没有敞开硬件加速,制作作业履行在哪个线程,整个烘托流程仍是要操控在一个VSync信号周期内完结,不然,该丢帧仍是会丢帧

特殊状况:SurfaceView

在游戏开发或其他需求展现3D图形时,多数状况是运用SurfaceView来制作

SurfaceView作为DecorView跟视图的成员之一,和一般View相同可以承受到Input作业(覆写onTouchEvent办法)

不过,SurfaceView有一般View没有的才能:“自主上帧”的权利

什么“自主上帧”呢?

咱们都知道SurfaceView具有独自的一块Surface,无论是运用Canvas进行2D开发仍是OpenGL ES进行3D开发,终究的制作成果都是保存在这块独自的Surface上

制作完结今后,SurfaceView可以调用unlockCanvasAndPost()/eglSwapBuffers()GraphicBuffer入列,提交给sf进程等候组成送显

SurfaceView让运用无需等候VSync信号的到来便可以履行制作作业,这是它和一般View最大的差异

2、SF进程:组成五部曲

无论运用运用哪种API进行图形开发,在制作流程完毕后,APP作为图层的生产者总是会调用BufferQueue#queueBuffer()办法将GraphicBuffer入列

一旦有新的图层参加行列,就意味着作为图层顾客的SF进程可以开端作业了

sf进程恳求VSync

由于VSync是注册制,因而,sf进程在作业之前必须先恳求VSync信号

/frameworks/native/services/surfaceflinger/Layer.cpp
class Layer {
    //当Surface发生改变今后,终究会调用onFrameAvailable()办法告诉sf,让sf恳求下一次VSync
    //这儿需求留意,VSync信号是EventThread来分发的,APP和sf各自办理自己是否需求恳求下一次VSync信号
    void Layer::onFrameAvailable() {
        mFlinger->signalLayerUpdate();
    }
}
/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
class SurfaceFlinger {
    //queue内部调用了恳求下一次VSync
    void SurfaceFlinger::signalLayerUpdate() {
        mEventQueue.invalidate();
    }
}
/frameworks/native/services/surfaceflinger/MessageQueue.cpp
class MessageQueue {
    //终究在MessageQueue类中履行了恳求VSync信号的操作
    void MessageQueue::invalidate() {
        mEvents->requestNextVSync();
    }
}
  1. 图层有改变,Layer收到onFrameAvilable()回调
  2. onFrameAvilable()调用SurfaceFlinger的signalLayerUpdate()办法
  3. 最后由MessageQueue调用requestNextVSync()发起VSync恳求

当APP端的Surface发生改变今后,Layer的onFrameAvailable()办法会被调用,经过层层转发,终究由MessageQueue#requestNextVSync()履行VSync信号的恳求

Layer这个类之前好像没有呈现过,简略介绍一下:

APP进程中的每一个Surface方针,对应SF进程傍边的一个Layer方针,它俩同享一个BufferQueue

Surface作为图层的生产者,封装了出列入列的操作

Layer作为图层的顾客,封装了获取烘托图层和释放图层的操作

sf进程处理VSync

sf进程运用MessageQueue类履行了恳求VSync信号的动作,所以,VSync信号到来后的处理相同也是在MessageQueue类中

/frameworks/native/services/surfaceflinger/MessageQueue.cpp
class MessageQueue {
    //承受来自DisplayEventReceiver的VSync信号
    int MessageQueue::eventReceiver(int /*fd*/, int /*events*/) {
        mHandler->dispatchInvalidate();
        return 1;
    }
    //收到VSync信号后,向sf进程中发送类型为"INVALIDATE"的音讯
    void MessageQueue::Handler::dispatchInvalidate() {
        mQueue.mLooper->sendMessage(this, Message(MessageQueue::INVALIDATE));
    }
    //sf进程判别需求何晨时调用该办法,用于向sf发送组成音讯
    void MessageQueue::refresh() {
        mHandler->dispatchRefresh();
    }
    //给sf发送类型为REFRESH的音讯,sf收到今后将会开端履行组成使命
    void MessageQueue::Handler::dispatchRefresh() {
        mQueue.mLooper->sendMessage(this, Message(MessageQueue::REFRESH));
    }
}
/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
class SurfaceFlinger {
    void SurfaceFlinger::onMessageReceived(){
        //接纳到VSync信号后,判别图层是否需求组成
        case MessageQueue::INVALIDATE: {
            bool refreshNeeded = false;
            refreshNeeded = handleMessageTransaction();
            refreshNeeded |= handleMessageInvalidate();
            //假如需求组成,告诉MessageQueue发送一条REFRESH类型的音讯
            if(refreshNeeded) signalRefresh();
        }
        //将会履行终究的组成操作
        case MessageQueue::REFRESH: {
            handleMessageRefresh();//组成并输出到屏幕
        }
    }
    //调用mEventQueue给sf自己发送一条Refresh类型的音讯
    void SurfaceFlinger::signalRefresh() {
        mEventQueue.refresh();
    }
}
  1. MessageQueue#eventReceiver()收到VSync信号后发送INVALIDATE音讯给sf进程

  2. SurfaceFlinger##onMessageReceived()办法被触发

  3. 在case为INVALIDATE的办法中,调用handleMessageTransaction()handleMessageInvalidate()检查是否需求履行下一步组成

  4. 假如需求履行组成,经过MessageQueue发送一条类型为REFRESH的音讯给自己,终究会履行到自己的SurfaceFlinger#handleMessageRefresh()办法

sf进程履行组成

一旦调用到handleMessageRefresh()办法,意味着SF进程的组成作业正式开端

/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
class SurfaceFlinger {
    //组成五部曲
    void SurfaceFlinger::handleMessageRefresh(){
        //组成之前的与处理,检查是否有新的图层改变,假如有,履行恳求下一次VSync信号
        preComposition();
        //若Layer的方位/先后顺序/可见性发生改变,从头核算Layer的方针组成区域和先后顺序
        rebuildLayerStacks();
        //调用hwc的prepare办法询问每个图层是否支撑硬件组成
        setUpHWComposer();
        //当打开开发者选项中的“显现Surface改写”时,额定为发生改变的图层制作闪烁动画
        doDebugFlashRegions();
        //假如不支撑硬件组成,在该办法中会调用GPU组成,接着提交buffer
        doComposition();
        //调Layer的onPostComposition办法,首要用于调试,可以忽略
        postComposition(refreshStartTime);
    }
}

整个组成流程有五个过程,所以可以取名叫做“组成五部曲”

接下来咱们一同来看看“组成五部曲”都做了哪些作业:

1. preComposition()

第一步是预处理阶段,调用每个layer的onPreComposition()办法询问是否需求组成

/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
class SurfaceFlinger {
    void SurfaceFlinger::preComposition(){
        bool needExtraInvalidate = false;
        const LayerVector& layers(mDrawingState.layersSortedByZ);
        const size_t count = layers.size();
        for (size_t i=0 ; i<count ; i++) {
            //由于在调用组成之前现已核算过脏区域,假如有图层在核算今后参加了行列,那么在预处理阶段要再次恳求VSync信号
            if (layers[i]->onPreComposition()) {
                needExtraInvalidate = true;
            }
        }
        //存在未处理的layer,履行恳求下一次VSync信号,防止这段时刻内的帧数据丢掉了
        if (needExtraInvalidate) {
            signalLayerUpdate();
        }
    }
}

第一步履行完今后,依据needExtraInvalidate来确认是否有遗失的图层,假如有就再次恳求VSync信号,防止丢掉帧数据

2. rebuildLayerStacks()

第二步:核算各个Layer的方针组成区域和先后顺序

/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
class SurfaceFlinger {
    void SurfaceFlinger::rebuildLayerStacks(){
        //获取当时运用程序一切依照z-order排列的layer
        const LayerVector& layers(mDrawingState.layersSortedByZ);
        //遍历每一个显现屏
        for (size_t dpy=0 ; dpy<mDisplays.size() ; dpy++) {
            //z-order排列的layer
            hw->setVisibleLayersSortedByZ(layersSortedByZ);
            //显现屏巨细
            hw->undefinedRegion.set(bounds);
            //减去不透明区域
            hw->undefinedRegion.subtractSelf(tr.transform(opaqueRegion));
            //累加脏区域
            hw->dirtyRegion.orSelf(dirtyRegion);
        }
    }
}

第二步履行完今后,确认了每个图层的可见区域和跟其他图层发生堆叠部分的脏区域

3. setUpHWComposer()

第三步:更新HWComposer方针中图层方针列表以及图层特点

prepareFrame()办法中调用了HWComposer#prepare办法

在HWC的prepare()办法中,将会确认每一个图层运用哪种组成办法

/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
class SurfaceFlinger {
    void SurfaceFlinger::setUpHWComposer() {
        //prepareFrame办法中调用了HWComposer::prepare办法
      	//在HWC的prepare办法中,将会确认每一个图层运用哪种组成办法
        for (size_t displayId = 0; displayId < mDisplays.size(); ++displayId) {
            auto& displayDevice = mDisplays[displayId];
            if (!displayDevice->isDisplayOn()) {
                continue;
            }
            status_t result = displayDevice->prepareFrame(*mHwc);
        }
    }
}

第三步履行完今后,确认了每个图层的组成办法

4. doComposition()

第四步:履行真正的组成作业

/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
class SurfaceFlinger {
    void SurfaceFlinger::doComposition(){
        //遍历一切的DisplayDevice然后调用doDisplayComposition函数
        for (size_t dpy=0 ; dpy<mDisplays.size() ; dpy++) {
            const sp<DisplayDevice>& hw(mDisplays[dpy]);
            if (hw->isDisplayOn()) {
                //获得屏幕的脏区域,将脏区转换为该屏幕的座标空间
                const Region dirtyRegion(hw->getDirtyRegion(repaintEverything));
                //在此办法中将会调用到doComposeSurfaces()办法
                //在doComposeSurfaces办法中,将会为被标记为不支撑硬件组成的图层调用Layer#draw()办法运用OpenGL ES组成
                doDisplayComposition(hw, dirtyRegion);
            }
        }
        postFramebuffer();
    }
}

第四步履行完今后,完结了两件事:

  1. 将不支撑硬件组成的图层进行GPU组成

    doComposeSurfaces()办法中,将会为被标记为不支撑硬件组成的图层调用Layer#draw()方法运用OpenGL ES组成

  2. 调用postFramebuffer()进行送显

    postFramebuffer()办法会将GPU组成后的图层和需求HWC组成的图层一同打包提交给HWC

    HWC终究会调用DRM结构进行送显,当下一次硬件VSync信号发生时交流Framebuffer

5. postComposition()

第五步:送显之后的善后作业

在Android 7.0版别中,postComposition()办法用来履行更正DispSync模型,可以忽略

/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
class SurfaceFlinger {
    //第五步:更新DispSync机制,详情拜见
    void SurfaceFlinger::postComposition(){
        //更新DispSync,详情拜见DispSync模型一节
    }
}

ps:关于SF进程组成部分的源码不方便调试,所以这儿的定论没有得到验证不一定对,读者朋友可以去查找其他材料学习

3、小结

至此,APP制作作业和SF组成作业现已悉数完结,咱们来画张图总结一下本章节内容

android_graphic_v3_dynamic_overview.png

图片来历:自己画的

一个APP完好的显现流程大致分为三个阶段

  1. app-恳求

    APP页面元素一旦发生改变,调用invalidate()/requestLayout()办法恳求下一次VSync信号,此刻sf什么都不做

  2. app-VSync & sf-恳求

    app-VSync信号到来后,APP进程履行绘图三部曲,

    绘图流程完毕后,sf收到onFrameAvailable(),sf进程恳求VSync

  3. sf-VSync

    sf-VSync信号到来,sf进程履行组成五部曲,接着将成果提交给hwc

    等候下次硬件VSync信号发生,切换Framebuffer展现给用户

四、结语

本文把Android图形子体系分为两个部分:静态部分和动态部分

在静态部分中,硬件驱动和Google组件库为运用供给了绘图的才能

在动态部分中,介绍了VSync信号是怎样把体系打理的井井有条的,侧重剖析了各个进程是怎样恳求和处理VSync信号

有了这两部分的内容,咱们来测验回答一下文章的标题:当咱们点击“微信”这个运用后,它是怎样在屏幕上显现出来的?

  • Launcher进程拉起微信的默认发动页:SplashActivity

  • WeChatSplashActivity加载视图文件,AMS和WMS为Activity创立DecorView、Window、ViewRoot

  • 视图文件预备就绪,恳求VSync信号

  • app-VSync处理,履行制作三部曲,制作完毕今后,告诉sf进程

  • sf进程恳求VSync信号

  • sf-VSync处理,履行组成五部曲,提交到hwc

  • hw-VSync原始信号处理,显现结构履行交流图层数据,用户看到微信的发动图

  • 发动页完毕后跳转到文章列表页,用户可以操作页面

《Android图形体系体系篇》到这儿就悉数完毕了,本篇文章之所以可以顺畅发生,得益于各位长辈的忘我分享(见参阅材料)

期望本文可以起到抛砖引玉的作用,给各位读者朋友带来一点点协助

全文完

五、参阅材料

  • 《深化了解Android内核规划思想》- 林学森
  • 《深化了解Android 卷III》- 张大伟
  • 《Weishu’s Notes》- 田维术(太极/两仪作者)
  • 《Android显现系列》- 努比亚团队
  • 《Systrace系列》- 高爷
  • 《Android 10 Display System源码剖析》- ZHOUJINJIAN
  • 《DRM与BufferQueue系列》- 何小龙
  • 《Android图形显现系列》- 落日叹
  • 《王小二的Android站》- 王小二(TCL王总)
  • 《Silence.Slow.Simple专栏》- simowce
  • 《Android SurfaceFlinger 学习之路》- windrunnerlihuan
  • 《Android 12(S) 图画显现体系》- 二的次方
  • 深化了解Flutter的图形图画制作原理 – OPPO数智技术
  • Android功用优化:定性和定位Android图形功用问题 – 飞起来_飞过来