前言
渲染机制是Android操作系统很重要的一环,本系列通过介绍应用从启动到渲染的流程,揭秘Android渲染原理。
问题
1.vsync如何协调应用和SurfaceFlinger配合来完成UI渲染、显示,App接收vsync后要做哪些工作?
2.requestLayout和invalidate区别?
3.performTraversals到底是干什么了?
4.surfaceflinger怎么分发vsync信号的?
5.app需要主动请求vsync信号,sw sync才会分发给app?
6.surfaceview显示视频的时候,视频会一直频繁刷新界面,为什么整个UI界面没有卡顿?
7.app是如何构建起上面这套机制的?
如果对于上面的几个问题没有非常确认、清晰的答案可以继续看下去,本文通过详细介绍渲染机制解答上面的问题。
Vsync信号
Android在“黄油计划”中引入的一个重要机制就是:vsync,引入vsync本质上是要协调app生成UI数据和SurfaceFlinger合成图像,app是数据的生产者,surfaceflinger是数据的消费者,vsync引入避免Tearing现象。vsync信号有两个消费者,一个是app,一个是surfaceflinger,这两个消费者并不是同时接收vsync,而是他们之间有个offset。
vsync-offset引入原因
上面提到hw vsync信号在目前的Android系统中有两个receiver,App + SurfaceFlinger,hw sync会转化为sw sync分别分发给app和sf,分别称为vsync-app和vsync-sf。app和sf接收vsync会有一个offset,引入这个机制的原因是提升“跟手性”,也就是降低输入响应延。
如果app和sf同时接收hw sync,从上面可以看到需要经过vsync * 2的时间画面才能显示到屏幕,如果合理的规划app和sf接收vsync的时机,想像一下,如果vsync-sf比vsync-app延迟一定时间,如果这个时间安排合理达到如下效果就能降低延迟:
SufaceFlinger工作机制
组成架构
-
EventControlThread: 控制硬件vsync的开关
-
DispSyncThread: 软件产生vsync的线程
-
SF EventThread: 该线程用于SurfaceFlinger接收vsync信号用于渲染
-
App EventThread: 该线程用于接收vsync信号并且上报给App进程,App开始画图
- HW vsync, 真实由硬件产生的vsync信号
- SW vsync, 由DispSync产生的vsync信号
- vsync-sf, SF接收到的vsync信号
- vsync-app, App接收到的vsync信号
应用程序基本架构
Android应用进程核心组成
上图列举了Android应用进程侧的几个核心类,PhoneWindow的构建是一个非常重要的过程,应用启动显示的内容装载到其内部的mDecor,Activity(PhoneWindow)要能接收控制也需要mWindowManager发挥作用。ViewRootImpl是应用进程运转的发动机,可以看到ViewRootImpl内部包含mView、mSurface、Choregrapher,mView代表整个控件树,mSurfacce代表画布,应用的UI渲染会直接放到mSurface中,Choregorapher使得应用请求vsync信号,接收信号后开始渲染流程,下面介绍上图构建的流程。
应用启动流程图(下文称该图为P0)
进程启动
应用冷启动第一步就是要先创建进程,这跟linux类似C/C++程序是一致的,Android亦是通过fork来孵化应用进程,我们知道Linux fork的子进程继承父进程很多的资源,即所谓的COW。应用进程同样会从其父进程zygote处继承资源,比如art虚拟机实例、预加载的class/drawable资源等,以付出一些开机时间为代价,一来能够节省内存,二来能够加速应用性能,下面结合systrace介绍Android如何启动一个应用进程,应用启动第一个介入的管理者是AMS,应用启动过程中AMS发现没有process创建,就会请求zygote fork进程,下图就是AMS中创建进程的耗时:
AMS(ActivityManagerService)请求zygote创建进程的流程如下:
##ActvityManager:startProcessLocked
private final void startProcessLocked(ProcessRecord app, String hostingType,
String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
boolean isActivityProcess = (entryPoint == null);
if (entryPoint == null) entryPoint = "android.app.ActivityThread";
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +app.processName);
checkTime(startTime, "startProcess: asking zygote to start proc");
ProcessStartResult startResult;
if (hostingType.equals("webview_service")) {
startResult = startWebView(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, entryPointArgs);
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith, entryPointArgs);