图形显示架构
显示框架
从APP启动到界面的显示,Android是如何将应用的画面显示出来的呢?了解Android内部的显示框架,对于我们理解Flutter这类跨平台技术的实现很有意义。
Android的图形显示架构是怎样的呢,简单说,APP负责画图(开发UI),系统服务负责控制图层,SurfaceFlinger负责混合图层并显示。
如果用画图来比喻,那么什么是画笔呢?
- Skia:CPU绘制2D图形(软件渲染)
- OpenGL: GPU绘制3D图形(硬件渲染)
有了画笔,还需要在画布上画画,那什么是画布呢?
FramebufferNativeWindow: Android4.4版本之前,它可用于SurfaceFlingerSurface: 在Android4.4版本之后,仅使用Surface
此外还需要混合图层:
- GPU: 使用OpenGL来合成图层
- HWC: 使用HWC来合成图层(Hardware Composer,硬件混合渲染器。首次在Android 3.0被引入。主要的目的是选择最高效的途径来合成混合图层。属于HAL层,它的实现依赖具体设备,通常由硬件厂家完成)
从应用到上屏显示,整个显示过程由三个进程一起配合完成:App,System Server和SurfaceFlinger

注意,在Android4.4之后版本,所有的图形都是绘制在Surface上,不管是应用程序画图还是SurfaceFlinger混合图层。应用程序可以通过Skia来绘制2D图形,也可以用OpenGL来绘制3D图形,SurfaceFlinger通过OpenGL来混合图形到指定的Surface上,然后这个Surface与其他没有被SurfaceFlinger合成的图层一起再次送往HWC进行合成。

SurfaceFlinger可以使用OpenGL ES合成Layer,这需要占用并消耗GPU资源。大多数GPU都没有针对图层合成进行优化,当SurfaceFlinger通过GPU合成图层时,应用程序无法使用GPU进行自己的渲染。而HWC通过硬件设备进行图层合成,可以减轻GPU的合成压力。具体流程,参见HWC文档的说明。
这里再以Activity显示的简单流程为例:
startActivity启动Activity;- 为
Activity创建一个window(PhoneWindow),并在WindowManagerService中注册这个window WindowManagerService会要求SurfaceFlinger为这个window创建一个surface用来绘图。SurfaceFlinger创建一个Surface。(SurfaceFlinger中Surface中以Layer来体现,即App中的一个Surface,对应SurfaceFlinger的一个Layer),这个Layer的核心即是一个BufferQueue,此时App页面就可以在这个Layer上渲染了- 将所有的
Layer进行合成,显示到屏幕上
一般而言,屏幕至少会有三个layer,屏幕顶端的status bar,屏幕下面的navigation bar(虚拟按键),App的UI部分。App的layer可能有多个。status bar和navigation bar是由系统进行渲染。而App的UI部分对应的layer 是由它自己去处理(通知SurfaceFlinger处理),最后再把这些layer合成 。

软件渲染与硬件渲染
从 Android 3.0 开始,支持硬件加速,到 4.0 时,默认开启硬件加速
硬件加速渲染与软件渲染整个流程差异较大,最核心就是通过 GPU 完成 Graphic Buffer 的内容绘制。硬件加速大大提高 Android 系统显示和刷新的速度,但是也存在一些问题:一方面加大内存消耗,OpenGL API 和Graphic Buffer 缓冲区占用内存。另外还存在耗电、兼容性等问题。

UI控件在绘制到屏幕之前,需要经过栅格化操作,而栅格化非常耗时。GPU 主要用于处理图形运算,可以加快栅格化的过程。

图形显示开发
就目前来说,应用开发者可通过三种方式将图像显示到屏幕上:即 Canvas、OpenGL ES 或 Vulkan,其中Vulkan是较新的技术,兼容不是很好,主要是前两种。这里Canvas即对应着Skia 2D引擎。
无论开发者使用什么渲染 API,一切内容都会渲染到Surface。Surface表示缓冲队列中的生产方,而缓冲队列通常会被 SurfaceFlinger 消耗。在 Android 平台上创建的每个窗口都由 Surface 提供支持。所有被渲染的可见 Surface都被 SurfaceFlinger 合成到显示部分中。
简单说,图形图像的显示就是一个围绕BufferQueue 的生产者——消费者模型。这一点,官方文档也做了详细说明

BufferQueue:Producer向BufferQueue中写数据,Consumer从BufferQueue中读数据。- Producer:生产者。因为应用程序不断地刷新UI,从而将产生的显示数据源源不断地写到
BufferQueue中。当Producer需要使用一块buffer时,调用BufferQueue的dequeue函数,获得的buffer就只属于producer,当生产者认为一块buffer已经写入完成后,调用BufferQueue的queue函数,把buffer归还到BufferQueue的队列中。 - Consumer:消费者 。当生产者准备好数据后,
BufferQueue就会调用onFrameAvailable()来通知Consumer进行消费
图像生产者 :相机 或 基于OpenGL ES 游戏生成的预览。
图像消费者 : SurfaceFlinger 或显示 OpenGL ES 流的另一个应用,如显示相机取景器的相机应用。
生产者和消费者可能是在不同的进程,它们的通信不是用Binder,为了高效传输大块数据,使用匿名共享内存,BufferQueue 不会复制缓冲区内容;通过句柄进行传递。

SurfaceView 与 TextureView

在开发中,我们想使用Surface,可以通过两个控件SurfaceView 和 TextureView来获取。
SurfaceView 继承自View,并提供了一个可以嵌入到View结构树中的独立的绘图层,你可以完全控制这个绘图层,比如说设定它的大小。SurfaceView负责将surface放置在屏幕上的正确位置。它是Surface的持有者,通过SurfaceHolder来对Surface进行管理控制的。SurfaceView的一大特性是允许其他线程(非UI线程)绘制图形(Canvas)。
public class MySurfaceView extends SurfaceView implements Callback{
public MySurfaceView(Context context) {
super(context);
init();
}
private void init(){
SurfaceHolder holder = getHolder();
holder.addCallback(this); //设置Surface生命周期回调
}
// surface变化的时候回调(格式/大小)
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
// surface创建时回调
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
// 隐藏并被销毁时回调
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
TextureView是Android 4.0中引入的控件,主要是为了解决一些SurfaceView的缺陷。与SurfaceView相比,TextureView不会创建一个单独的窗口,这使得它可以像一般的View一样执行一些变换操作,比如移动、动画等等,例如,你可以通过调用setAlpha将TextureView设置成半透明。
tv = (TextureView) findViewById(R.id.texture);
// 设置监听
tv.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
//SurfaceTexture创建成功
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
// 执行操作,如播放视频
}
//大小发生变化
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
//被销毁(在此回调释放资源)
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
//更新
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
});
两者的对比
| TextureView | SurfaceView | |
|---|---|---|
| 绘制 | 1-3帧延迟 | 低 |
| 内存 | 高 | 低 |
| 动画和截图 | 支持 | 不支持 |
| 耗电 | 高 | 低 |
SurfaceView 和 TextureView的使用总结
SurfaceView是独立的一个Window,不存在于界面的view体系结构中,因此不能进行动画、平移、缩放,两个SurfaceView不能相互覆盖。由于不在同一层级中,也难以放在ListView或者ScrollView中。
TextureView更像一般的View,可以被缩放、平移,也能进行动画。但它只能在开启了硬件加速的Window中使用,并且消费的内存要比SurfaceView多
TextureView在5.0版本前在主线程渲染,在5.0后可在单独线程渲染
TextureView内部封装了SurfaceTexture,
SurfaceTexture用于图像流数据的二次处理(如Camera滤镜、美颜、桌面特效等)。与SurfaceView的直接输出相比,这样就会有若干帧的延迟。实际上SurfaceView+SurfaceTexture也能完成类似的功能。TextureView实现视频播放时需注意,接收视频流的对象是SurfaceTexture,而不是TextureView。TextureView只是作为一个硬件加速的展示层。所以如果需要无缝衔接的播放(比如大小屏幕切换播放),TextureView无需复用,但一定要保证SurfaceTexture的复用。
从TextureView中获得一个Surface,使用:
new Surface(textTureView.getSurfaceTexture())
公众号“编程之路从0到1”