本文来自网易云社区
作者:宋文海
常常听到这样一种疑问,为什么Android没有iOS那么流畅?为了解决这个问题,Google几乎在每个版本的Android系统更新中的都有这方面的优化,Android 3.0开始引入硬件加速,4.0引入Vsync、Triple buffer,4.1引入Choreographer,5.0引入Render Thread。
其中Choreographer机制在SDK中提供了一组API给应用开发者使用,和应用层开发息息相关。Choreographer可以看成和Handler类似的任务处理中心,区别在于Handler是基于Looper不断的进行轮询调度,而Choreographer是结合底层的Vsync事件进行有规律的调度。理想情况下,如果采用60fps的显示屏,即1秒显示60帧,那么Choreographer最快可以做到1/60秒进行一次调度,否则就是发生了丢帧。有了Choreographer,UI相关的任务可以做到在Vsync事件到来时统一处理。
Choreographer接受三种类型的任务,CALLBACK_INPUT、CALLBACK_ANIMATION和CALLBACK_TRAVERSAL。当Vsync事件达到时,Choreographer会按此顺序执行任务。从调度顺序来看,CALLBACK_INPUT是最高的,因为用户对触摸事件是最敏感的。常见的invalidate方法通过ViewRootImpl投递了一个CALLBACK_TRAVERSAL类型的任务,当Vsync事件到来时执行我们熟悉的performTraversal方法。动画方面,3.0之前的View Animation通过在draw方法中drawAnimation来执行,新的Property Animation通过CALLBACK_ANIMATION来执行动画,说明动画的优先级在Android新版本中得到了提高。
和Handler类似,Choreographer也是通过post方法来提交任务,Choreographer提供了两种post方法,postCallback(Runnbale)和postFrameCallback(FrameCallback)。FrameCallback的接口方法比Runnbale的接口方法多一个表示当前帧时间的参数,通过这个参数可以判断出是否发生了丢帧。
Choreographer是一个ThreadLocal的单例,创建Choreographer的线程必须要有Looper。既然Choreographer是基于Vsync事件进行任务调度的,为什么还需要Looper。从下面的构造函数可以看到,是FrameHandler和FrameDisplayEventReceiver成员需要Looper。
private Choreographer(Looper looper) {
mLooper = looper;
mHandler = new FrameHandler(looper);
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
···
}
FrameDisplayEventReceiver需要用Looper是因为它在native层构建NativeDisplayEventReceiver时要用到Looper中的MessageQueue;在Native层,Vsync事件和Looper中的派发的事件共用同一个MessageQueue。
上图是Choreographer请求和派发Vsync事件的流程图。如图可见,请求和派发Vsync事件时都需要FrameHandler来进行调度。 在请求Vsync事件的流程中,如果是延时请求,需要通过FrameHandler来控制时间;如果某个线程拿了另一个线程的Choreographer请求Vsync事件的时候,isRunningOnLooperThreadLocked返回false,此时需要通过FrameHandler来切换线程,确保事件调度都在Choreographer所在线程中实施。
通过上面两幅图可以看到,第一幅图因为没有在Vsync事件到来时及时处理,导致丢帧;第二幅图,引入Choreographer后,每一帧都在Vsync事件到来时刷新,避免了丢帧。
上面两种情况每一帧都能在两次Vsync事件之间完成,万一某些帧的处理时间很长,丢帧还是不可避免。如上图所示,由于B帧处理时间过长,A缓冲区被屏幕占用,无法开始下一帧的处理,导致下一帧继续丢帧。为了避免这种情况,Android引入了Triple buffer,三缓冲机制。
当B帧处理超时,A帧缓冲区被屏幕占用的情况下,CPU可以拿第三个缓冲区C进行处理,即使C帧处理也超时了,但仍然可以避免丢帧。
Triple buffer的引入提高了CPU和GPU的并行程度,一定程度上提高了UI流畅性。而Render Thread的引入,则进一步提高了CPU内部的并行程度。引入Render Thread之前,所有CPU部分和UI相关的操作都在进行UI Thread进行。从Android 5.0开始,UI Thread中的那些和绘制相关的处理被移到了Render Thread中处理,这部分处理更偏向于和GPU交互,可以和UI Thread并行执行。
从上图可以看到,前一帧由于UI Thread操作过多,第二帧Vsync信号到来时Render Thread还在进行操作;尽管如此,得益于UI Thread和Render Thread可以并行操作,后一帧的UI Thread也开始执行了。因此,Render Thread的引入进一步提高了UI处理的并行性,从而提高了UI流畅度。
随着这些机制的逐步引入,Android的UI流畅度有了很大提高。虽然在硬件加速方面,由于Android硬件多样化,和积淀已久的iOS还有差距,但是相信随着时间推移,差距也会越来越小。而且,Android还在陆续引入Art虚拟机,Doze休眠等提高系统整体性能的机制,这些机制也能从侧面提高UI流畅度,相信Android的UI体验还会越来越好。
网易云免费体验馆,0成本体验20+款云产品!
更多网易研发、产品、运营经验分享请访问网易云社区。