-
Notifications
You must be signed in to change notification settings - Fork 42
Andorid 4.1 黄油计划
Android 4.1通过黄油计划(Project Butter)解决了令人诟病的系统不流畅的问题。看了官方的文档及其他人写的一些分析文档,总感觉有些地方还是云里雾里,不太明白。因此对比了Android 4.0.3 与 Android 4.1 的源码,希望通过代码层面的分析来理解这个Project Butter是怎么解决系统不流畅的问题。
了解过黄油计划的人应该会知道两个关键词:VSync(垂直同步)、Triple Buffer(三缓存)。
VSync(垂直同步)不是一个新概念(或技术)。我们知道使用的显示器都有一定刷新率(单位是Hz),一般都是60次每分钟。显示器每隔一定时间间隔(如16ms)将显卡中的已经准备好的图像上屏进行显示。如传统CRT显示器的隔行扫描与逐行扫描,液晶显示器则是通过控制器控制每个像素点的发光。液晶显示器的刷新率受其响应时间的限制,会有一个上限,如60或90等。显示器上屏显示的图像由程序使用显卡渲染生成;程序生成图像也有一定的速度,这个速度叫帧率(FPS)。如果FPS与显示器的刷新率一致,一般不会有什么问题。如果FPS高于刷新率,就会有撕裂(Tearing)的问题,显示器上一屏内显示的画面不是同一帧(特别是当画面变化剧烈,帧与帧之间差异大时会比较明显)。为了解决撕裂的问题,引入了VSync。程序(显卡)不能在渲染好图像后就立即提交上屏,需要等一个固定的时间点。显示器60的刷新率,则每隔16ms就是一个固定的时间点。通过引入这种协调机制,使得FPS与刷新率达到同步。
归纳一句话:VSync通过限制FPS与显示器的刷新率保持一致来解决Tearing的问题。
回到Android的流畅性问题。App在UI发生变化(如翻页、滑动)时需要重绘来刷新。如果重绘不及时,就会有延迟,感觉不流畅、卡顿。我们先来看下Android 4.0.3的重绘机制。
Android系统的UI框架是基于Activity与Fragment,控件则都是基于View这个基类。在需要刷新重绘时,会调用View的invalidate()方法。分析源码,invalidate()会调用接口ViewParent的invalidateChild()方法。ViewParent接口由类ViewRootImpl实现。invalidateChild()方法最后通过调用scheduleTraversals()方法向消息队列发送了一个DO_TRAVERSAL的消息。
总结一下:当需要刷新时,调用了invalidate()方法,消息队列中多了一个DO_TRAVERSAL待处理消息。
接下来,看下DO_TRAVERSAL消息的处理。查看ViewRootImpl中handleMessage()的实现,DO_TRAVERSAL就是一个普通的消息,与其他的UI消息、系统事件消息一样。处理DO_TRAVERSAL消息时,会调用performeTraversals()方法。performeTraversals会对UI进行遍历,走一遍onMeasure、onLayout、onDraw实现UI重绘。
这样分析下来,好像也没什么问题。当需要重绘刷新时,调用下invalidate()方法,会在消息队列中新增DO_TRAVERSAL消息,消息循环会过通过Handler处理这个消息,完成重绘。其实有一个大的问题就是:DO_TRAVERSAL消息处理可能不及时。上面说过DO_TRAVERSAL就是一个普通的消息,与其他消息都在一个消息队列中。如果消息队列中消息较多或某个消息处理耗时较长,DO_TRAVERSAL可能得不到及时的处理。当显示器刷新时,新的UI重绘没有完成,无法上屏,这时就卡顿了。