Android渲染机制

参考:

震惊!原来Android渲染机制还可以这样理解!_android traversal-优快云博客

问题
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是如何构建起上面这套机制的?

如果对于上面的几个问题没有非常确认、清晰的答案可以继续看下去,本文通过详细介绍渲染机制解答上面的问题

app是数据的生产者,surfaceflinger是数据的消费者,vsync引入避免Tearing现象。vsync信号有两个消费者,一个是app,一个是surfaceflinger,这两个消费者并不是同时接收vsync,而是他们之间有个offset。

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信号

应用程序基本架构

ndroid应用进程核心组成

上图列举了Android应用进程侧的几个核心类,PhoneWindow的构建是一个非常重要的过程,应用启动显示的内容装载到其内部的mDecor,Activity(PhoneWindow)要能接收控制也需要mWindowManager发挥作用。ViewRootImpl是应用进程运转的发动机,可以看到ViewRootImpl内部包含mView、mSurface、Choregrapher,mView代表整个控件树,mSurfacce代表画布,应用的UI渲染会直接放到mSurface中,Choregorapher使得应用请求vsync信号,接收信号后开始渲染流程,下面介绍上图构建的流程。
————————————————

进程启动
应用冷启动第一步就是要先创建进程,这跟linux类似C/C++程序是一致的,Android亦是通过fork来孵化应用进程,我们知道Linux fork的子进程继承父进程很多的资源,即所谓的COW。应用进程同样会从其父进程zygote处继承资源,比如art虚拟机实例、预加载的class/drawable资源等,以付出一些开机时间为代价,一来能够节省内存,二来能够加速应用性能,下面结合systrace介绍Android如何启动一个应用进程,应用启动第一个介入的管理者是AMS,应用启动过程中AMS发现没有process创建,就会请求zygote fork进程,下图就是AMS中创建进程的耗时:
————————————————

前面systrace打印的proc创建时间就是来自与此,Process.start是请求zygote来创建创建进程,这其中有几个很重要问题,比如新建进程入口函数在哪?这个新建进程如何做到创建以后能够不退出,且能不断响应外部输入的等,接下来介绍下入口函数这个点,正如C/C++跑起来去找main函数一样,可以看到startProcess函数有个entrypoint参数:

if (entryPoint == null) entryPoint = "android.app.ActivityThread";

1
2
原来进程启动以后就会先去执行ActivityThread:main这个入口,应用自此开始了自己启动流程,这点systrace展示的非常清晰:


————————————————

ActivityThread对象
ActivityThread main执行的第一件事是调用AMS的attacApplicationLock(P0 :6)向大管家汇报:“进程已经启动好了,继续往下启动吧”。AMS收到汇报就回调了(P0:7)ActvityThread的bindApplication,这里“绑定”理解起来比较抽象,到底是要把哪些东西跟应用程序“绑定”起来呢?其实是把app本身的“上下文(context)”信息跟刚刚创建的进程绑定起来,噢,又出来一个“上下文(context)”概念,用大白话讲就是应用的apk包包含应用的所有身家信息,这些个信息就可以称为是应用的“上下文(context)”,应用可以通过这个Context访问自己的家当,此处会创建Application Context(具体关于应用程序几种context区别自行google,此处不予展开)
————————————————

Activity对象
Activity的构建开始窗口显示之旅,上面“Android应用进程核心组成”架构图中可以看到Activity核心是PhoneWindow,P0图中步骤13 performLauncherActivity中包含了14/15两个重要的操作,attach函数创建了“PhoneWindow”,这个窗口具体承载了什么信息?用大白话来说点击启动一个应用以后,可以说是显示了一个”窗口”(Window),这个“窗口”至少要承载两个功能:

显示内容

可以操作

窗口显示的内容就是android的布局(layout),布局信息需要有个“房间”存放,PhoneWindow:mDecor就是这个“房间”,attach首先将布局的“房间”建好,等到后续15 onCreate调用到就会调用setContentView使用应用程序开发者提供的布局(layout)“装饰、填充”这个“房间”。
————————————————

空的HellWorld工程都默认包含上面两行代码,setContentView就是操作系统给开发机会告诉系统“到底让我显示什么?”就是这么简单的一行代码很可能就是导致应用性能卡顿,那么setContentView干啥了?

setContentView函数
该函数的作用就是使用布局文件填充“房间”mDecor,如果布局文件非常复杂会导致“房间”装饰的费时费力(豪装),装修过程中从原理说就是讲布局文件activity_main中的控件实例化,Android这个过程称作inflate,systrace展示如下:
————————————————

上面只是操作系统从让开发给填充、装饰了房间,但是这个房间还没“开灯”,看不见,也没开门(窗口无法操作),因为需要真正把这个窗口注册到WindowManagerService后,WMS同SurfaceFlinger取得联系才能看到,后面我们来分析这个窗口是如何开灯显示,并且能开门迎客接收按键消息的。
————————————————

上面performResumeActivity会回调应用程序的onResume函数,从这里可以看到onResume被回调时用户是看不到窗口的。wm.addView是重点,这一步就要把“房间”亮灯,也就是把窗口注册到wms中着手显示出来,并且开门接收用户操作,这里是调用的WindowManagerImpl.java:addView:


————————————————

从这里开始创建应用进程最核心的:ViewRootImpl类,它负责与WMS通信,负责管理Surface,负责触发控件的测量、布局、绘制,同时也是输入事件的中转站,可以说ViewRootImpl是整个控件系统运转的中枢,应用进程中最为重要的一个组件,有了ViewRootImpl这个窗口才能开始渲染被用户看到,并且接受用户操作(开灯、开门)。
————————————————

ViewRootImpl剖析
上面的框架图提到ViewRootImpl有个非常重要的对象Choreographer,整个应用布局的渲染依赖这个对象发动,应用要求渲染动画或者更新画面布局时都会用到Choreographer,接收vsync信号也依赖于Choreographer,我们以一个View控件调用invalidate函数来分析应用如何接收vsync、以及如何更新UI的。
————————————————

从systrace中我们经常看到doFrame就是从上面的doFrame打印,这说明应用程序收到了vsync信号要开始渲染布局了,图示如下:

doFrame函数就开始一次处理input/animation/measure/layout/draw,doFrame代码如下:

doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
————————————————

上面就是应用进程收到vsync信号之后的渲染UI的大概流程,可以看到app进程收到vsync信号以后就开始其measure/layout/draw三大流程,这里面就会回调应用的应用各个空间的onMeasure/onLayout/onDraw,这个部分是在UIThread完成的。UIThread在完成上述步骤以后会绘制指令(DisplayList)同步(sync)给RenderThread,RenderThread会真正的跟GPU通信执行draw动作,systrace图示如下:


————————————————

 

上图中看到doFrame下面会有input/anim(时间短色块比较小)、measure、layout、draw,结合上面的代码分析就清楚了app收到vsync信号的行为,measure/layout/draw的具体分析就涉及到控件系统相关的内容,这块内容本文不作深入分析,提一下draw这个操作,使用硬件加速以后draw部分只是在UIThread中收集绘制命令而已,不做真正的绘制操作,该部分后续开一篇介绍硬件加速和hwui的文章做介绍。
————————————————

    

APP为什么滑动卡顿、不流畅
这里我们指UI/Render线程里面的卡顿,因为这里才涉及Android的核心原理,非UIThread的执行逻辑导致的卡顿需要根据具体业务场景分析,比如影视播放卡顿可能是播放器原因,可能是网络原因等等。UIThread的卡顿有如下几类的原因:

后台进程CPU消耗高
如果CPU被后台进程或者线程消耗,前台的应用流畅性势必会受影响,这点也是很容易被忽略的。

复杂的控件树
复杂的布局不仅会导致inflate时间变长,同时也会导致traversal时间变长,如果traversal + renderthread 的渲染部分不能在16ms内完成就出现掉帧现象,布局优化可以参考前面启动性能文章。

不合理requestLayout
requestLayout顾名思义就是应用布局发生变化,需要重新进行measure/layout/draw的流程,比invalidate调用更重,invalidate只是标记一个“脏区域”,不需要执行meausre/layout调用,只需要重绘即可。requestLayout调用意味着频繁的traversal动作,此时肯定会导致卡顿掉帧问题。
————————————————

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值