通常我们说一个系统不如另一个系统流畅,说的就是前者动画显示不如后者流畅,因此动画显示流畅程度是衡量一个系统流畅性的关键指标。为什么这样说呢?这是因为流畅的动画显示需要60fps的UI刷新速度,然而这却不是一个容易达到的速度。Android 5.0通过引入Render Thread尽最大努力提升动画显示流畅性。
在前面我们提到了Render Thread对动画显示的两个优化。第一个优化是在动画显示期间,临时将动画的目标View的Layer Type设置为LAYER_TYPE_HARDWARE,这样就可以使得目标View以Open GL里面的Frame Buffer Object(FBO)进行渲染。这种优化的效果就如Render Thread直接以Open GL里面的Texture来渲染TextureView一样。第二个优化是在Main Thread不需要参与动画的显示过程时,动画就会被注册到Render Thread中,这样动画的计算和显示过程就完全由Render Thread来负责。这种优化带来的好处就是在动画显示期间,Main Thread可以去处理其它的用户输入,而且动画的显示也会更加流畅。
上面描述的两种动画优化涉及到的Main Thread和Render Thread的交互过程如图所示:
接下来,我们就通过代码分析上述的两种动画显示优化过程。
我们通过调用View类的成员函数animate可以获得一个ViewPropertyAnimator对象,如下所示:
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
......
public ViewPropertyAnimator animate() {
if (mAnimator == null) {
mAnimator = new ViewPropertyAnimator(this);
}
return mAnimator;
}
......
}
有了这个ViewPropertyAnimator对象之后,就可以调用它的成员函数withLayer将它关联的View的Layer Type设置为LAYER_TYPE_HARDWARE,如下所示:
public class ViewPropertyAnimator {
......
public ViewPropertyAnimator withLayer() {
mPendingSetupAction= new Runnable() {
@Override
public void run() {
mView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
if (mView.isAttachedToWindow()) {
mView.buildLayer();
}
}
};
final int currentLayerType = mView.getLayerType();
mPendingCleanupAction = new Runnable() {
@Override
public void run() {
mView.setLayerType(currentLayerType, null);
}
};
......
return this;
}
......
}
ViewPropertyAnimator类的成员函数withLayer创建了两个Runnable,分别保存在成员变量mPendingSetupAction和mPendingCleanupAction中。其中,成员变量mPendingSetupAction指向的Runnable在动画开始显示之前执行,而成员变量mPendingCleanupAction指向的Runnable在动画结束显示之后执行。由此我们就可以看到:
- 在动画开始显示之前,目标View的Layer Type会被设置为LAYER_TYPE_HARDWARE,并且它的成员函数buildLayer会被调用来创建一个Layer。
- 在动画结束显示之后,目标View的Layer Type会被恢复为它之前的Layer Type。注意,这里调用目标View的成员函数getLayerType获得的是它的Layer Type未被设置为LAYER_TYPE_HARDWARE的Layer Type。
接下来我们就继续分析View类的成员函数buildLayer的实现,以便可以了解为一个View设置一个Layer的过程。
View类的成员函数buildLayer的实现如下所示:
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
......
public void buildLayer() {
......
final AttachInfo attachInfo = mAttachInfo;
......
switch (mLayerType) {
case LAYER_TYPE_HARDWARE:
updateDisplayListIfDirty();
if (attachInfo.mHardwareRenderer != null && mRenderNode.isValid()) {
attachInfo.mHardwareRenderer.buildLayer(mRenderNode);
}
break;
case LAYER_TYPE_SOFTWARE:
buildDrawingCache(true);
break;
}
}
......
}
前面已经将当前正在处理的View的Layer Type设置为LAYER_TYPE_HARDWARE,因此View类的成员函数buildLayer首先是调用我们在前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文中分析过的View类的另外一个成员函数updateDisplayListIfDirty更新它的Display List。更新完毕之后,再调用保存在成员变量mAttachInfo描述的一个AttachInfo对象的成员变量mHardwareRenderer指向的一个ThreadedRenderer对象的成员函数buildLayer为当前正在处理的View创建一个Layer。
从这里还可以看到,如果当前正在处理的View的Layer Type被设置为LAYER_TYPE_SOFTWARE,即该View是以软件方式进行渲染的,那么就会调用另外一个成员函数buildDrawingCache将View上次绘制得到的UI缓存在一个Bitmap中,以便下次可以快速地绘制View动画的下一帧。View类的成员函数buildDrawingCache的实现,同样可以参考前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文。
接下来我们主要关注为一个View创建一个Layer的过程,即ThreadedRenderer对象的成员函数buildLayer的实现,如下所示:
public class ThreadedRenderer extends HardwareRenderer {
......
@Override
void buildLayer(RenderNode node) {
nBuildLayer(mNativeProxy, node.getNativeDisplayList());
}
......
}
ThreadedRenderer对象的成员函数buildLayer调用另外一个成员函数nBuildLayer为参数node描述的一个Render Node关联的View创建一个Layer。
ThreadedRenderer对象的成员函数nBuildLayer是一个JNI函数,由Native层的函数android_view_ThreadedRenderer_buildLayer实现,如下所示:
static void android_view_ThreadedRenderer_buildLayer(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlong nodePtr) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
RenderNode* node = reinterpret_cast<RenderNode*>(nodePtr);
proxy->buildLayer(node);
}
参数proxyPtr描述的是一个RenderProxy对象,这里调用它的成员函数buildLayer为参数nodePtr描述的一个Render Node创建一个Layer。
RenderProxy类的成员函数buildLayer的实现如下所示:
CREATE_BRIDGE2(buildLayer, CanvasContext* context, RenderNode* node) {
args->context->buildLayer(args->node);
return NULL;
}
void RenderProxy::buildLayer(RenderNode* node) {
SETUP_TASK(buildLayer);
args->context = mContext;
args->node = node;
postAndWait(task);
}
RenderProxy类的成员函数buildLayer首先是通过宏SETUP_TASK创建一个Task,接下来再调用另外一个成员函数postAndWait将该Task添加到Render Thread的Task Queue去等待执行。最后这个Task由宏CREATE_BRIDGE2声明的函数buildLayer来执行,主要就是调用参数context描述的一个CanvasContext对象的成员函数buildLayer为参数node描述的一个Render Node创建一个Layer。
CanvasContext类的成员函数buildLayer的实现如下所示:
void CanvasContext::buildLayer(RenderNode* node) {
......
TreeInfo info(TreeInfo::MODE_FULL, mRenderThread.renderState());
......
node->prepareTree(info);
......
mCanvas->flushLayerUpdates();
......
}
这里就可以看到CanvasContext类的成员函数buildLayer调用了我们在前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文的关键函数——RenderNode类的成员函数prepareTree,它用来将参数node描述的Render Node的Display List从Main Thread同步到Render Thread中,并且为该Render Node创建了一个Layer,但是这个Layer处理待更新状态。
CanvasContext类的成员函数buildLayer接下来继续调用成员变量mCanvas指向的一个OpenGLRenderer对象的成员函数flushLayerUpdates更新刚才创建的Layer,它的实现如下所示:
void OpenGLRenderer::flushLayerUpdates() {
......
updateLayers();
flushLayers();
......
}
OpenGLRenderer类的成员函数flushLayerUpdates主要是执行了以下两个操作:
- 调用成员函数updateLayers重排和合并所有设置了Layer的Render Node的Display List的绘制命令,这些Layer包括了我们在前面一步创建的Layer。
- 调用成员函数flushLayers执行所有设置了Layer的经过了重排和合并的Render Node的Display List的绘制命令,使得每一个这样的Render Node的UI都分别渲染在一个FBO上。
这样,我们就临时地为一个即将要显示动画的View创建了一个Layer,这个Layer将即将要显示的动画的View的UI渲染在一个FBO,这样以后就可以基于这个FBO来更高效率地显示动画了。
后面的动画显示过程实质上就不断地渲染应用程序窗口的UI,这个过程可以参考前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文。不过,再结合前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文,我们可以知道,在这个过程中,并不是应用程序窗口视图中的每一个View的Display List都是需要重建的,而且对于要显示动画的View,我们也只是需要将动画参数应用在前面为它创建的FBO之上即可。当然,为了得到应用程序窗口的UI,在渲染的过程中,需要重新执行一遍应用程序窗口视图中的每一个View的Display List的绘制命令。我们可以将这个过程看作是应用程序窗口的各个View的UI合成过程。相对于应用程序窗口的每一个View的Display List构建,以及对它里面的绘制命令进行重排和合并的过程来说,上述合成过程的成本是低很多的。
以上分析的就是View动画显示过程中的第一种优化,即在View的动画开始显示之前,