PS:最近工作事情太多,Android显示框架这一块都没有时间好好分析,其次是显示框架的内容实在是太多,自己看代码还好,但是要总结也不知道从哪里开始好。估计可能是理解还不够透彻吧,所以还是多看一些博客,自己也结合代码分析一下会好点。
前言
Android的Graphic系统是设计的很精妙,但是也很复杂。SurfaceFlinger也是Graphic系统中十分重要的组成部分,要从正面分析不是一件容易的事情。所以我们先从侧面窥视它的重要模块,积累起足够的模块基础,再统一分析,就会一目了然。
GUI框架
图形显示过程
一般应用开发都要将UI数据使用Activity这个载体去展示,典型的Activity显示流程为:
- startActivity启动Activity;
- 为Activity创建一个window(PhoneWindow),并在WindowManagerService中注册这个window;
- 切换到前台显示时,WindowManagerService会要求SurfaceFlinger为这个window创建一个surface用来绘图。SurfaceFlinger创建一个”layer”(surface)。(以想象一下C/S架构,SF对应Server,对应Layer;App对应Client,对应Surface),这个layer的核心即是一个BufferQueue,这时候app就可以在这个layer上render了;
- 将所有的layer进行合成,显示到屏幕上。
一般app而言,在任何屏幕上起码有三个layer:屏幕顶端的status bar,屏幕下面的navigation bar,还有就是app的UI部分。一些特殊情况下,app的layer可能多余或者少于3个,例如对全屏显示的app就没有status bar,而对launcher,还有个为了wallpaper显示的layer。status bar和navigation bar是由系统进行去render,因为不是普通app的组成部分嘛。而app的UI部分对应的layer当然是自己去render,所以就有了第4条中的所有layer进行“合成”。
GUI框架
SurfaceFlinger:每当用户程序刷新UI的时候,会中介BufferQueue申请一个buffer(dequeueBuffer),然后把UI的信息填入,丢给SurfaceFlinger,SurfaceFlinger通过计算多重计算合成visibleRegion之后,丢给openGL层处理,处理之后送到显示器display上显示。
根据整个Android系统的GUI设计理念,我们不难猜想到至少需要两种本地窗口:
- 面向管理者(SurfaceFlinger):既然SurfaceFlinger扮演了系统中所有UI界面的管理者,那么它无可厚非地需要直接或间接地持有“本地窗口”,这个窗口就是FramebufferNativeWindow
- 面向应用程序:这类窗口是Surface(这里和以前版本出入比较大,之前的版本本地窗口是SurfaceTextureClient)
第二种窗口是能直接显示在终端屏幕上的——它使用了帧缓冲区,而第一种Window实际上是从内存缓冲区分配的空间。当系统中存在多个应用程序时,这能保证它们都可以获得一个“本地窗口”,并且这些窗口最终也能显示到屏幕上——SurfaceFlinger会收集所有程序的显示需求,对它们做统一的图像混合操作。
Render过程
android提供了两种方式:Canvas ,OpenGL ES。这两种方式在我认为都应该算是标准,提供了一套固定的API,实现和平台无关。Java中类似于给你提供了一个interface接口,这个接口你可以用任何方式去实现,只要满足功能要求即可。不同的人可以有不同的实现方式,对10个数进行排序,A可以用冒泡法,B可以用选择法,最终结果只要保证把这10个数排好序了,唯一差别就是实现难度和效率问题。
Canvas
android提供了Canvas 2D API用来进行普通图形的绘制的,类似TextView这种应该都是用Canvas API来完成的。而Canvas这个”标准”的具体实现是由/external/skia库来完成的,真正干活的是skia。上层Canvas调用的API到下层其实封装了skia的实现。
OpenGL ES
OpenGL ES相关的API是为了3D图形的绘制而准备的。
android上有个EGL库,EGL和OpenGL ES是什么关系?代码在frameworks\native\opengl\libs\,我们查看frameworks\native\opengl\libs\Android.mk中:
1 2 3 4 5 6 7 8 9 |
...... LOCAL_SHARED_LIBRARIES += libcutils libutils liblog libGLES_trace #编译的库文件为/system/lib/libEGL.so LOCAL_MODULE:= libEGL LOCAL_LDFLAGS += -Wl,--exclude-libs=ALL LOCAL_SHARED_LIBRARIES += libdl # we need to access the private Bionic header <bionic_tls.h> LOCAL_C_INCLUDES += bionic/libc/private ...... |
SurfaceFlinger这个非常重要的系统服务依赖EGL库,包含了EGL头文件,使用了EGL中的函数,查看frameworks/native/services/surfaceflinger/Android.mk:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
...... LOCAL_SHARED_LIBRARIES := \ libcutils \ liblog \ libdl \ libhardware \ libutils \ libEGL \ libGLESv1_CM \ libGLESv2 \ libbinder \ libui \ libgui \ libpowermanager LOCAL_MODULE:= libsurfaceflinger include $(BUILD_SHARED_LIBRARY) ...... |
在SurfaceFlinger::init()中,调用了egl开头的函数,位于frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp中:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void SurfaceFlinger::init() { ALOGI( "SurfaceFlinger's main thread ready to run. " "Initializing graphics H/W..."); status_t err; Mutex::Autolock _l(mStateLock); // initialize EGL for the default display //#define EGL_DEFAULT_DISPLAY ((EGLNativeDisplayType)0) mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(mEGLDisplay, NULL, NULL); ...... } |
在eglGetDisplay()中调用了egl_init_drivers(),位于frameworks/native/opengl/libs/egl/eglApi.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
EGLDisplay eglGetDisplay(EGLNativeDisplayType display) { clearError(); uintptr_t index = reinterpret_cast<uintptr_t>(display); if (index >= NUM_DISPLAYS) { return setError(EGL_BAD_PARAMETER, EGL_NO_DISPLAY); } //egl_init_drivers函数载入OpenGL ES相关库 if (egl_init_drivers() == EGL_FALSE) { return setError(EGL_BAD_PARAMETER, EGL_NO_DISPLAY); } EGLDisplay dpy = egl_display_t::getFromNativeDisplay(display); return dpy; } |
关于egl_init_drivers()函数,载入OpenGL ES相关库,限于本篇只是简单介绍,所以不详细分析。相关文件如下:
- frameworks/native/opengl/libs/egl/Egl.cpp
- frameworks/native/opengl/libs/egl/Loader.cpp
代码也不多,主要做的事情:
首先在frameworks\native\opengl\libs\EGL\egl_entries.in中保存了EGL的相关API,函数都是以egl开头;在frameworks\native\opengl\libs\entries.in中保存了OpenGL ES相关API,函数都是以gl开头。函数egl_init_drivers()需要做的就是找到EGL和OpenGL ES本地的实现,然后对这些函数进行赋值。可以查看frameworks/native/opengl/libs/egl/Egl.cpp:
1 2 3 4 5 6 7 8 9 |
char const * const gl_names[] = { #include "../entries.in" NULL }; char const * const egl_names[] = { #include "egl_entries.in" NULL }; |
在/vendor/lib/egl或/system/lib/egl下(不是/system/lib/下),寻找libGLES.so,如果未找到,则寻找libGLES_*.so,上面两种库只要找到一个,则打开库,这个库里包含了EGL和OpenGL ES具体实现的库,然后“取出”库中具体的函数实现进行赋值。
但是较新版本的android已经不使用软件实现,即libagl,所以即使找到libGLES_android.so也不会去用。位于frameworks/native/opengl/libs/egl/Loader.cpp中load_driver函数实现:
1 2 3 4 5 6 7 8 9 10 |
void *Loader::load_driver(const char* kind, egl_connection_t* cnx, uint32_t mask) { ...... if (!strcmp(e->d_name, "libGLES_android.so")) { // always skip the software renderer continue; } ...... } |
如果libGLES.so和libGLES_.so都未找到,需要分别去加载/vendor/lib/egl或/system/lib/egl下libEGL.so, libGLESv1CM.so, libGLESv2.so这三个库或者libEGL.so, libGLESv1CM.so, libGLESv2_.so,然后分别对EGL、OpenGL ES V1、OpenGL ES V2的具体实现去赋值(那意思是libGLES_*.so中其实囊括了三个库所有的具体实现)。至此,就将OpenGL ES“标准”和“实现”挂钩了。
所以android中OpenGL ES的实现方式有2种:
- 一种是软件实现,用cpu去绘图,这就是所谓的agl(libGLES_android.so),代码路径在frameworks/native/opengl/libagl,即the software OpenGL ES library;
- 另一种是硬件厂商根据自己GPU提供的实现,一般都不开放源代码,就是上面介绍的需要去/vendor/lib/egl或/system/lib/egl找的几个库,但是只要把API 函数赋值上正确的实现函数即可。
此外由于OpenGL ES的实现是系统无关的,所以EGL库的另一个作用就是将OpenGL ES和本地窗口系统结合起来,举个例子好理解,如果你要画个纹理多边形,调用OpenGL ES接口,如果要把图形render到屏幕,需要调用EGL接口。例如在使用OpenGL ES前首先需要调用EGL的相关函数去搭建好OpenGL ES的本地环境等,EGL是android使用OpenGL ES API绘图的助手!
hardware-accelerated Canvas
从android 4.0开始,支持了硬件加速的Canvas,应该就是修改了Canvas的具体实现,不用skia了,而改调用EGL和OpenGL ES API了。
android的封装
那么我们平时写app时为何不用调用上面的Canvas和OpenGL ES,也能出现漂亮的UI呢?因为我们使用的都是android上层封装好的类,例如TextView就是用了Canvas,而GLSurfaceView就是使用了OpenGL ES,android已经帮我们做了大部分的工作。当然完全可以不用调用上层的Java类,而用c++/c去直接调用Canvas和OpenGL ES。
如何去合成
首先介绍个概念,hardware overlay,来自维基百科。
From Wikipedia, the free encyclopedia
In computing, hardware overlay, a type of video overlay, provides a method of rendering an image to a display screen with a dedicated memory buffer inside computer video hardware. The technique aims to improve the display of a fast-moving video image — such as a computer game, a DVD, or the signal from a TV card. Most video cards manufactured since about 1998 and most media players support hardware overlay.[1]The overlay is a dedicated buffer into which one app can render (typically video), without incurring the significant performance cost of checking for clipping and overlapping rendering by other apps. The framebuffer has hardware support for importing and rendering the buffer contents without going through the GPU.[citation needed]
hardware overlay 是提供一种机制,直接render到display screen的硬件内存中,提高显示效率吧。
而android对layer的合成主要包括2部分:在GPU中合成和在display的硬件中进行buffer的合成。
在GPU中进行合成,既是利用OpenGL ES进行合成,需要注意,画图的时候我们用了OpenGL ES,这里合成时也用了,功能真是强大,开始一直奇怪为何SurfaceFlinger也要去调用EGL的函数,原来是需要用OpenGL ES去合成layer;在display的硬件中进行合成,也就是hardware overlay机制。
Hardware Composer
那么android是如何使用这两种合成机制的呢?这里就是Hardware Composer的功劳。处理流程为:
- SurfaceFlinger给HWC提供layer list,询问如何处理这些layer;
- HWC将每个layer标记为overlay或者GLES composition,然后回馈给SurfaceFlinger;
- SurfaceFlinger需要去处理那些GLES的合成,而不用去管overlay的合成,最后将overlay的layer和GLES合成后的buffer发送给HWC处理。
借用google一张图说明,可以将上面讲的很多概念展现,很清晰。地址位于 https://source.android.com/devices/graphics/
在我认为使用overlay后,可以将SurfaceFlinger的工作减轻,即少一些GLES的合成,HWC承担了部分OpenGL ES 和GPU的工作, 从而减少了功耗。
注:如果屏幕上的画面基本不变化,这时候用GLES 合成的效率要高于overlay(overlay主要是为了render快速变化的图形等);android 4.4往上支持4个oveylay,如果要合成超过4个layer,系统就会对剩余的使用GLES合成,所以app的layer个数对手机的功耗影响挺大。
SurfaceFlinger和BufferQueue
生产模型
一个UI完全显示到diplay的过程,SurfaceFlinger扮演着重要的角色但是它的职责是“Flinger”,即把系统中所有应用程序的最终的“绘图结果”进行“混合”,然后统一显示到物理屏幕上,而其他方面比如各个程序的绘画过程,就由其他东西来担任了。这个光荣的任务自然而然地落在了BufferQueue的肩膀上,它是每个应用程序“一对一”的辅导老师,指导着UI程序的“画板申请”、“作画流程”等一系列细节。下面的图描述了这三者的关系:
虽说是三者的关系,但是他们所属的层却只有两个,app属于Java层,BufferQueue/SurfaceFlinger属于native层。也就是说BufferQueue也是隶属SurfaceFlinger,所有工作围绕SurfaceFlinger展开。
这里IGraphicBufferProducer就是app和BufferQueue重要桥梁,GraphicBufferProducer承担着单个应用进程中的UI显示需求,与BufferQueue打交道的就是它。它的工作流程如下:
BpGraphicBufferProducer是GraphicBufferProducer在客户端这边的代理对象,负责和SF交互,GraphicBufferProducer通过gbp(IGraphicBufferProducer类对象)向BufferQueue获取buffer,然后进行填充UI信息,当填充完毕会通知SF,SF知道后就对该Buffer进行下一步操作。典型的生产-消费者模式。
工作流程
接下来具体说明客户端(producer)和服务端SurfaceFlinger(consumer)工作的模式:
首先这里的buffer是共享缓冲区,故肯定会涉及到互斥锁,所以buffer的状态也会有多种,一般的buffer大致会经过FREE->DEQUEUED->QUEUED->ACQUIRED->FREE这个流程,如下图:
- BufferQueue:可以认为BufferQueue是一个服务中心,其它两个owner必须要通过它来管理buffer。比如说当producer想要获取一个buffer时,它不能越过BufferQueue直接与consumer进行联系,反之亦然。
- Producer:生产者就是“填充”buffer空间的人,通常情况下当然就是应用程序。因为应用程序不断地刷新UI,从而将产生的显示数据源源不断地写到buffer中。当Producer需要使用一块buffer时,它首先会向中介BufferQueue发起dequeue申请,然后才能对指定的缓冲区进行操作。这种情况下buffer就属于producer一个人的了,它可以对buffer进行任何必要的操作,而其它owner此刻绝不能擅自插手。当生产者认为一块buffer已经写入完成后,它进一步调用BufferQueue的queue。从字面上看这个函数是“入列”的意思,形象地表达了buffer此时的操作——把buffer归还到BufferQueue的队列中。一旦queue成功后,owner也就随之改变为BufferQueue了。
- Consumer:消费者是与生产者相对应的,它的操作同样受到BufferQueue的管控。当一块buffer已经就绪后,Consumer就可以开始工作了。这里需要特别留意的是,从各个对象所扮演的角色来看,BufferQueue是中介机构,属于服务提供方;Producer属于buffer内容的产出方,它对缓冲区的操作是一个“主动”的过程;反之,Consumer对buffer的处理则是“被动”的、“等待式”的——它必须要等到一块buffer填充完成后才能做工作。在这样的模型下,我们怎么保证Consumer可以及时的处理buffer呢?换句话说,当一块buffer数据ready后,应该怎么告知Consumer来操作呢?
仔细观察的话,可以看到BufferQueue里还同时提供了一个特别的类,名称为ProxyConsumerListener,其中的函数接口包括:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
classProxyConsumerListener : public BnConsumerListener { public: //省略构造函数 virtual void onFrameAvailable();/*当一块buffer可以被消费时,这个函数会被调用,特别注意此时没有共享锁的保护*/ virtual voidonBuffersReleased();/*BufferQueue通知consumer它已经释放其slot中的一个或多个 GraphicBuffer引用*/ private: wp<ConsumerListener>mConsumerListener; } |
这样子就很清楚了,当有一帧数据准备就绪后,BufferQueue就会调用onFrameAvailable()来通知Consumer进行消费。
消费模型
BufferQueue和SurfaceFlinger之间的通信模式如下:
也是有一对BpGraphicBufferConsumer/BnGraphicBufferConsumer支持他们之间的信息传输。
转载自:http://windrunnerlihuan.com/2017/04/27/Android-SurfaceFlinger-%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF-%E4%BA%8C-SurfaceFlinger%E6%A6%82%E8%BF%B0/