在上一篇文章 Android进阶宝典 — WindowManager原理深度剖析,着重介绍了WindowManager关于窗口的操控,例如什么时分增加view,以及窗口的改写机制。经过上节咱们简略介绍了UI的改写流程,以及图画烘托进程SurfaceFlinger,那么这节我将会着重介绍Android体系的图画显现及改写原理。
1 Android体系的图画显现
当咱们翻开某个app时,多彩的画面就会展现在手机屏幕上,只要有数据变化画面就会改动,那么这些画面是如何展现在手机上的,画面数据是存储在哪里的?
有关Android图形的官方文档:
source.android.com/docs/core/g…
1.1 硬件帧缓冲区
这儿是一个新的概念,Android设备的屏幕能够笼统为一个(硬件)帧缓冲区,在体系的HAL层是存在一个Gralloc模块,内部封装了一切对帧缓冲区的拜访操作。 当然一个Android设备至少会有一个显现屏用于展现图形,当这个画面需求改写时,会往帧缓冲区写入要制作的画面内容,已然有写那么就会有读,当帧缓冲区的数据准备好后,就会将帧缓冲区中的数据读取出来,烘托到屏幕上。
当然这也只是一个比较简略的介绍,有兴趣的同伴能够去看下HAL层的源码,其间会介绍Gralloc模块,以及gralloc设备与fb设备。
那么已然涉及到了读写操作,必定会有同步的问题发生;假如需求制作的画面数据没有准备好,那么就去读数据,就会造成图画帧不同步,屏幕画面呈现撕裂。
其实在多线程中,处理同步的一种处理方式便是加锁,在读写操作持有同一把锁,当写操作的时分,读操作就需求等候,只有比及写操作完结之后释放锁,交由读操作履行。
可是加锁带来的一个问题便是功能稍差一点,同一时刻只有读或许写,功率比较低,除此之外还有什么方法呢?便是增加缓冲区。
1.2 多缓冲区处理屏幕撕裂问题
选用多缓冲区的意图便是为了提高功率,让读写能够一起进行。前缓冲区用于屏幕直接数据读取,后缓冲区则是用于写入数据。
所以类似于另开了一个线程,每个线程只会干自己的事情,可是这儿并不是说后缓冲区的数据写完之后,还会传输到前缓冲区,而是会在一个机遇进行交流,前缓冲区变后缓冲区,后缓冲区变前缓冲区。
1.3 从帧缓冲区了解卡顿的原因
前面咱们介绍的缓冲区图形读写流程是理想的,当时缓冲区的数据读取完结之后,后缓冲区的数据刚好写完,可是实践上这种场景很难呈现,由于一旦两者的速率不一致,就或许造成卡顿或许屏幕撕裂。
通常咱们在买手机的时分,会看下设备参数:
分辨率:2400×1080
显现帧率:最高120Hz
这儿的显现帧率指的便是每秒屏幕的改写速率,最高120HZ,也便是说最高每秒120次;还有一个参数叫FPS,这个参数代表体系每秒组成的帧数,通常由硬件功能决议的,假如玩过LOL,屏幕右上角会显现FPS帧数,假如低于60FPS,就会感觉到卡顿。
- 改写帧率HZ 大于 组成帧率FPS
经过对这两个参数的比较,假如显现帧率要大于组成帧率,也就意味着前缓冲区的数据改写完结之后,后缓冲区的数据帧还没有准备好,此刻屏幕仍然显现上一帧,就造成了卡顿。
- 组成帧率HZ 大于 改写帧率HZ
这儿刚好跟上面的反着,当后缓冲区的数据写完之后,前缓冲区的数据还没烘托完,就请求烘托下一帧的数据了,导致呈现屏幕撕裂,例如下图:上半部分展现的还是上一帧图画,下半部分就展现下一帧的图画了。
所以为了保证改写速率与组成速率的一致,才有了笔直同步信号的概念,经过同步信号束缚图画组成与改写,使其步调一致。
也便是说当我的屏幕需求改写的时分,才会去组成图画写入后缓冲区,然后屏幕去前缓冲区获取数据改写。
2 VSYNC同步信号与三缓冲
这儿官方关于VSYNC信号的解释,其主要效果便是为了同步烘托展现的流程,防止卡顿。
VSYNC 信号可同步显现流水线。显现流水线由运用烘托、SurfaceFlinger 组成以及用于在屏幕上显现图画的硬件混合烘托器 (HWC) 组成。VSYNC 可同步运用唤醒以开端烘托的时刻、SurfaceFlinger 唤醒以组成屏幕的时刻以及屏幕改写周期。这种同步能够消除卡顿,并提升图形的视觉体现
看下面这张图:
- 咱们先看榜首帧的生成,经过CPU的核算以及GPU的栅格化处理,组成了榜首帧,当VSYNC信号来了之后,榜首帧被Display展现在了屏幕上,此刻改写速率与组成速率是一致的 ;
- 当开端组成第二帧的时分,咱们发现当VSYNC信号来的时分,图画并没有组成完结,因而展现的还是榜首帧,由于后缓冲区数据没写完,前缓冲区数据为上一帧的数据。
- 当第三个VSYNC信号来的时分,缓存完成了交流,此刻正常展现了第二帧的数据。
2.1 Android 4.1 后关于VSYNC的优化
本小节开头介绍了Android 4.1 之前的改写机制,只有在数据帧组成完结,并完结缓存交流之后,才会进行下一帧的组成,那么势必就会糟蹋掉一些时刻,看下图:
中间的时刻分明能够进行下一帧的烘托,然后防止一次Jank,因而在Android 4.1之后进行了优化,见下图:
只有当接收到Vsync信号之后,才开端进行图画组成,此刻CPU和GPU将会有完好的16ms来处理,当下次VSYNC信号来暂时,进行缓存交流展现。
2.2 三缓冲机制
可是即便如此,假如由于页面杂乱,例如层级嵌套深,会导致CPU和GPU的处理时刻增加,然后导致在下次VSYNC信令来暂时,图画没有组成,导致无法交流缓存空间,然后发生了卡顿,例如下图:
由于现在是双缓存机制,会导致榜首次VSYNC和第2次VSYNC之间很长一段CPU GPU时刻被糟蹋了,因而为了防止卡顿,引入了三缓冲的机制,其实便是新请求一块Graphic Buffer内存,如下图:
当VSYNC信号来暂时,由于没有组成图画,所以没有进行缓存交流,可是为了防止CPU糟蹋,敞开了C帧的组成,此刻B帧也组成完结,下次VSYNC信号来暂时,成功烘托B帧,以此类推然后减少了一次卡顿。
所以VSYNC虽然保持了改写频率与组成频率的步调一致,可是仍然不能完全防止卡顿的发生;由于内部组成速度的问题,假如由于耗时核算导致了CPU和GPU需求花更多的时刻去组成一帧图画,就会导致缓冲区内的数据不能及时更新然后发生卡顿 ,所以体系从底层做了优化,选用了三缓冲机制。
所以有同伴或许会想,多加几个buffer岂不是更好的防止了卡顿,当然这样做是能处理问题,可是内存会猛然上升,影响功能。
3 图画组成 SurfaceFlinger
前面咱们介绍了当VSYNC信号来暂时,CPU和GPU开端作业组成图画,那么详细组成图画的进程便是SurfaceFlinger。
SurfaceFlinger 接受缓冲区,对它们进行组成,然后发送到屏幕。WindowManager 为 SurfaceFlinger 提供缓冲区和窗口元数据,而 SurfaceFlinger 可运用这些信息将 Surface 组成到屏幕。
3.1 Window与Surface的联系
在上节中,咱们介绍了Window和Activity的联系,由于Window是承载画面的窗体,所以Window跟Surface也是一一对应的,Surface目标使运用能够烘托要在屏幕上显现的图画,像OpenGL便是将画面烘托在Surface上,而SurfaceFlinger则是将Surface组成到屏幕上,用户在手机屏幕上就能够看到这些画面。
所以Surface与SurfaceFlinger是存在直接的联系的,他们之间的联系是一种常见的模型:生产者与顾客,Surface用于生产数据帧,而SurfaceFlinger则是用于消费数据帧,以便组成数据帧。
当咱们经过WindowManager增加一个View之后,ViewRootImpl中会履行scheduleTraversals方法进行丈量制作,当进行制作的时分,就会运用到SurfaceFlinger。
- 首先在SurfaceFlinger中存在一个BufferQueue,用于数据帧的入列和出列,其间会有一个生产者和顾客模型,生产者便是Surface,在运用侧拿到的Surface其实便是在SurfaceFlinger中顾客的代理目标;
- 当进行制作时,Surface就会烘托所要展现的图画,例如Text、Button等,制作完结之后就会生成对应的数据帧交给BufferQueue;
- 当VSYNC信号来暂时,HWC会从顾客中取出要展现在屏幕上的数据帧展现,一起完结缓存Buffer替换。
3.2 SurfaceView
官方文档:
source.android.com/docs/core/g…
提到SurfaceView,或许许多同伴会在一些面试题中看到,可是实践用到却很少。这也是正常的,是由于原生Skia烘托器通常是在Canvas上烘托页面,这儿不需求开发者自己去完成烘托逻辑;可是假如碰到运用OpenGL去烘托画面,例如摄像头的预览画面,在一些直播场景中会运用到,往往需求自采集数据并烘托,这时就会运用到SurfaceView。
SurfaceView看名字就知道也是一个控件,与其他控件不同的是,它内部有一个Surface,咱们不能直接拿到Surface,因而Android提供了SurfaceHolder用来对Surface进行封装。
class CameraPreView @JvmOverloads constructor(
context: Context
) : SurfaceView(context), SurfaceHolder.Callback {
/**是否在制作*/
private var isDrawing = false
private var renderHandler:RenderHandler
init {
renderHandler = RenderHandler()
}
override fun surfaceCreated(holder: SurfaceHolder) {
isDrawing = true
//敞开
renderHandler.start()
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
isDrawing = false
}
inner class RenderHandler : Thread(){
override fun run() {
super.run()
while(isDrawing){
val canvas = holder.lockCanvas() // 请求缓冲区
//敞开制作
try {
drawCanvas(canvas)
}catch (e:Exception){
}finally {
holder.unlockCanvasAndPost(canvas)
}
}
}
private fun drawCanvas(canvas: Canvas?) {
canvas?.drawText("长安三万里")
}
}
}
首先咱们看下上面的自定义View,这儿其实便是在做自定义的制作,可是跟一般的View不同的是,这儿的制作是能够放在子线程的。
这便是SurfaceView与一般的View不同之处,Android要求在运用SurfaceView时需求运用主线程之外的线程烘托Surface,因而一些耗时的烘托使命就能够放在子线程中进行,防止堵塞主线程。
假如想要监听Surface的创立或许毁掉,需求完成SurfaceHolder的Callback接口,当Surface创立成功之后,就会回调surfaceCreated接口,此刻能够敞开烘托线程;当Surface毁掉之后,会回调surfaceDestroyed接口,中止烘托。
当敞开Surface烘托之后,能够获取SurfaceHolder目标,这是SurfaceView的内部目标,前面咱们提到过SurfaceHolder是对Surface的一系列封装,咱们介绍其间比较核心的API接口。
- lockCanvas
当调用lockCanvas时,会向SurfaceFlinger请求缓冲区以写入pixel,并且会锁定这个缓冲区不会被其他客户端一起写入,一起会回来Canvas目标;
当回来Canvas目标之后,能够经过画笔来制作图形,类似于履行onDraw的流程。
- unlockCanvasAndPost
当往缓冲区写完数据帧之后,调用unlockCanvasAndPost会解锁缓冲区,并将其发送到组成器也便是SurfaceFlinger,此刻生产者完结了一次制作的使命入队BufferQueue,当屏幕需求改写时,就会从BufferQueue中取出数据,经过HWC组成到屏幕中。
3.3 View和SurfaceView的差异
这儿咱们总结一下一般的View和SurfaceView的差异:
- 关于一般的View,其实是处于Window之上的,能够经过WindowManager增加;而SurfaceView内部是有一个Surface,制作的内容是烘托在Surface上的;
- 关于自定义View,是需求放在主线程改写的,这是Android体系的规则;而SurfaceView关于UI的改写是能够放在子线程的,这也是Android体系的规则,能够提高功能。
- 关于一般View来说,其改写机制依赖于底层的规则,60HZ是Android体系的标准;而SurfaceView由于能够独立线程烘托,人为能够操控,因而能够设置改写的速率。