Android应用程序UI硬件加速渲染的动画执行过程分析

熬夜冠军 分享 时间: 收藏本文

【简介】感谢网友“熬夜冠军”参与投稿,下面是小编收集整理的Android应用程序UI硬件加速渲染的动画执行过程分析(共4篇),供大家参考借鉴,希望可以帮助到有需要的朋友。

篇1:Android应用程序UI硬件加速渲染的动画执行过程分析

通常我们说一个系统不如另一个系统流畅,说的就是前者动画显示不如后者流畅,因此动画显示流畅程度是衡量一个系统流畅性的关键指标,为什么这样说呢?这是因为流畅的动画显示需要60fps的UI刷新速度,然而这却不是一个容易达到的速度。Android 5.0通过引入Render Thread尽最大努力提升动画显示流畅性。本文就分析Render Thread显示动画的过程,以便了解它是如何提高动画显示流畅性的。

在前面Android应用程序UI硬件加速渲染技术简要介绍和学习计划一文中,我们提到了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的交互过程如图1所示:

图1 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; } ......}这个函数定义在文件frameworks/base/core/java/android/view/View.java中。

有了这个ViewPropertyAnimator对象之后,就可以调用它的成员函数withLayer将它关联的View的Layer Type设置为LAYER_TYPE_HARDWARE,如下所示:

public class ViewPropertyAnimator { ...... public ViewPropertyAnimator withLayer() { mPendingSetupAction= new Runnable() {@Overridepublic void run() { mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); if (mView.isAttachedToWindow()) { mView.buildLayer(); }} }; final int currentLayerType = mView.getLayerType(); mPendingCleanupAction = new Runnable() {@Overridepublic void run() { mView.setLayerType(currentLayerType, null);} }; ...... return this; } ......}这个函数定义在文件frameworks/base/core/java/android/view/ViewPropertyAnimator.java中。

ViewPropertyAnimator类的成员函数withLayer创建了两个Runable,分别保存在成员变量mPendingSetupAction和mPendingCleanupAction中。其中,成员变量mPendingSetupAction指向的Runable在动画开始显示之前执行,而成员变量mPendingCleanupAction指向的Runable在动画结束显示之后执行。由此我们就可以看到:

1. 在动画开始显示之前,目标View的Layer Type会被设置为LAYER_TYPE_HARDWARE,并且它的成员函数buildLayer会被调用来创建一个Layer。

2. 在动画结束显示之后,目标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; } } ......}

这个函数定义在文件frameworks/base/core/java/android/view/View.java中。

前面已经将当前正在处理的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()); } ......}这个函数定义在文件frameworks/base/core/java/android/view/ThreadedRenderer.java中。

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(proxyPtr); RenderNode* node = reinterpret_cast(nodePtr); proxy->buildLayer(node);}

这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。

参数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);}这个函数定义在文件frameworks/base/libs/hwui/renderthread/RenderProxy.cpp中。

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(); ......}这个函数定义在文件frameworks/base/libs/hwui/renderthred/CanvasContext.cpp中。

这里就可以看到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(); ......}这个函数定义在文件frameworks/base/libs/hwui/OpenGLRenderer.cpp中。

OpenGLRenderer类的成员函数flushLayerUpdates主要是执行了以下两个操作:

1. 调用成员函数updateLayers重排和合并所有设置了Layer的Render Node的Display List的绘制命令,这些Layer包括了我们在前面一步创建的Layer。

2. 调用成员函数flushLayers执行所有所有设置了Layer的经过了重排和合并的Render Node的Display List的绘制命令,使得每一个这样的Render Node的UI都分别渲染在一个FBO上。

关于OpenGLRenderer类的成员函数updateLayers和flushLayers的实现,可以参考前面前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文。

这样,我们就临时地为一个即将要显示动画的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的动画开始显示之前,临时地为它创建一个Layer,使得View的UI渲染在一个FBO上,以后的动画就直接作用在该FBO上。接下来我们继续分析View动画显示过程的第二种优化,即将View动画的计算和显示完全交给Render Thread来负责。

当我们通过View类的成员函数animate获得了一个即将要显示动画的View关联的ViewPropertyAnimator对象之后,就可以通过这个ViewPropertyAnimator对象设置各种动画参数。动画参数设置完毕,就可以调用上述ViewPropertyAnimator对象的成员函数start进行动画显示。

ViewPropertyAnimator类的成员函数start的实现如下所示:

public class ViewPropertyAnimator { ...... public void start() { ...... startAnimation(); } ......}这个函数定义在文件frameworks/base/core/java/android/view/ViewPropertyAnimator.java中。

ViewPropertyAnimator类的成员函数start调用了另外一个成员函数startAnimation来启动动画,后者的实现如下所示:

public class ViewPropertyAnimator { ...... /** * A RenderThread-driven backend that may intercept startAnimation */ private ViewPropertyAnimatorRT mRTBackend; ...... private void startAnimation() { if (mRTBackend != null && mRTBackend.startAnimation(this)) {return; } ...... ValueAnimator animator = ValueAnimator.ofFloat(1.0f); ...... animator.start(); } ......}这个函数定义在文件frameworks/base/core/java/android/view/ViewPropertyAnimator.java中。

ViewPropertyAnimator类的成员函数startAnimation首先是检查成员变量mRTBackend的值是否等于null。如果不等于null,那么它指向的就是一个ViewPropertyAnimatorRT对象,因此接下来就调用该ViewPropertyAnimatorRT对象的成员函数startAnimation来执行动画相关的操作。

如果调用ViewPropertyAnimator类的成员变量mRTBackend指向的ViewPropertyAnimatorRT对象的成员函数startAnimation得到的返回值为true,就表示由Render Thread完全负责动画的计算以及显示。否则的话,就需要由Main Thread负责动画的计算,然后将计算好的动画应用在View上,再由Render Thread负责将动画显示出来。

从ViewPropertyAnimator类的成员变量mRTBackend的注释我们也可以看到,ViewPropertyAnimatorRT类是用来拦截ViewPropertyAnimator类负责的动画的,也就是将动画完全交给Render Thread来管理。不过在5.0的代码中,还没有看到任何初始化ViewPropertyAnimator类的成员变量mRTBackend的代码。这就是意味着ViewPropertyAnimator类的成员变量mRTBackend始终为null,因此就不会去调用ViewPropertyAnimatorRT类的成员函数startAnimation。

我们相信以后ViewPropertyAnimatorRT类的功能一定会派上用场的,因此接下来我们就假设ViewPropertyAnimator类的成员变量mRTBackend已经被始化,这样我们就可以更好地理解我们上面提到的动画显示的第二种优化。

ViewPropertyAnimatorRT类的成员函数startAnimation的实现如下所示:

class ViewPropertyAnimatorRT { ...... public boolean startAnimation(ViewPropertyAnimator parent) { ...... if (!canHandleAnimator(parent)) {return false; } doStartAnimation(parent); return true; } ......}这个函数定义在文件frameworks/base/core/java/android/view/ViewPropertyAnimatorRT.java中。

ViewPropertyAnimatorRT类的成员函数startAnimation首先是调用成员函数canHandleAnimator判断是否能够让Render Thread来完全负责当前正在处理的动画。如果不能完全负责,那么就返回一个false值给调用者。否则的话,就调用另外一个成员函数doStartAnimation将当前正在处理的动画交给Render Thread来管理。

我们先看什么情况下ViewPropertyAnimatorRT类的成员函数startAnimation不能让Render Thread来完全负责当前正在处理的动画,也就是ViewPropertyAnimatorRT类的成员函数canHandleAnimator的实现,如下所示:

class ViewPropertyAnimatorRT { ...... private boolean canHandleAnimator(ViewPropertyAnimator parent) { ...... if (parent.getUpdateListener() != null) {return false; } if (parent.getListener() != null) {// TODO supportreturn false; } if (!mView.isHardwareAccelerated()) {// TODO handle this maybe?return false; } if (parent.hasActions()) {return false; } // Here goes nothing... return true; } ......}这个函数定义在文件frameworks/base/core/java/android/view/ViewPropertyAnimatorRT.java中。

参数parent指向的是一个ViewPropertyAniamtor对象,该ViewPropertyAniamtor对象原先是由它负责显示View的动画的,现在ViewPropertyAnimatorRT类的成员函数canHandleAnimator判断它是否设置了一系列的Listener。如果设置了,就意味着Main Thread需要获得动画显示过程中的通知,这样就不能将一个动画完全地将交给Render Thread来管理。

上面提到的Listener有两个:

1. 用来监听动画更新事件的Listener。这个Listener如果设置有,那么就可以通过调用ViewPropertyAniamtor类的成员函数getUpdateListener获得。

2. 用来监听动画的开始和结束等事件的Listener。这个Listener如果设置有,那么就可以通过调用ViewPropertyAniamtor类的成员函数getListener获得。

此外,如我们前面所述,如果在View的动画显示之前,我们调用了与它关联的ViewPropertyAniamtor对象的成员函数withLayer,那么与它关联的ViewPropertyAniamtor对象就会在内部创建两个Runnable。这两个Runable用来临时地将要显示动画的View的Layer Type设置为LAYER_TYPE_HARDWARE。在这种情况下,调用与View关联的ViewPropertyAniamtor对象的成员函数hasActions得到的返回值就等于true。这意味着Main Thread需要获得动画显示开始和结束的通知,因此就不能将一个动画完全地将交给Render Thread来管理。

再者,如果要显示动画的View不支持硬件加速渲染,即调用它的成员函数isHardwareAccelerated得到的返回值等于false。很明显,Render Thread是通过硬件加速渲染的方式来显示View的动画的。因此,在这种情况下,也不能一个动画完全地将交给Render Thread来管理。

我们假设ViewPropertyAnimatorRT类的成员函数canHandleAnimator的返回值为false,那么接下来就会调用到ViewPropertyAnimatorRT类的成员函数doStartAnimation,它的实现如下所示:

class ViewPropertyAnimatorRT { ...... private void doStartAnimation(ViewPropertyAnimator parent) { int size = parent.mPendingAnimations.size(); ...... for (int i = 0; i < size; i++) {NameValuesHolder holder = parent.mPendingAnimations.get(i);int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant);final float finalValue = holder.mFromValue + holder.mDeltaValue;RenderNodeAnimator animator = new RenderNodeAnimator(property, finalValue);animator.setStartDelay(startDelay);animator.setDuration(duration);animator.setInterpolator(interpolator);animator.setTarget(mView);animator.start();mAnimators[property] = animator; } parent.mPendingAnimations.clear(); } ......}这个函数定义在文件frameworks/base/core/java/android/view/ViewPropertyAnimatorRT.java中。

当前要显示的动画保存在参数parent描述的一个ViewPropertyAnimator对象的成员变量mPendingAnimations描述的一个NameValueHodler对象列表中。ViewPropertyAnimatorRT类的成员函数doStartAnimation为这个列表的每一个NameValueHodler对象都创建一个RenderNodeAnimator对象,并且将相关的动画参数都设置到新创建的RenderNodeAnimator对象中去。当这些参数设置完毕,就可以调用新创建的RenderNodeAnimator对象的成员函数start,来它们标记为可以执行的状态。

其中,上述新创建的RenderNodeAnimator对象有一个重要的参数需要设置,就是它所关联的View,这是通过调用RenderNodeAnimator类的成员函数setTarget来完成的。RenderNodeAnimator类的成员函数setTarget在执行的过程中,就会将相关的动画注册到Render Thread中去,以便Render Thread可以执行它们。

RenderNodeAnimator类的成员函数setTarget的实现如下所示:

public class RenderNodeAnimator extends Animator { ...... public void setTarget(View view) { mViewTarget = view; setTarget(mViewTarget.mRenderNode); } ......}这个函数定义在文件frameworks/base/core/java/android/view/RenderNodeAnimator.java中。

参数view描述的就是要显示动画的View,RenderNodeAnimator类的成员函数setTarget首先是将它保存成员变量mTargetView中,接首再获得与它关联的一个Render Node传递给另外一个重载版本的成员函数setTarget处理,如下所示:

public class RenderNodeAnimator extends Animator { ...... private void setTarget(RenderNode node) { ...... mTarget = node; mTarget.addAnimator(this); } ......}这个函数定义在文件frameworks/base/core/java/android/view/RenderNodeAnimator.java中。

RenderNodeAnimator类的重载版本的成员函数setTarget首先是将参数node描述的一个Render Node保存在成员变量mTarget中,接着再通过调用RenderNode类的成员函数addAniamtor将当前正在处理的动画注册到Render Thread中去。

RenderNode类的成员函数addAniamtor的实现如下所示:

public class RenderNode { ...... public void addAnimator(RenderNodeAnimator animator) { ...... nAddAnimator(mNativeRenderNode, animator.getNativeAnimator()); mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this); } ......}这个函数定义在文件frameworks/base/core/java/android/view/RenderNode.java中。

RenderNode类的成员函数addAniamtor首先是调用另外一个成员函数nAddAnimator将参数animator描述的动画注册到Render Thread中去。

RenderNode类的成员变量mOwningView描述的是一个与当前正在处理的Render Node关联的一个View,这个View就是要显示动画的View。每一个View都有一个成员变量mAttachInfo,它指向的是一个AttachInfo对象,该AttachInfo对象描述的是一个View所属的窗口的相关信息。其中,这个AttachInfo对象有一个成员变量mViewRootImpl,它指向的是一个ViewRootImpl对象,该ViewRootImpl对象负责管理一个View所属的窗口,例如用来分发输入事件给窗口,以及用来发起窗口的绘制流程等。

RenderNode类的成员函数addAniamtor接下来要做的事情就是调用上述的ViewRootImpl对象的成员函数registerAnimatingRenderNode告诉Render Thread,当前正在处理的Render Node有动画注册到了它里面,这样Render Node就可以将渲染应用窗口的下一帧时,显示前面已经注册的动画。

接下来我们就先分析RenderNode类的成员函数nAddAnimator的实现,接着再分析ViewRootImpl类的成员函数registerAnimatingRenderNode的实现。

RenderNode类的成员函数nAddAnimator是一个JNI函数,由Native层的函数android_view_RenderNode_addAnimator实现,如下所示:

static void android_view_RenderNode_addAnimator(JNIEnv* env, jobject clazz, jlong renderNodePtr, jlong animatorPtr) { RenderNode* renderNode = reinterpret_cast(renderNodePtr); RenderPropertyAnimator* animator = reinterpret_cast(animatorPtr); renderNode->addAnimator(animator);}这个函数定义在文件frameworks/base/core/jni/android_view_RenderNode.cpp中。

参数renderNodePtr指向的是Native层的一个RenderNode对象,该RenderNode对象与要显示动画的View关联。参数animatorPtr指向的Native层的一个RenderPropertyAnimator对象,该RenderPropertyAnimator对象描述的就是要显示的动画。这里调用RenderNode类的成员函数addAnimator将参数animatorPtr描述的动画保存在参数renderNodePtr描述的Render Node的内部。

RenderNode类的成员函数addAnimator的实现如下所示:

void RenderNode::addAnimator(const sp& animator) { mAnimatorManager.addAnimator(animator);}这个函数定义在文件frameworks/base/libs/hwui/RenderNode.cpp中。

RenderNode类的成员变量mAnimatorManager描述的是一个AnimatorManager对象。这个AnimatorManager对象用来管理一个RenderNode对象所关联的动画。因此,RenderNode类的成员函数addAnimator所做的事情就是将参数animator描述的动画保存在当前正在处理的RenderNode对象内部的一个AnimatorManager对象中,这是通过调用AnimatorManager类的成员函数addAnimator完成的。

AnimatorManager类的成员函数addAnimator的实现如下所示:

void AnimatorManager::addAnimator(const sp& animator) { animator->incStrong(0); animator->attach(&mParent); mNewAnimators.push_back(animator.get());}这个函数定义在文件frameworks/base/libs/hwui/AnimatorMaager.cpp中。

在分析AnimatorManager类的成员函数addAnimator的实现之前,我们首先了解AnimatorManager类的三个成员变量:

1. mParent,它描述的是一个RenderNode对象,该RenderNode对象与当前正在处理的AnimatorManager对象关联。

2. mNewAnimators,它描述的是一个Vector,该Vector用来保存新增加的动画。

3. mAnimators,它描述的也是一个Vector。在绘制应用程序窗口的下一帧之前,新增加的动画会从成员变量mNewAnimators描述的Vector转移到成员变量mAnimators描述的Vector中去等待处理。

AnimatorManager类的成员函数addAnimator首先是增加参数animator描述的一个BaseRenderNodeAnimator对象的引用计数,因为后面要将它添加成员变量mNewAnimators描述的一个Vector中去。此外,AnimatorManager类的成员函数addAnimator还会调用参数animator描述的一个BaseRenderNodeAnimator对象的成员函数attach将该BaseRenderNodeAnimator对象与要显示动画的Render Node进行关联。

这一步执行完成之后,我们就将要显示的动画设置到目标View关联的一个RenderNode对象内部的一个AnimatorManager对象中去了。返回到Java层的RenderNode类的成员函数addAniamtor中,接下来我们继续分析ViewRootImpl类的成员函数registerAnimatingRenderNode的实现,以便可以了解Render Thread知道有Render Node有新的动画需要显示的过程。

ViewRootImpl类的成员函数registerAnimatingRenderNode的实现如下所示:

public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { ...... public void registerAnimatingRenderNode(RenderNode animator) { if (mAttachInfo.mHardwareRenderer != null) {mAttachInfo.mHardwareRenderer.registerAnimatingRenderNode(animator); } else {if (mAttachInfo.mPendingAnimatingRenderNodes == null) { mAttachInfo.mPendingAnimatingRenderNodes = new ArrayList();}mAttachInfo.mPendingAnimatingRenderNodes.add(animator); } } ......}这个函数定义在文件frameworks/base/core/java/android/view/ViewRootImpl.java中。

当ViewRootImpl类的成员变量mAttachInfo指向的一个AttachInfo对象的成员变量mHardwareRenderer的值不等于null的时候,它指向的是一个ThreadedRenderer对象。在这种情况下,ViewRootImpl类的成员函数registerAnimatingRenderNode会直接调用该ThreadedRenderer对象的成员函数registerAnimatingRenderNode将参数animator描述的一个Render Node注册到Render Thread中去,以便Render Thread知道哪些Rendr Node有新的动画需要显示。

另一方面,当ViewRootImpl类的成员变量mAttachInfo指向的一个AttachInfo对象的成员变量mHardwareRenderer的值等于null的时候,这意味着应用程序窗口使用软件渲染或者使用硬件加速渲染但是硬件加速渲染环境还没有初始化好。在这两种情况下,都是先将参数animator描述的Render Node保存在成员变量mAttachInfo指向的一个AttachInfo对象的成员变量mPendingAnimatingRenderNodes描述的一个列表中等待处理。

从前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文可以知道,当使用硬件加速渲染时,应用程序窗口的渲染是从调用ThreadedRenderer类的成员函数draw开始的,它的实现如下所示:

public class ThreadedRenderer extends HardwareRenderer {...... @Overridevoid draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) { ......updateRootDisplayList(view, callbacks); ......if (attachInfo.mPendingAnimatingRenderNodes != null) { final int count = attachInfo.mPendingAnimatingRenderNodes.size(); for (int i = 0; i < count; i++) {registerAnimatingRenderNode( attachInfo.mPendingAnimatingRenderNodes.get(i)); } attachInfo.mPendingAnimatingRenderNodes.clear(); // We don't need this anymore as subsequent calls to // ViewRootImpl#attachRenderNodeAnimator will go directly to us. attachInfo.mPendingAnimatingRenderNodes = null; }int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,recordDuration, view.getResources().getDisplayMetrics().density); if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) { attachInfo.mViewRootImpl.invalidate(); }} ...... }这个函数定义在文件frameworks/base/core/java/android/view/ThreadedRenderer.java中。

ThreadedRenderer类的成员函数draw在调用成员函数updateRootDisplayList构建应用程序窗口的Display List之后,并且调用成员函数nSyncAndDrawFrame渲染应用程序窗口的Display List之前,会调用成员函数registerAnimatingRenderNode处理保存在上面提到的AttachInfo对象的成员变量mPendingAnimatingRenderNodes描述的一个列表中的每一个Render Node。

ThreadedRenderer类的成员函数registerAnimatingRenderNode的实现如下所示:

public class ThreadedRenderer extends HardwareRenderer {...... @Override void registerAnimatingRenderNode(RenderNode animator) { nRegisterAnimatingRenderNode(mRootNode.mNativeRenderNode, animator.mNativeRenderNode); }...... }这个函数定义在文件frameworks/base/core/java/android/view/ThreadedRenderer.java中。

ThreadedRenderer类的成员函数registerAnimatingRenderNode调用另外一个成员函数nRegisterAnimatingRenderNode将参数animator描述的一个Render Node注册到Render Thread中去。

ThreadedRenderer类的成员函数nRegisterAnimatingRenderNode是一个JNI函数,由Native层的函数android_view_ThreadedRenderer_registerAnimatingRenderNode实现,如下所示:

static void android_view_ThreadedRenderer_registerAnimatingRenderNode(JNIEnv* env, jobject clazz, jlong rootNodePtr, jlong animatingNodePtr) { RootRenderNode* rootRenderNode = reinterpret_cast(rootNodePtr); RenderNode* animatingNode = reinterpret_cast(animatingNodePtr); rootRenderNode->attachAnimatingNode(animatingNode);}这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。

参数rootNodePtr指向的是一个RootRenderNode对象,该RootRenderNode对象描述的是应用程序窗口的Root Render Node,这里主要就是将参数animatorNodePtr描述的一个Render Node注册在上述的RootRenderNode对象的内部,这是通过调用RootRenderNode类的成员函数attachAnimatingNode实现的。

RootRenderNode类的成员函数attachAnimatingNode的实现如下所示:

class RootRenderNode : public RenderNode, ErrorHandler {public: ...... void attachAnimatingNode(RenderNode* animatingNode) { mPendingAnimatingRenderNodes.push_back(animatingNode); } ......private: ...... std::vector< sp>mPendingAnimatingRenderNodes;};这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中,

电脑资料

RootRenderNode类的成员函数attachAnimatingNode将参数animatingNode描述的一个Render Node保存成员变量mPendingAnimatingRenderNodes描述的一个Vector中。保在这个Vector中的Render Node将会在应用程序窗口的下一帧被渲染时得到处理。

从前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文可以知道,Render Thread在渲染应用程序窗口的下一帧时,会调用CanvasContext类的成员函数prepareTree将应用程序窗口的Display List从Main Thread同步到Render Thread,如下所示:

void CanvasContext::prepareTree(TreeInfo& info) { ...... info.renderer = mCanvas; ...... mAnimationContext->startFrame(info.mode); mRootRenderNode->prepareTree(info); mAnimationContext->runRemainingAnimations(info); ...... int runningBehind = 0; // TODO: This query is moderately expensive, investigate adding some sort // of fast-path based off when we last called eglSwapBuffers() as well as // last vsync time. Or something. mNativeWindow->query(mNativeWindow.get(),NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind); info.out.canDrawThisFrame. = !runningBehind; if (info.out.hasAnimations || !info.out.canDrawThisFrame) { if (!info.out.requiresUiRedraw) {// If animationsNeedsRedraw is set don't bother posting for an RT anim// as we will just end up fighting the UI thread.mRenderThread.postFrameCallback(this); } }}这个函数定义定义在文件frameworks/base/libs/hwui/renderthread/CanvasContext.cpp中。

CanvasContext类的成员函数prepareTree的详细实现分析可以参考前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文,这里我们主要关注与动画相关的两个操作:

1. 第一个操作是在调用RenderNode类的成员函数prepareTree同步应用程序窗口的Display List之前,调用成员变量mAnimationContext描述的一个AnimationContext对象的成员函数startFrame执行一些动画显示的准备工作。

2. 第二个操作是在调用RenderNode类的成员函数prepareTree同步应用程序窗口的Display List之后,调用成员变量mAnimationContext描述的一个AnimationContext对象的成员函数runRemainingAnimations更新剩下的还未完成的动画。

接下来我们就分别分析这两个操作的执行过程,即分析AnimationContext类的成员函数startFrame和runRemainingAnimations的实现。不过,在分析这两个函数的实现之前,我们首先要了解CanvasContext类的成员变量mAnimationContext的初始化过程。

从前面Android应用程序UI硬件加速渲染环境初始化过程分析一文可以知道,在Android应用程序的硬件加速渲染环境的初始化过程中,会创建一个RenderProxy对象,如下所示:

static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz, jboolean translucent, jlong rootRenderNodePtr) { RootRenderNode* rootRenderNode = reinterpret_cast(rootRenderNodePtr); ContextFactoryImpl factory(rootRenderNode); return (jlong) new RenderProxy(translucent, rootRenderNode, &factory);}这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。

在调用RenderProxy类的构造函数创建一个RenderProxy对象的时候,传递进去的第三个参数是一个ContextFactoryImpl对象。

我们继续看RenderProxy类的构造函数的实现,如下所示:

CREATE_BRIDGE4(createContext, RenderThread* thread, bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory) { return new CanvasContext(*args->thread, args->translucent,args->rootRenderNode, args->contextFactory);}RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory) : mRenderThread(RenderThread::getInstance()) , mContext(0) { SETUP_TASK(createContext); args->translucent = translucent; args->rootRenderNode = rootRenderNode; args->thread = &mRenderThread; args->contextFactory = contextFactory; mContext = (CanvasContext*) postAndWait(task); mDrawFrameTask.setContext(&mRenderThread, mContext);}这个函数定义在文件frameworks/base/libs/hwui/renderthread/RenderProxy.cpp中。

RenderProxy类的构造函数首先是向Render Thread的Task Queue发送一个Task,并且等待该Task执行完成。上述Task在执行的时候,会调用由宏CREATE_BRIDGE4声明的函数createContext。该函数所做的事情就是创建一个CanvasContext对象。该CanvasContext对象最终保存在RenderProxy类的成员变量mContext中。

在调用CanvasContext类的构造函数创建一个CanvasContext对象的时候,传递进去的第四个参数就是在前面分析的函数android_view_ThreadedRenderer_createProxy声明的一个ContextFactoryImpl对象。

CanvasContext类的构造函数的实现如下所示:

CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory) :...... { mAnimationContext = contextFactory->createAnimationContext(mRenderThread.timeLord()); ......}这个函数定义在文件frameworks/base/libs/hwui/renderthread/CanvasContext.cpp中。

从这里就可以看出,CanvasContext的成员变量mAnimationContext指向的一个AnimationContext对象是由参数contextFactory描述的一个IContextFactory接口的成员函数createAnimationContext创建的。

从前面的调用过程可以知道,参数contextFactory指向的实际上是一个ContextFactoryImpl对象,它的成员函数createAnimationContext的实现如下所示:

class ContextFactoryImpl : public IContextFactory {public: ContextFactoryImpl(RootRenderNode* rootNode) : mRootNode(rootNode) {} virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) { return new AnimationContextBridge(clock, mRootNode); }private: RootRenderNode* mRootNode;};这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。

ContextFactoryImpl类的成员函数createAnimationContext创建的是一个AnimationContextBridge对象,由此就可见,CanvasContext的成员变量mAnimationContext实际指向的一个AnimationContextBridge对象。

明白了这一点之后,回到前面分析的CanvasContext类的成员函数prepareTree中,它在同步应用程序窗口的Display List之前,调用了AnimationContextBridge类的成员函数startFrame执行一些动画显示的准备工作。

AnimationContextBridge类的成员函数startFrame的实现如下所示:

class AnimationContextBridge : public AnimationContext {public: ...... // Marks the start of a frame, which will update the frame. time and move all // next frame. animations into the current frame. virtual void startFrame(TreeInfo::TraversalMode mode) { if (mode == TreeInfo::MODE_FULL) {mRootNode->doAttachAnimatingNodes(this); } AnimationContext::startFrame(mode); } ......private: spmRootNode; ......};这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。

参数mode的值等于TreeInfo::MODE_FULL,表示目前Render Thread是处于同步应用程序窗口的Display List的过程中。在这种情况下,AnimationContextBridge类的成员函数startFrame执行两个操作:

1. 调用成员变量mRootNode描述的一个RootRenderNode对象的成员函数doAttachAnimatingNodes处理它内部保存的有动画显示的Render Node。

2. 调用父类AnimationContext的成员函数startFrame继续执行一些动画显示之前的准备工作。

接下来,我们就先分析RootRenderNode类的成员函数doAttachAnimatingNodes的实现,再分析AnimationContext类的成员函数startFrame的实现。

RootRenderNode类的成员函数doAttachAnimatingNodes的实现如下所示:

class RootRenderNode : public RenderNode, ErrorHandler {public: ...... void doAttachAnimatingNodes(AnimationContext* context) { for (size_t i = 0; i < mPendingAnimatingRenderNodes.size(); i++) {RenderNode* node = mPendingAnimatingRenderNodes[i].get();context->addAnimatingRenderNode(*node); } mPendingAnimatingRenderNodes.clear(); }private: ...... std::vector< sp>mPendingAnimatingRenderNodes;};这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。

RootRenderNode类的成员函数doAttachAnimatingNodes遍历保存在成员变量mPendingAnimatingRenderNodes描述的一个Vector中的每一个RenderNode对象,并且分别调用参数context描述的一个AnimationContextBridge对象的成员函数addAnimatingRenderNode对它们进行处理。

AnimationContextBridge类的成员函数addAnimatingRenderNode是从父类AnimationContext继承下来的,它的实现如下所示:

void AnimationContext::addAnimatingRenderNode(RenderNode& node) { if (!node.animators().hasAnimationHandle()) { AnimationHandle* handle = new AnimationHandle(node, *this); addAnimationHandle(handle); }}这个函数定义在文件frameworks/base/libs/hwui/AnimationContext.cpp中。

参数node指向的是一个RenderNode对象,调用它的成员函数animators可以获得它内部的一个AnimatorManager对象。如果该AnimatorManager对象还没有设置过一个AnimationHandle对象,那么AnimationContext类的成员函数addAnimatingRenderNode就为其设置一个AnimationHandle对象,并且这个AnimationHandle对象会通过AnimationContext类的成员函数addAnimationHandle保存在当前正在处理的一个AnimationContext对象的内部。

AnimationContext类的成员函数addAnimationHandle的实现如下所示:

void AnimationContext::addAnimationHandle(AnimationHandle* handle) { handle->insertAfter(&mNextFrameAnimations);}这个函数定义在文件frameworks/base/libs/hwui/AnimationContext.cpp中。

AnimationContext类的成员函数addAnimationHandle所做的事情就是将参数handle描述的一个AnimationHandle对象插入成员变量mNextFrameAnimations描述的一个AnimationHandle列表中。

由此我们就可以推断出,只要AnimationContext类的成员变量NextFrameAnimations描述的一个AnimationHandle列表不为空,那么就意味着Render Thread在渲染应用程序窗口的下一帧的时候,有Render Node需要显示动画。

这一步执先完成之后,返回到AnimationContextBridge类的成员函数startFrame中,接下来它所做的事情就是调用父类AnimationContext的成员函数startFrame继续执行一些动画显示之前的准备工作。

AnimationContext的成员函数startFrame的实现如下所示:

void AnimationContext::startFrame(TreeInfo::TraversalMode mode) { ...... AnimationHandle* head = mNextFrameAnimations.mNextHandle; if (head) { mNextFrameAnimations.mNextHandle = NULL; mCurrentFrameAnimations.mNextHandle = head; head->mPreviousHandle = &mCurrentFrameAnimations; } ......}这个函数定义在文件frameworks/base/libs/hwui/AnimationContext.cpp中。

AnimationContext的成员函数startFrame所做的事情就是将成员变量mNextFrameAnimations描述的一个AnimationHandle列表转移到另外一个成员变量mCurrentFrameAnimations中。

由此我们就可以推断出,只要AnimationContext类的成员变量mCurrentFrameAnimations描述的一个AnimationHandle列表不为空,那么就意味着Render Thread在渲染应用程序窗口的当前帧的时候,有Render Node需要显示动画。

这一步执行完成之后,返回到CanvasContext类的成员函数prepareTree中,当它同步同步应用程序窗口的Display List之后,就调用成员变量mAnimationContext描述的一个AnimationContextBridge对象的成员函数runRemainingAnimations更新剩下的还未完成的动画。

AnimationContextBridge类的成员函数runRemainingAnimations的实现如下所示:

class AnimationContextBridge : public AnimationContext {public: ...... // Runs any animations still left in mCurrentFrameAnimations virtual void runRemainingAnimations(TreeInfo& info) { AnimationContext::runRemainingAnimations(info); ...... } ......};这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。

AnimationContextBridge类的成员函数runRemainingAnimations主要是通过调用父类AnimationContext的成员函数runRemainingAnimations更新剩下的还未完成的动画。

AnimationContext类的成员函数runRemainingAnimations的实现如下所示:

void AnimationContext::runRemainingAnimations(TreeInfo& info) { while (mCurrentFrameAnimations.mNextHandle) { AnimationHandle* current = mCurrentFrameAnimations.mNextHandle; AnimatorManager& animators = current->mRenderNode->animators(); animators.pushStaging(); animators.animateNoDamage(info); ...... }}这个函数定义在文件frameworks/base/libs/hwui/AnimationContext.cpp中。

前面提到,应用程序窗口当前帧要显示动画都记录在AnimationContext类的成员mCurrentFrameAnimations描述的一个AnimationHandle列表中。因此,AnimationContext类的成员函数runRemainingAnimations就遍历这个列表中的每一个AnimationHandle,并且获得与其关联的一个AnimatorManager。有了这些AnimatorManager之的,就可以调用它们的成员函数pushStaging和animateNoDamage来执行动画,其实就是计算动画的下一帧参数,以便应用在目标Render Node上。

AnimatorManager类的成员函数pushStaging的实现如下所示:

void AnimatorManager::pushStaging() { if (mNewAnimators.size()) { ...... move_all(mNewAnimators, mAnimators); } for (vector::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) { (*it)->pushStaging(mAnimationHandle->context()); }}这个函数定义在文件frameworks/base/libs/hwui/AnimatorManager.cpp中。

从前面的分析可以知道,一开始的时候,应用程序窗口新增加的动画都保存在AnimatorManager类的成员变量mNewAnimators描述的一个Vector中,这里将它们移动至AnimatorManager类的另外一个成员变量mAnimators中,类似于我们在前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文提到的将Display List从Main Thread同步到Render Thread一样。

最后,AnimatorManager类的成员函数pushStaging遍历已经移动至新列表中的每一个动画,并且调用它们的成员函数pushStaging,用来同步它们的开始和结束状态,也就是检查动画是否已经被start或者已经end了。如果已经被start,那么就执行一些动画开始执行前的准备工作,例如计算动画的开始时间。如果已经end,就发出事件通知给侦听者。

这一步执先完成之的,回到前面分析的AnimationContext类的成员函数runRemainingAnimations中,接下来就调用AniamtorManager类的成员函数animateNoDamage执行动画,它的实现如下所示:

void AnimatorManager::animateNoDamage(TreeInfo& info) { if (!mAnimators.size()) return; animateCommon(info);}这个函数定义在文件frameworks/base/libs/hwui/AnimatorManager.cpp中。

AniamtorManager类的成员函数animateNoDamage通过调用另外一个成员函数animateCommon来执行当前正在处理的AnimatorManager对象关联的动画,也就是某一个Render Node关联的动画。

AniamtorManager类的成员函数animateCommon的实现如下所示:

uint32_t AnimatorManager::animateCommon(TreeInfo& info) { AnimateFunctor functor(info, mAnimationHandle->context()); std::vector< BaseRenderNodeAnimator* >::iterator newEnd; newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor); mAnimators.erase(newEnd, mAnimators.end()); mAnimationHandle->notifyAnimationsRan(); return functor.dirtyMask;}这个函数定义在文件frameworks/base/libs/hwui/AnimatorManager.cpp中。

前面提到,当前正在处理的AnimatorManager对象关联的动画都保存其成员变量mAnimators描述的一个Vector,因此这里就会通过函数std::remove_if来遍历这些动画,并且对于每一个动画,都通过本地变量functor描述的一个AnimatorFunctor对象的操作符重载函数()来执行它的当前帧。

AnimatorFunctor类的操作符重载函数()执行完成动画的当前帧之后,如果动画已经完成,即没有下一帧了,那么它就会返回一个true值给调用者,这样会导致函数std::remove_if将该动画移动至列表的末尾位置。

AnimatorFunctor类的操作符重载函数()的实现如下所示:

class AnimateFunctor {public: AnimateFunctor(TreeInfo& info, AnimationContext& context): dirtyMask(0), mInfo(info), mContext(context) {} bool operator() (BaseRenderNodeAnimator* animator) { dirtyMask |= animator->dirtyMask(); bool remove = animator->animate(mContext); if (remove) {animator->decStrong(0); } else {if (animator->isRunning()) { mInfo.out.hasAnimations = true;}if (CC_UNLIKELY(!animator->mayRunAsync())) { mInfo.out.requiresUiRedraw = true;} } return remove; } uint32_t dirtyMask;private: TreeInfo& mInfo; AnimationContext& mContext;};这个函数定义在文件frameworks/base/libs/hwui/AnimatorManager.cpp中。

AnimatorFunctor类的操作符重载函数()主要就是调用参数animator指向的一个BaseRenderNodeAnimator对象的成员函数animate来执行该BaseRenderNodeAnimator对象所描述的动画的当前帧。

当BaseRenderNodeAnimator类的成员函数animate的返回值等于true的时候,就表示参数animator描述的动画已经显示结束了,这意味着它可以从我们上面描述的动画列表中删除。

另一方面,当BaseRenderNodeAnimator类的成员函数animate的返回值等于false的时候,就表示参数animator描述的动画还没有显示完成。这时候如果该动画没有被中途停止,即调用参数animator指向的一个BaseRenderNodeAnimator对象的成员函数isRunning的返回值等于true,那么就会将当前正在处理的AnimatorFunctor对象的成员变量mInfo指向的一个TreeInfo对象的成员变量out描述的一个Out结构体的成员变量hasAnimations的值设置为true。这样做的作用是什么呢?

我们首先梳理一下Render Thread目前所处的位置。目前Render Thread正处于将Main Thread的Display List同步到Render Thread的过程中,只不过目前正在处理的动画相关的操作。Render Thead在执行这个同步过程之前,会构造一个TreeInfo对象,这一点可以参考前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文分析的DrawFrameTask类的成员函数run。DrawFrameTask类的成员函数run会将该TreeInfo对象传递给接下来要调用的函数。AnimatorFunctor类的操作符重载函数()就是被调用的其中一个函数,并且它的成员变量mInfo指向的TreeInfo对象就上述在DrawFrameTask类的成员函数run构造的TreeInfo对象。

通过将这个TreeInfo对象的成员变量out描述的一个Out结构体的成员变量hasAnimations的值,就可以让Render Thread通知应用程序窗口的下一帧是否还有动画需要显示。再结合上述TreeInfo对象的成员变量out描述的一个Out结构体的另外一个成员变量requiresUiRedraw的值,Render Thread就可以知道它是自动执行那些未完成的动画,还是等待Main Thread通知它执行。

当参数animator指向的一个BaseRenderNodeAnimator对象描述的是一个同步动画时,那么调用它的成员函数mayRunAsync的返回值就等于false,这时候上述TreeInfo对象的成员变量out描述的一个Out结构体的成员变量requiresUiRedraw的值就会被设置为true,这将导致未完成的动画要由Main Thread通知Render Thread来执行。这个逻辑同时也意味着只有当所有未完成的动画都是异步动画时,Render Thread才可以自动执行它们。这个自动执行未完成的异步动画的过程我们后面再详细分析。

这一步执行完成之后,返回到前面分析的AniamtorManager类的成员函数animateCommon,也就是函数std::remove_if执行完毕的位置。函数std::remove_if执行完毕,会返回一个iterator。这个iterator指向的是被移动至列表末尾的第一个动画。这意味着从这个iterator开始,到列表的结束,保存的动画都是已经执行完成了的,因此就可以将它们从列表中删除。

最后,AniamtorManager类的成员函数animateCommon调用成员变量mAnimationHandle指向的一个AnimationHandle对象的成员函数notifyAnimationsRan,用来发送一个动画帧执先完成的通知。从上面的分析可以知道,这个AnimationHandle对象是在添加一个新的动画添到Render Thread时创建并且设置给关联的AnimatorManager对象的,具体可以参数前面分析的AnimationContext类的成员函数addAnimatingRenderNode的实现。

AnimationHandle类的成员函数notifyAnimationsRan的实现如下所示:

void AnimationHandle::notifyAnimationsRan() { removeFromList(); if (mRenderNode->animators().hasAnimators()) { mContext.addAnimationHandle(this); } else { release(); }}这个函数定义在文件frameworks/base/libs/hwui/AnimationContext.cpp中。

AnimationHandle类的成员函数notifyAnimationsRan首先是调用成员函数removeFromList将当前正处理的一个AnimationHandle对象从所在的列表中移除。回忆前面的分析,当前正处理的一个AnimationHandle对象位于的列表正是AnimationContext类的成员变量mCurrentFrameAnimations描述的一个AnimationHandle列表。

将当前正处理的一个AnimationHandle对象从所在的列表中移除之后,AnimationHandle类的成员函数notifyAnimationsRan再判断它所关联的Render Node是否还有未执行完成的动画。如果有的话,那么就会调用成员变量mContext描述的一个AnimationContext对象的成员函数addAnimationHandle将当前正处理的一个AnimationHandle对象重新添加到另外一个列表去。

从前面的分析可以知道,AnimationContext类的成员函数addAnimationHandle会将指定的AnimationContext对象添加到其成员变量mNextFrameAnimations描述的一个列表中。又从前面分析的AnimationContext类的成员函数startFrame可以知道,保存在AnimationContext类的成员变量mNextFrameAnimations描述的列表中的AnimationContext对象在应用程序窗口动画显示之前,会被移动至AnimationContext类的成员变量mCurrentFrameAnimations描述的另一个列表中。

这意味动画的执行过程如下所示:

1. 在应用程序窗口每一帧的渲染过程中,如果需要显示一个动画,那么就需要先将一个AnimationContext对象添加到AnimationContext类的成员变量mNextFrameAnimations描述的列表中。

2. 当一个动画需要执行一帧时,与它关联的AnimationContext对象将会AnimationContext类的成员变量mNextFrameAnimations描述的列表转移至成员变量mCurrentFrameAnimations描述的另外一个列表。

3. 当一个动画的一帧执行完成时,与它关联的AnimationContext对象又会从AnimationContext类的成员变量mCurrentFrameAnimations描述的列表移除。

4. 当一个动画的一帧执行完成时,如果整个动画还未执行完成,那么它关联的AnimationContext对象又会重新添加到AnimationContext类的成员变量mNextFrameAnimations描述的列表中。接着下来又重复执行前面的第1步。

另一方面,如果一个Render Node关联的所有动画均已执行完毕,那么AnimationHandle类的成员函数notifyAnimationsRan会调用另外一个成员函数release将它内部的一个AnimatorManager对象关联的一个AnimationHandle对象移除。这意味着下次我们再为该Render Node时,需要重新为它创建和关联一个新的AnimationHandle对象,如前面分析的AnimationContext类的成员函数addAnimatingRenderNode所示。

这样在应用程序窗口每一个帧的渲染过程中涉及到的动画的执行过程我们就分析完毕。注意,这个动画执行过程是在Main Thread和Render Thread完全同步的条件下执行的,并且这里说的动画执行,只是计算出动画对目标Render Node的属性影响。例如,计算出动画对目标Render Node在X轴和Y轴的位置。新计算出来的属性将会在目标Render Node的Display List的绘制命令转化为Open GL命令执行时得到应用,从而就完成一个动画帧的显示。

理解了动画的正常执行过程之后,也就是由Main Thread驱动Render Thread执行的过程,我们再来看Render Thread在不需要Main Thread干预的情况下自动执行动画的过程。在分析之前,我们首先理解一下前面提到的同步动画和异步动画的概念。

从前面的分析可以知道,如果我们使用Render Thread来计算和执行动画,那么动画就会被封装成一个RenderNodeAnimator对象,如前面分析的ViewPropertyAnimatorRT类的成员函数doStartAnimation所示。

RenderNodeAnimator类有一个成员函数setAllowRunningAsynchronously,允许在一个动画执行之前,设置为是否可以异步执行,如下所示:

public class RenderNodeAnimator extends Animator { ...... @Override public void setAllowRunningAsynchronously(boolean mayRunAsync) { checkMutable(); nSetAllowRunningAsync(mNativePtr.get(), mayRunAsync); } ......}这个函数定义在文件frameworks/base/core/java/android/view/RenderNodeAnimator.java中。

当参数mayRunAsync的值等于true时,就表示将一个动画设置为异步动画;否则的话,就设置为同步动画。这个信息会通过调用RenderNodeAnimator类的另外一个成员函数nSetAllowRunningAsync同步到Native层关联的同样是用来描述动画的一个BaseRenderNodeAnimator对象中去。

RenderNodeAnimator类的成员函数nSetAllowRunningAsync是一个JNI函数,由Native层的函数setAllowRunningAsync实现,如下所示:

static void setAllowRunningAsync(JNIEnv* env, jobject clazz, jlong animatorPtr, jboolean mayRunAsync) { BaseRenderNodeAnimator* animator = reinterpret_cast(animatorPtr); animator->setAllowRunningAsync(mayRunAsync);}这个函数定义在文件frameworks/base/core/jni/android_view_RenderNodeAnimator.cpp中。

参数animatorPtr描述的就是Native用来描述动画的一个BaseRenderNodeAnimator对象,这里调用它的成员函数setAllowRunningAsync设置它是一个同步动画还是一个异步动画。

BaseRenderNodeAnimator类的setAllowRunningAsync的实现如下所示:

class BaseRenderNodeAnimator : public VirtualLightRefBase { ......public: ...... ANDROID_API void setAllowRunningAsync(bool mayRunAsync) { mMayRunAsync = mayRunAsync; } bool mayRunAsync() { return mMayRunAsync; } ......};这两个函数定义在文件frameworks/base/libs/hwui/Animator.h中。

BaseRenderNodeAnimator类的setAllowRunningAsync将动画的同步异步信息记录在成员变量mMayRunAsync中。这样就可以通过调用另外一个成员函数mayRunAsync就可以知道一个动画是同步的还是异步,如前面分析的AnimatorFunctor类的操作符重载函数()所示。

了解了一个动画的同步异步概念之后,回到前面分析的CanvasContext类的成员函数prepareTree中,当它同步完成应用程序窗口的Display List以及处理过应用程序窗口的动画之后,如果应用程序窗口还有未完成的动画,并且这些都是异步动画,那么就会注册一个IFrameCallback接口到Render Thread中。为了方便描述,我们重新将这部分代码列出来,如下所示:

void CanvasContext::prepareTree(TreeInfo& info) { ...... info.renderer = mCanvas; ...... mAnimationContext->startFrame(info.mode); mRootRenderNode->prepareTree(info); mAnimationContext->runRemainingAnimations(info); ...... if (info.out.hasAnimations || !info.out.canDrawThisFrame) { if (!info.out.requiresUiRedraw) {// If animationsNeedsRedraw is set don't bother posting for an RT anim// as we will just end up fighting the UI thread.mRenderThread.postFrameCallback(this); } }}这个函数定义在文件frameworks/base/libs/hwui/renderthread/CanvasContext.cpp中。

根据我们前面的分析,当应用程序窗口还有未完成的动画时,参数info指向的一个TreeInfo对象的成员变量out指向的一个Out结构体的成员变量hasAnimations的值就等于true,而当这些未完成的动画都是异步动画时,上述的Out结构体的成员变量requiresUiRedraw的值就等于false。在这种情况下,CanvasContext类的成员函数prepareTree就会注册一个IFrameCallback接口到Render Thread中。这个注册过程可以参考前面Android应用程序UI硬件加速渲染环境初始化过程分析一文。

上面注册的IFrameCallback接口CanvasContext类实现,从前面Android应用程序UI硬件加速渲染环境初始化过程分析一文可以知道,当下一个Vsync信号到来的时候,Render Thread就会调用CanvasContext类的成员函数doFrame。

CanvasContext类的成员函数doFrame的实现如下所示:

void CanvasContext::doFrame() { ...... TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState()); prepareTree(info); if (info.out.canDrawThisFrame) { draw(); }}这个函数定义在文件frameworks/base/libs/hwui/renderthread/CanvasContext.cpp中。

CanvasContext类的成员函数doFrame首先是构造一个mode值等于TreeInfo::MODE_RT_ONLY的TreeInfo对象,接着再使用这个TreeInfo对象来调用我们前面分析过的成员函数prepareTree。从前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文可以知道,当传递给anvasContext类的成员函数prepareTree的TreeInfo对象的mode值等于TreeInfo::MODE_RT_ONLY时,它就仅仅会执行动画相关的操作,以及更新设置了Layer的Render Node的操作,而不会执行同步应用程序窗口的Display List的操作。

执行完成动画相关的操作之后,CanvasContext类的成员函数doFrame接下来在允许绘制应用程序窗口下一帧的条件下,也就是在Surface Flinger不是太忙的时候,就会调用另外一个成员函数draw渲染应用程序窗口的Display List,也就是使用Open GL命令来渲染应用程序窗口的UI,并且将得到的图形缓冲区提交给Surface Flinger进行合成以及显示在屏幕上。

注意,前面在调用CanvasContext类的成员函数prepareTree的过程中,如果那些未完成的异步动画还未执行完成,那么它又会继续向Render Thread注册一个IFrameCallback接口,这样就会使得下一个Vsync信号到来的时候,CanvasContext类的成员函数doFrame又会被调用。这个过程直至所有的异步动画都执行完成为止。这样就使得Render Thread可以在没有Main Thread干预的条件下自动执行动画。

这样,Android在动画显示过程中的两个优化点都分析完成了,从中我们就可以看到Render Thread对提高动画流畅性的贡献。至些,Android应用程序UI硬件加速渲染技术这个系列的文章我们就全部学习完成了。要重新学习,请参考前面Android应用程序UI硬件加速渲染技术简要介绍和学习计划一文。更多的信息也可以关注老罗的 :weibo.com/shengyangluo。

篇2:Android应用程序UI硬件加速渲染的预加载资源地图集服

我们知道,Android系统在启动的时候,会对一些系统资源进行预加载,这样不仅使得应用程序在需要时可以快速地访问这些资源,还使得这些资源能够在不同应用程序之间进行共享。在硬件加速渲染环境中,这些预加载资源还有进一步优化的空间。Android系统提供了一个地图集服务,负责将预加载资源合成为一个纹理上传到GPU去,并且能够在所有的应用程序之间进行共享。本文就详细分析这个预加载资源地图集服务的实现原理。

资源预加载是发生在Zygote进程的,然后Zygote进程fork了应用程序进程,于是就使得预加载的资源可以在Zygote进程与所有的应用程序进程进行共享。这种内存共享机制是由Linux进程创建方式决定的。也就是说,父进程fork子进程之后,只要它们都不去修改某一块内存,那么这块内存就可以在父进程和子进程之间进行共享。一旦父进程或者子进程修改了某一块内存,那么Linux内核就会通过一种称为COW(Copy On Wrtie)的技术为要修改的进程创建一块内存拷贝出来,这时候被修改的内存就不再可以共享。

对于预加载资源来说,它们都是只读的,因此就可以保证它们在Zygote进程与所有的应用程序进程进行共享。这在应用程序UI使用软件方式渲染时可以工作得很好。但是当应用程序UI使用硬件加速渲染时,情况就发生了变化。资源一般是作为纹理来使用的。这意味着每一个应用程序都会将它要使用的预加载资源作为一个纹理上传到GPU去,如图1所示:

图1 应用程序独立将预加载资源上传到GPU

因此,这种做法会浪费GPU内存。为了节省GPU内存,Android系统在System进程中运行了一个Asset Atlas Service。这个Asset Atlas Service将预加载资源合成为一个纹理,并且上传到GPU去。应用程序进程可以向Asset Atlas Service请求上传后的纹理,从而使得它们不需要再单独去上传一份,这样就可以起到在GPU级别共享的作用,如图2所示:

图2 应用程序在GPU级别共享预加载资源

在图2中,最右侧显示的是应用程序进程的Render Thread,它们通过Asset Atlas Service获得已经上传到GPU的预加载资源纹理,这样就可以直接使用它们,而不再需要独立上传。

接下来,我们从Zygote进程预加载资源、System进程合成资源为纹理并且上传到GPU,以及应用程序使用上传后的纹理三个过程来描述预加载资源地图集机制,以便可以更好地理解应用程序是如何做到在GPU级别共享预加载资源的。

我们首先看Zygote进程预加载资源的过程。从前面Android系统进程Zygote启动过程的源代码分析一文可以知道, Zygote进程在Java层的入口点为ZygoteInit类的静态成员函数main,它的实现如下所示:

public class ZygoteInit { ...... public static void main(String argv[]) { try {......registerZygoteSocket(socketName);......preload();......if (startSystemServer) { startSystemServer(abiList, socketName);}......runSelectLoop(abiList);...... } catch (MethodAndArgsCaller caller) {...... } catch (RuntimeException ex) {...... } } ......}这个函数定义在文件frameworks/base/core/java/com/android/internal/os/ZygoteInit.java中。

ZygoteInit类的静态成员函数main的执行流程如下所示:

1. 调用成员函数registerZygoteSocket创建一个Server端的Socket,用来与运行在System进程中的ActivityManagerService服务通信,也就是用来接收ActivityManagerService发送过来的创建应用程序进程的请求。

2. 调用成员函数preload执行预加载资源的操作。

3. 调用成员函数startSystemServer启动System进程。

4. 调用成员函数runSelectLoop进入一个循环中等待和处理ActivityManagerService服务发送过来的创建应用程序进程的请求。

这里我们只关注资源预加载的过程,即ZygoteInit类的成员函数preload的实现,如下所示:

public class ZygoteInit { ...... static void preload() { Log.d(TAG, “begin preload”); preloadClasses(); preloadResources(); preloadOpenGL(); preloadSharedLibraries(); // Ask the WebViewFactory to do any initialization that must run in the zygote process, // for memory sharing purposes. WebViewFactory.prepareWebViewInZygote(); Log.d(TAG, “end preload”); } ......}

这个函数定义在文件frameworks/base/core/java/com/android/internal/os/ZygoteInit.java中。

Zygote进程需要预加载的东西很多,包括:

1. 预加载系统类,这是通过调用ZygoteInit类的静态成员函数preloadClasses实现的。

2. 预加载系统资源,这是通过调用ZygoteInit类的静态成员函数preloadResources实现的。

3. 预加载Open GL资源,这是通过调用ZygoteInit类的静态成员函数preloadOpenGL实现的。

4. 预加载一些共享库,这是通过调用ZygoteInit类的静态成员函数preloadSharedLibraries实现的。

5. 预加载WebView库,这是通过调用WebViewFactory类的静态成员函数prepareWebViewInZygote实现的。

所有的这些预加载行为都是为了实现内存共享目的的,也就是在Zygote进程和所有应用程序进程之间进行内存共享。这里我们只关注系统资源的预加载过程,即ZygoteInit类的静态成员函数preloadResources的实现,如下所示:

public class ZygoteInit { ...... private static void preloadResources() { ...... try {......mResources = Resources.getSystem();mResources.startPreloading();if (PRELOAD_RESOURCES) { ...... TypedArray ar = mResources.obtainTypedArray(com.android.internal.R.array.preloaded_drawables); int N = preloadDrawables(runtime, ar); ...... ar = mResources.obtainTypedArray(com.android.internal.R.array.preloaded_color_state_lists); N = preloadColorStateLists(runtime, ar); ......}mResources.finishPreloading(); } catch (RuntimeException e) {...... } finally {...... } } ......}这个函数定义在文件frameworks/base/core/java/com/android/internal/os/ZygoteInit.java中。

从这里就可以看到,预加载的系统资源有两类,一类是Drawable资源,另一类是Color State List资源,分别通过调用ZygoteInit类的静态成员函数preloadDrawables和preloadColorStateLists实现。这两类资源所包含的具体资源列表分别由frameworks/base/core/res/res/values/arrays.xml文件里面的数组preloaded_drawables和preloaded_color_state_lists定义。

接下来我们只关注Drawable资源的预加载过程,即ZygoteInit类的静态成员函数preloadDrawables的实现,如下所示:

public class ZygoteInit { ...... private static int preloadDrawables(VMRuntime runtime, TypedArray ar) { int N = ar.length(); for (int i=0; i这个函数定义在文件frameworks/base/core/java/com/android/internal/os/ZygoteInit.java中。

ZygoteInit类的静态成员函数preloadDrawables通过调用静态成员变量mResources指向的一个Resources对象的成员函数getDrawable来依次读取由参数ar描述的一系列Drawable资源。ZygoteInit类的静态成员变量mResources指向的Resources对象就是用来描述系统资源的,从前面Android资源管理框架(Asset Manager)简要介绍和学习计划这个系列的文章可以知道,当我们调用它的成员函数getXXX来获取指定的资源时,如果该资源还没有加载,那么就会被加载。

这些被预加载的Drawable将会被运行在System进程里面的Asset Atlas Service合成一个地图集,并且作为纹理上传到GPU去,因此,接下来我们就继续分析Asset Atlas Service的实现。

前面提到,System进程是由Zygote进程启动的。System进程启动之后,就会加载系统服务,其中就包括Asset Atlas Service,如下所示:

public“ if=”if“ loop=”Loop“ looper.loop=”Looper.loop();“ looper.preparemainlooper=”Looper.prepareMainLooper();“ mainstring=”main(String[]“ mfactorytestmode=”(mFactoryTestMode“ new=”new“ pre=”pre“ private=”private“ public=”public“ run=”run()“ servicemanager.addserviceassetatlasservice.asset_atlas_service=”ServiceManager.addService(AssetAtlasService.ASSET_ATLAS_SERVICE,“ services.=”services.“ start=”Start“ startotherservices=”startOtherServices()“ static=”static“ systemserver=”SystemServer“ systemserver.run=”SystemServer().run();“ throwable=”(Throwable“ try=”try“ void=”void“>这三个函数定义在文件frameworks/base/services/java/com/android/server/SystemServer.java中。

Asset Atlas Service是一个非系统核心服务,当设备启动在非工厂模式,并且在没有禁用非系统核心服务的条件下,就会启动Asset Atlas Service。

Asset Atlas Service的启动过程如下所示:

public class AssetAtlasService extends IAssetAtlas.Stub { ...... public AssetAtlasService(Context context) { ...... ArrayListbitmaps = new ArrayList(300); int totalPixelCount = 0; // We only care about drawables that hold bitmaps final Resources resources = context.getResources(); final LongSparseArraydrawables = resources.getPreloadedDrawables(); final int count = drawables.size(); ...... for (int i = 0; i < count; i++) {final Bitmap bitmap = drawables.valueAt(i).getBitmap();if (bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888) { bitmaps.add(bitmap); totalPixelCount += bitmap.getWidth() * bitmap.getHeight();} } // Our algorithms perform. better when the bitmaps are first sorted // The comparator will sort the bitmap by width first, then by height Collections.sort(bitmaps, new Comparator() {@Overridepublic int compare(Bitmap b1, Bitmap b2) { if (b1.getWidth() == b2.getWidth()) { return b2.getHeight() - b1.getHeight(); } return b2.getWidth() - b1.getWidth();} }); // Kick off the packing work on a worker thread new Thread(new Renderer(bitmaps, totalPixelCount)).start(); } ......}

这个函数定义在文件frameworks/base/services/core/java/com/android/server/AssetAtlasService.java。

Asset Atlas Service的构造函数首先是获得预加载的Drawable资源,并且按照宽度和高度从大到小的顺序保存在一个Bitmap数组列表中,接着再将这个Bitmap数组列表封装在一个Renderer对象中,最后将这个Renderer对象post到一个新创建的线程去处理,即在新创建的线程中调用Renderer类的成员函数run,如下所示:

public class AssetAtlasService extends IAssetAtlas.Stub { ...... private GraphicBuffer mBuffer; ...... private class Renderer implements Runnable { private final ArrayListmBitmaps; private final int mPixelCount; ...... Renderer(ArrayListbitmaps, int pixelCount) {mBitmaps = bitmaps;mPixelCount = pixelCount; } ...... @Override public void run() {Configuration config = chooseConfiguration(mBitmaps, mPixelCount, mVersionName);......if (config != null) { mBuffer = GraphicBuffer.create(config.width, config.height,PixelFormat.RGBA_8888, GRAPHIC_BUFFER_USAGE); if (mBuffer != null) { Atlas atlas = new Atlas(config.type, config.width, config.height, config.flags); if (renderAtlas(mBuffer, atlas, config.count)) {mAtlasReady.set(true); } }} } ...... } ......}这个函数定义在文件frameworks/base/services/core/java/com/android/server/AssetAtlasService.java。

Renderer类的成员函数run首先调用另外一个成员函数chooseConfiguration计算出将所有预加载的Drawable资源合成在一张图片中所需要的最小宽度和高度值。有了这两个值之后,就可以创建一块Graphic Buffer了。这个Graphic Buffer保存在外部类AssetAtlasService的成员变量mBuffer中。

Renderer类的成员函数run接着再调用另外一个成员函数renderAtlas将所有预加载的Drawable资源渲染在前面创建的Graphic Buffer中,从形成一个预加载Drawable资源地图集,实际上就是将预加载Drawable资源合成在一张大的图片中。这个合成的过程要借助于Atlas类来完成,因此Renderer类的成员函数run会将一个Atlas对象传递给成员函数renderAtlas。

Renderer类的成员函数renderAtlas的实现如下所示:

public class AssetAtlasService extends IAssetAtlas.Stub { ...... private static final boolean DEBUG_ATLAS_TEXTURE = false; ...... private long[] mAtlasMap; ...... private class Renderer implements Runnable { ...... private long mNativeBitmap; ...... private boolean renderAtlas(GraphicBuffer buffer, Atlas atlas, int packCount) {// Use a Source blend mode to improve performance, the target bitmap// will be zero'd out so there's no need to waste time applying blendingfinal Paint paint = new Paint();paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));// We always render the atlas into a bitmap. This bitmap is then// uploaded into the GraphicBuffer using OpenGL to swizzle the contentfinal Canvas canvas = acquireCanvas(buffer.getWidth(), buffer.getHeight());if (canvas == null) return false;final Atlas.Entry entry = new Atlas.Entry();mAtlasMap = new long[packCount * ATLAS_MAP_ENTRY_FIELD_COUNT];long[] atlasMap = mAtlasMap;int mapIndex = 0;boolean result = false;try { final long startRender = System.nanoTime(); final int count = mBitmaps.size(); ...... for (int i = 0; i < count; i++) { final Bitmap bitmap = mBitmaps.get(i); if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) {......canvas.save();canvas.translate(entry.x, entry.y);if (entry.rotated) { canvas.translate(bitmap.getHeight(), 0.0f); canvas.rotate(90.0f);}canvas.drawBitmap(bitmap, 0.0f, 0.0f, null);canvas.restore();atlasMap[mapIndex++] = bitmap.mNativeBitmap;atlasMap[mapIndex++] = entry.x;atlasMap[mapIndex++] = entry.y;atlasMap[mapIndex++] = entry.rotated ? 1 : 0; } } ...... if (mNativeBitmap != 0) { result = nUploadAtlas(buffer, mNativeBitmap); } ......} finally { releaseCanvas(canvas);}return result; } private Canvas acquireCanvas(int width, int height) {if (DEBUG_ATLAS_TEXTURE) { ......} else { Canvas canvas = new Canvas(); mNativeBitmap = nAcquireAtlasCanvas(canvas, width, height); return canvas;} } private void releaseCanvas(Canvas canvas) {if (DEBUG_ATLAS_TEXTURE) { ......} else { nReleaseAtlasCanvas(canvas, mNativeBitmap);} } ...... } ......}

这个函数定义在文件frameworks/base/services/core/java/com/android/server/AssetAtlasService.java。

Renderer类的成员函数renderAtlas将所有预加载的Drawable资源合成在一张图片的过程如下所示:

1. 调用成员函数acquireCanvas创建一个Canvas,这个Canvas封装了一个SkBitmap,这个SkBitmap是通过调用外部类AssetAtlasService的JNI成员函数nAcquireAtlasCanvas在Native层创建的,它的地址保存在成员变量mNativeBitmap中。

2. 对于每一个预加载的Drawable资源,首先通过参数atlas指向的一个Atlas对象的成员函数pack计算出它们在合成的一张大的图片中的位置和旋转角度。有了这些信息之后,就可以将每一个预加载的Drawable资源渲染在前面创建的Canvas中。同时,每一个预加载的Drawable资源对应的Bitmap对象的地址值,以及渲染在Canvas中的位置和旋转信息,还会记录在外部类AssetAtlasService的成员变量mAtlasMap描述的一个long数组中。记录的这些信息在使用合成的图片时是很重要的。首先,通过对应的Bitmap对象的地址值可以知道一个Drawable资源是否位于合成的图片中。其次,确定一个Drawable资源在合成的图片之后,通过位置和旋转信息可以准确地在合成的图片中访问到Drawable资源的内容。后面我们分析应用程序进程使用这个合成的图片的时候,就会更清楚地看到上述记录的信息是如何使用的。

3. 将所有预加载的Drawable资源都渲染在前面创建的Canvas之后,接下来就再通过调用外部类AssetAtlasService的JNI成员函数nUploadAtlas将该Canvas封装的SkBitmap的内容作为一个纹理上传到GPU中。上传到GPU的纹理可以通过前面创建的Graphic Buffer来访问。

4. 将Canvas封装的SkBitmap上传到GPU之后,前面创建的Canvas就不需要了,因此就可以调用外部类AssetAtlasService的JNI成员函数releaseCanvas释放它占用的资源,实际上就是释放它封装的SkBitmap占用的内存。

接下来我们简单描述一下将预加载Drawable资源合成一张图片的算法,如图3所示:

图3 将预加载Drawable资源合成一张图片的算法示意图

开始的时候,整个图片当作一个空闲块C0,如图3(a)所示。接下来将Drawable B1对应的Bitmap渲染在空闲C0的左上角位置,如图3(b)所示。剩下的空闲位置按照垂直或者水平方向划分为C1和C2两块,如图3(c)和图3(d)所示。再接下来的其它Drawable资源对应的Bitmap,例如B2,将在C1或者C2空闲块找到合适的位置进行渲染。假设选择的空闲块是C1,那么C1剩余的空闲位置又继续按照上述过程进行划分。依次类推,每一个Drawable资源都在当前的空闲块中找到位置进行渲染,从而完成整个合成过程。关于这个合成过程的详细实现,可以参考rameworks/base/graphics/java/android/graphics/Atlas.java文件中的Atlas类的实现。

预加载Drawable资源合成到一张大的图片之后,就可以作为纹理上传到GPU了,这是通过外部类AssetAtlasService的成员函数nUploadAtlas来实现的。AssetAtlasService的成员函数nUploadAtlas是一个JNI函数,由Native层的函数com_android_server_AssetAtlasService_upload实现,如下所示:

static jboolean com_android_server_AssetAtlasService_upload(JNIEnv* env, jobject, jobject graphicBuffer, jlong bitmapHandle) { SkBitmap* bitmap = reinterpret_cast(bitmapHandle); // The goal of this method is to copy the bitmap into the GraphicBuffer // using the GPU to swizzle the texture content spbuffer(graphicBufferForJavaObject(env, graphicBuffer)); if (buffer != NULL) { EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); ...... EGLint major; EGLint minor; if (!eglInitialize(display, &major, &minor)) {...... } // We're going to use a 1x1 pbuffer surface later on // The configuration doesn't really matter for what we're trying to do EGLint configAttrs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 0, EGL_DEPTH_SIZE, 0, EGL_STENCIL_SIZE, 0, EGL_NONE }; EGLConfig configs[1]; EGLint configCount; if (!eglChooseConfig(display, configAttrs, configs, 1, &configCount)) {...... } ...... // These objects are initialized below but the default ”null“ // values are used to cleanup properly at any point in the // initialization sequence GLuint texture = 0; EGLImageKHR image = EGL_NO_IMAGE_KHR; EGLSurface surface = EGL_NO_SURFACE; EGLSyncKHR fence = EGL_NO_SYNC_KHR; EGLint attrs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; EGLContext context = eglCreateContext(display, configs[0], EGL_NO_CONTEXT, attrs); ...... // Create the 1x1 pbuffer EGLint surfaceAttrs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }; surface = eglCreatePbufferSurface(display, configs[0], surfaceAttrs); ...... if (!eglMakeCurrent(display, surface, surface, context)) {...... } // We use an EGLImage to access the content of the GraphicBuffer // The EGL image is later bound to a 2D texture EGLClientBuffer clientBuffer = (EGLClientBuffer) buffer->getNativeBuffer(); EGLint imageAttrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs); ...... glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); ...... // Upload the content of the bitmap in the GraphicBuffer glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel()); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap->width(), bitmap->height(), GL_RGBA, GL_UNSIGNED_BYTE, bitmap->getPixels()); ...... // The fence is used to wait for the texture upload to finish // properly. We cannot rely on glFlush() and glFinish() as // some drivers completely ignore these API calls fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, NULL); ...... // The flag EGL_SYNC_FLUSH_COMMANDS_BIT_KHR will trigger a // pipeline flush (similar to what a glFlush() would do.) EGLint waitStatus = eglClientWaitSyncKHR(display, fence, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, FENCE_TIMEOUT); ...... } return JNI_FALSE;}这个函数定义在文件frameworks/base/services/core/jni/com_android_server_AssetAtlasService.cpp中。

参数bitmapHandle描述的是一个SkBitmap对象,这个SkBitmap对象就是前面用来渲染Drawable资源的Canvas底层封装的SkBitmap对象,因此它里面包含的内容就是预加载Drawable资源合成后的图片。另外一个参数graphicBuffer指向的是之前创建的一个Graphic Buffer,它是在GPU分配的。现在要做的事情就是将参数bitmapHandle描述的SkBitmap的内容上传到参数graphicBuffer描述的Graphic Buffer中。

上传的过程如下所示:

1. 调用eglGetDisplay、eglInitialize、eglChooseConfig、eglCreateContext、eglCreatePbufferSurface和eglMakeCurrent等egl函数初始化一个Open GL环境。

2. 调用glGenTextures、glBindTexture和glEGLImageTargetTexture2DOES等gl函数创建一个纹理,并且指定参数graphicBuffer描述的Graphic Buffer作为该纹理的储存。

3. 调用glPixelStorei和glTexSubImage2D等gl函数将参数bitmapHandle描述的SkBitmap的内容上传到前面创建的纹理中去,也就是上传到参数graphicBuffer描述的Graphic Buffer中去。

4. 调用eglCreateSyncKHR和eglClientWaitSyncKHR等egl函数等待上传操作完成。

这样,当函数com_android_server_AssetAtlasService_upload执行完毕,预加载的Drawable资源合成的图片就作为纹理上传到GPU去了,并且这个纹理可以通过一个Graphic Buffer来访问。这个Graphic Buffer可以通过Asset Atlas Service提供的接口getBuffer来获得,如下所示:

public class AssetAtlasService extends IAssetAtlas.Stub { ...... private GraphicBuffer mBuffer; ...... private long[] mAtlasMap; ...... @Override public GraphicBuffer getBuffer() throws RemoteException { return mAtlasReady.get() ? mBuffer : null; } @Override public long[] getMap() throws RemoteException { return mAtlasReady.get() ? mAtlasMap : null; } ......}这个函数定义在文件frameworks/base/services/core/java/com/android/server/AssetAtlasService.java。

注意,为了能够访问到每一个预加载Drawable资源的内容,只获得它们合成在的Graphic Buffer还不足够,我们还必须知道每一个预加载Drawable资源在Graphic Buffer中的位置和旋转等辅助信息,因此,Asset Atlas Service还提供了另外一个接口getMap来获得上述辅助信息。

预加载Drawable资源合成在的图片就是我们前面提到的地图集,接下来我们继续分析应用程序进程是如何使用它们的。在前面Android应用程序UI硬件加速渲染环境初始化过程分析一文中提到,Android应用程序进程在初始Open GL渲染上下文时,会通过一个AtlasInitializer类的成员函数init来初始化预加载资源地图集,如下所示:

public class ThreadedRenderer extends HardwareRenderer { ...... private static class AtlasInitializer { ...... private boolean mInitialized = false; ...... synchronized void init(Context context, long renderProxy) {if (mInitialized) return;IBinder binder = ServiceManager.getService(”assetatlas“);......IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder);try { if (atlas.isCompatible(android.os.Process.myPpid())) { GraphicBuffer buffer = atlas.getBuffer(); if (buffer != null) {long[] map = atlas.getMap();if (map != null) { ...... nSetAtlas(renderProxy, buffer, map); mInitialized = true;}...... } }} catch (RemoteException e) { ......} } ...... } ......}

这个函数定义在文件frameworks/base/core/java/android/view/ThreadedRenderer.java中。

AtlasInitializer类的成员函数init首先是获得Asset Atlas Service的Binder代理接口,接着再调用这个接口的成员函数isCompatible判断当前进程的父进程与Asset Atlas Service运行在的进程的父进程是不是一样的。如果是一样的,那么才会进一步调用Asset Atlas Service的Binder代理接口的成员函数getBuffer和getMap获得预加载资源的地图集信息。

这里之所以要做这样的判断,是为了保证当前进程和Asset Atlas Service运行在的进程是可以共享预加载Drawable资源的。当前进程是一个Android应用程序进程,它是由Zygote进程fork出来的,而Asset Atlas Service运行在的进程即为System进程,它也是由Zygote进程fork出来的。因此,它们的父进程是一样的。

最后,AtlasInitializer类的成员函数init调用另外一个成员函数nSetAtlas将获得的预加载资源地图集信息设置到当前进程的Render Thread中去。AtlasInitializer类的成员函数nSetAtlas是一个JNI函数,它由Native层的函数android_view_ThreadedRenderer_setAtlas实现,如下所示:

static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz, jlong proxyPtr, jobject graphicBuffer, jlongArray atlasMapArray) { spbuffer = graphicBufferForJavaObject(env, graphicBuffer); jsize len = env->GetArrayLength(atlasMapArray); if (len <= 0) { ALOGW(”Failed to initialize atlas, invalid map length: %d“, len); return; } int64_t* map = new int64_t[len]; env->GetLongArrayRegion(atlasMapArray, 0, len, map); RenderProxy* proxy = reinterpret_cast(proxyPtr); proxy->setTextureAtlas(buffer, map, len);}这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。

参数proxPtr指向的是一个RenderProxy对象,从前面Android应用程序UI硬件加速渲染环境初始化过程分析一文可以知道,这个RenderProxy对象是应用程序进程的Main Thread用来与Render Thread进通通信的,这里通过调用它的成员函数setTextureAtlas将预加载资源地图集信息传递给Render Thread。

RenderProxy类的成员函数setTextureAtlas的实现如下所示:

CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, size_t size) { CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size); args->buffer->decStrong(0); return NULL;}void RenderProxy::setTextureAtlas(const sp& buffer, int64_t* map, size_t size) { SETUP_TASK(setTextureAtlas); args->thread = &mRenderThread; args->buffer = buffer.get(); args->buffer->incStrong(0); args->map = map; args->size = size; post(task);}这个函数定义在文件frameworks/base/libs/hwui/renderthread/RenderProxy.cpp中,

RenderProxy类的成员函数setTextureAtlas通过宏SETUP_TASK将预加载资源地图集信息封装在一个Task中,并且通过调用另外一个成员函数post将该Task添加到Render Thread的Task Queue中,最终该Task将在Render Thread中调用由宏CREATE_BRIDGE4定义的函数setTextureAtlas进行处理。

宏CREATE_BRIDGE4定义的函数setTextureAtlas主要就是调用CanvasContext类的静态成员函数setTextureAtlas来接收预加载资源地图集信息,它的实现如下所示:

void CanvasContext::setTextureAtlas(RenderThread& thread, const sp& buffer, int64_t* map, size_t mapSize) { thread.eglManager().setTextureAtlas(buffer, map, mapSize);}这个函数定义在文件frameworks/base/libs/hwui/renderthread/CanvasContext.cpp。

CanvasContext类的静态成员函数setTextureAtlas首先是获得在Render Thread中创建的一个EglManager对象,接着再调用这个EglManager对象的成员函数setTextureAtlas来处理参数buffer和map描述的预加载资源地图集信息。

EglManager类的成员函数setTextureAtlas的实现如下所示:

void EglManager::setTextureAtlas(const sp& buffer, int64_t* map, size_t mapSize) { ...... mAtlasBuffer = buffer; mAtlasMap = map; mAtlasMapSize = mapSize; if (hasEglContext()) { ...... initAtlas(); }}这个函数定义在文件frameworks/base/libs/hwui/renderthread/EglManager.cpp中。

EglManager类的成员函数setTextureAtlas首先是将预加载资源地图集信息记录在成员变量mAtlasBuffer、mAtlasMap和mAtlasMapSize中,接着再调用另外一个成员函数hasEglContext判断Render Thread的Open GL渲染上下文是否已经创建。如果已经创建,那么就会调用成员函数initAtlas对前面记录下来的预加载资源地图集信息进行初始化。

从前面Android应用程序UI硬件加速渲染环境初始化过程分析一文可以知道,当Java层的ThreadedRenderer对象创建时,Render Thread的Open GL渲染上下文还没有创建,因此这时候就不可以对预加载资源地图集信息进行初始化。

从前面Android应用程序UI硬件加速渲染环境初始化过程分析一文还可以知道,等到Render Thread的Open GL渲染上下文创建时,CanvasContext类的成员函数setSurface会被调用来绑定当前激活的窗口。CanvasContext类的成员函数setSurface在调用的过程中,又会调用EglManager类的成员函数createSurface将当前激活的窗口封装为一个EGL Surface,作为Open GL的渲染Surface。

EglManager类的成员函数createSurface的实现如下所示:

EGLSurface EglManager::createSurface(EGLNativeWindowType window) { initialize(); EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, NULL); ...... return surface;}这个函数定义在文件frameworks/base/libs/hwui/renderthread/EglManager.cpp中。

EglManager类的成员函数createSurface在调用函数eglCreateWindowSurface将参数window描述的窗口封装成一个EGL Surface之前,会先调用另外一个成员函数initialize来在当前线程中初始化一个Open GL渲染上下文。

EglManager类的成员函数initialize的实现如下所示:

void EglManager::initialize() { ...... mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,”Failed to get EGL_DEFAULT_DISPLAY! err=%s“, egl_error_str()); EGLint major, minor; LOG_ALWAYS_FATAL_IF(eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE,”Failed to initialize display %p! err=%s“, mEglDisplay, egl_error_str()); ...... createContext(); ....... initAtlas();}这个函数定义在文件frameworks/base/libs/hwui/renderthread/EglManager.cpp中。

从这里就可以看到,EglManager类的成员函数initialize为当前线程创建好Open GL渲染上下文之后,就会调用另外一个成员函数initAtlas去初始化前面已经记录下来的预加载资源地图集信息。

EglManager类的成员函数initAtlas的实现如下所示:

void EglManager::initAtlas() { if (mAtlasBuffer.get()) { Caches::getInstance().assetAtlas.init(mAtlasBuffer, mAtlasMap, mAtlasMapSize); }}这个函数定义在文件frameworks/base/libs/hwui/renderthread/EglManager.cpp中。

Render Thread通过一个Caches类来管理一些Open GL对象信息,例如在在渲染过程要重复使用的一些Texture和FBO等对象。同时,Caches类也通过一个类型为AssetAtlas的成员变量assetAtlas来管理预加载资源地图集信息。因此, EglManager类的成员函数initAtlas就会调用Caches类的成员变量assetAtlas指向的一个AssetAtlas对象的成员函数init来负责执行初始化预加载资源地图集的工作。

AssetAtlas类的成员函数init的实现如下所示:

void AssetAtlas::init(spbuffer, int64_t* map, int count) { ...... mImage = new Image(buffer); if (mImage->getTexture()) { Caches& caches = Caches::getInstance(); mTexture = new Texture(caches); mTexture->id = mImage->getTexture(); mTexture->width = buffer->getWidth(); mTexture->height = buffer->getHeight(); createEntries(caches, map, count); } ......}这个函数定义在文件frameworks/base/libs/hwui/AssetAtlas.cpp中。

AssetAtlas类的成员函数init首先是根据参数buffer指向的一个Graphic Buffer生成一个Open GL纹理。如果生成成功,再创建一个Texture对象来描述这个Open GL纹理的ID、宽度和高度等信息,并且将该Texture对象保存在AssetAtlas类的成员变量mTexture中。接着再调用另外一个成员函数createEntries为预加载资源地图集包含的每一个Drawable资源创建描述信息。

我们首先看AssetAtlas类的成员函数init根据参数buffer指向的一个Graphic Buffer生成Open GL纹理的过程,这是创建一个Image对象来完成的,因此我们接下来分析Image类的构造函数的实现,如下所示:

Image::Image(spbuffer) { // Create the EGLImage object that maps the GraphicBuffer EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); EGLClientBuffer clientBuffer = (EGLClientBuffer) buffer->getNativeBuffer(); EGLint attrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; mImage = eglCreateImageKHR(display, EGL_NO_CONTEXT,EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs); if (mImage == EGL_NO_IMAGE_KHR) { ALOGW(”Error creating image (%#x)“, eglGetError()); mTexture = 0; } else { // Create a 2D texture to sample from the EGLImage glGenTextures(1, &mTexture); Caches::getInstance().bindTexture(mTexture); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, mImage); GLenum status = GL_NO_ERROR; while ((status = glGetError()) != GL_NO_ERROR) {ALOGW(”Error creating image (%#x)“, status); } }}这个函数定义在文件frameworks/base/libs/hwui/Image.cpp。

我们首先明确,参数buffer指向的一个Graphic Buffer是在Asset Atlas Service运行在的System进程创建的,它是预加载资源地图集纹理的储存。也就是说,这个Graphic Buffer里面包含了预加载资源地图集的内容。现在Image类的构造函数将这个Graphic Buffer封装成一个EGLImageKHR对象,并且将该EGLImageKHR对象作为当前进程中的一个纹理来使用。这就相当于是说,在当前进程中创建的纹理与前面在System进程创建的预加载资源地图集纹理背后对应的储存都是一样的,因此就达到了在GPU中共享预加载资源的目的。这种共享机制的一个关键技术点是通过Graphic Buffer来在不同进程之间传递纹理储存,从而实现共享。

回到AssetAtlas类的成员函数init中,接下来我们继续分析预加载资源地图集包含的每一个Drawable资源的描述信息的创建过程,即AssetAtlas类的成员函数createEntries的实现,如下所示:

void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) { const float width = float(mTexture->width); const float height = float(mTexture->height); for (int i = 0; i < count; ) { SkBitmap* bitmap = reinterpret_cast(map[i++]); // NOTE: We're converting from 64 bit signed values to 32 bit // signed values. This is guaranteed to be safe because the ”x“ // and ”y“ coordinate values are guaranteed to be representable // with 32 bits. The array is 64 bits wide so that it can carry // pointers on 64 bit architectures. const int x = static_cast(map[i++]); const int y = static_cast(map[i++]); bool rotated = map[i++] >0; // Bitmaps should never be null, we're just extra paranoid if (!bitmap) continue; const UvMapper mapper( x / width, (x + bitmap->width()) / width, y / height, (y + bitmap->height()) / height); Texture* texture = new DelegateTexture(caches, mTexture); texture->id = mTexture->id; texture->blend = !bitmap->isOpaque(); texture->width = bitmap->width(); texture->height = bitmap->height(); Entry* entry = new Entry(bitmap, x, y, rotated, texture, mapper, *this); texture->uvMapper = &entry->uvMapper; mEntries.add(entry->bitmap, entry); }}这个函数定义在文件frameworks/base/libs/hwui/AssetAtlas.cpp中。

预加载资源地图集包含的每一个Drawable资源的描述信息记录在参数map描述的一个int64_t数组中。从前面的分析可以知道,每一个Drawable资源在这个int64_t数组中占据着4个描述信息,分别是底层对应的SkBitmap对象的地址值、在合成的地图集图片中的偏移位置和旋转情况。

其中,最重要的就是Drawable资源在合成的地图集图片中的偏移位置。我们知道,纹理坐标是被归一化处理的,也就是它的取值范围是[0, 1]。但是参数map描述的int64_t数组记录的Drawable资源偏移位置不是归一化处理的。因此,如果我们要在前面创建的地图集纹理中准确地访问到指定的Drawable资源,就必须要将参数map描述的int64_t数组记录的Drawable资源偏移位置进行归一化处理。处理的过程很简单,只要我们知道一个Drawable资源合成前的大小和合成在地图集图片的位置,以及合成的地图集图片的大小,就可以计算得到它在前面创建的地图集纹理的归一化偏移位置。计算得到的归一化偏移位置保存在一个UvMapper对象中。

为了方便描述一个Drawable资源在地图集纹理对应的那部分内容,AssetAtlas类的成员函数createEntries为每一个Drawable资源创建一个委托纹理对象(DelegateTexture)。也就是说,通过这个委托纹理对象能够在地图集纹理中访问到对应的Drawable资源占据的那部分内容。这个委托纹理对象记录了地图集纹理的ID,以及对应的Drawable资源对应的SkBitmap对象的宽度、高度和透明信息。其中,透明信息是一个很重要的信息,后面在渲染这些Drawable资源时将会使用到。这一点我们后面再分析。

现在,上面收集到的每一个预加载的Drawable资源的描述信息都记录在一个Entry对象中,并且这个Entry对象会以预加载的Drawable资源对应的SkBitmap对象的地址值为Key值,保存在AssetAtlas类的成员变量mEntries描述的一个KeyedVector中。这样,以后给出一个Drawable资源对应的SkBitmap对象,我们就可以快速地通过这个KeyedVector查询得知它是否是一个预加载的Drawable资源。如果是一个加载的Drawable资源的话,那么在渲染它的时候,就直接到地图集纹理去访问它的内容就行了,而不用为它单独创建一个纹理。

例如,假设我们在UI上将一个预加载的Drawable资源作为一个Bitmap来使用,那么这个Bitmap最终会通过OpenGLRenderer类的成员函数drawBitmap进行渲染,如下所示:

status_t OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) { if (quickRejectSetupScissor(0, 0, bitmap->width(), bitmap->height())) { return DrawGlInfo::kStatusDone; } mCaches.activeTexture(0); Texture* texture = getTexture(bitmap); if (!texture) return DrawGlInfo::kStatusDone; const AutoTexture autoCleanup(texture); if (CC_UNLIKELY(bitmap->colorType() == kAlpha_8_SkColorType)) { drawAlphaBitmap(texture, 0, 0, paint); } else { drawTextureRect(0, 0, bitmap->width(), bitmap->height(), texture, paint); } return DrawGlInfo::kStatusDrew;}这个函数定义在文件frameworks/base/libs/hwui/OpenGLRenderer.cpp中。

参数bitmap描述的就是预加载的Drawable资源底层对应的SkBitmap对象。在调用OpenGLRenderer类的成员函数drawAlphaBitmap或者drawTextureRect来渲染该Drawable资源之前,我们首先要为它创建一个纹理,这是通过调用OpenGLRenderer类的成员函数getTexture来实现的,它的实现如下所示:

Texture* OpenGLRenderer::getTexture(const SkBitmap* bitmap) { Texture* texture = mCaches.assetAtlas.getEntryTexture(bitmap); if (!texture) { return mCaches.textureCache.get(bitmap); } return texture;}

这个函数定义在文件frameworks/base/libs/hwui/OpenGLRenderer.cpp中。

OpenGLRenderer类的成员函数getTexture首先是通过调用成员变量mCaches指向的一个Caches对象的成员变量assetAtlas描述的一个AssetAtlas对象的成员函数getEntryTexture检查参数bitmap描述的是否是地图集纹理包含的一个Drawable资源。如果是的话,那么就会得到一个对应的纹理;否则的话,就需要为参数bitmap描述的资源创建一个新的纹理,这是通过调用成员变量mCaches指向的一个Caches对象的成员变量textureCache描述的一个TextureCache对象的成员函数get实现的。

TextureCache类会缓存那些曾经使用的纹理,当调用它的成员函数get的时候,它首先检查参数描述的SkBitmap在缓存中是否已经有对应的纹理了。如果有的话,就将该纹理返回给调用者;否则的话,就会为该Bitmap创建一个新的纹理,然后再返回给调用者。

这里我们只关注参数bitmap描述的是地图集纹理包含的一个Drawable资源的情况,这时候调用AssetAtlas类的成员函数getEntryTexture就会获得一个不为NULL的纹理,如下所示:

Texture* AssetAtlas::getEntryTexture(const SkBitmap* bitmap) const { ssize_t index = mEntries.indexOfKey(bitmap); return index >= 0 ? mEntries.valueAt(index)->texture : NULL;}这个函数定义在文件frameworks/base/libs/hwui/AssetAtlas.cpp中。

从前面的分析可以知道,当参数bitmap描述的SkBitmap是地图集纹理包含的一个Drawable资源时,在AssetAtlas类的成员变量mEntries描述一个KeyedVector中就可以找到一个对应的Entry对象,并且通过这个Entry对象的成员变量texture可以获得一个对应的纹理。

从上面这个例子我们就可以看出预加载资源地图集是如何在Zygote进程、System进程以及应用程序进程之间进行GPU级别的纹理共享的。事实上,预加载资源地图集纹理的作用远不止于此。后面我们分析Render Thread渲染窗口UI的时候,可以看到一个Open GL绘制命令合并渲染优化的操作。通过合并若干个Open GL绘制命令为一个Open GL绘制命令,可以减少Open GL渲染管线的状态切换操作,从而提高渲染的效率。预加载资源地图集纹理为这种合并渲染优化提供了可能,接下来我们就分析这种合并渲染优化的实现原理。

我们知道,Android应用程序窗口UI的视图是树形结构的。在渲染的时候,先绘制父视图的UI,再绘制子视图的UI。我们可以把这种绘制模式看作是分层的,即先绘制背后的层,再绘制前面的层,如图4所示:

图4 Android应用程序窗口UI分层绘制模式

图4显示的窗口由g一个背景图、一个ActionBar、一个Button和一个应用程序图标组成,它们按照从前到后的顺序排列。其中,ActionBar和Button都是由背景和文字组成的,它们使用的背景图均为一个预加载的Drawable资源,并且是按照九宫图方式绘制。

按照我们前面描述的分层绘制模式,图4显示的窗口UI按照A、B、C、D、E和F的顺序进行绘制,每一次绘制对应的都是一个Open GL绘制命令。但是实际上,有些绘制命令是可以进行合并的。例如,ActionBar和Button的背景图,它们使用的都是预加载的Drawable资源,并且这些资源已经合成为一个地图集纹理上传到了GPU去了。如果可以将这两个背景图的绘制合并成一个Open GL命令,那么就可以Open GL渲染管线的状态切换次数,提高渲染效率。

注意,这种合并操作并不是总能执行的。例如,假设在图4中,介于ActionBar和Button之间应用程序图标不仅与ActionBar重叠,还与Button重叠,那么ActionBar和Button的背景就不可以进行合并绘制。这意味着两个绘制命令是否能够进行合并是由许多因素决定的。

后面我们在分析应用程序窗口的Display List构建和渲染过程就会看到,图4显示的A、B、C、D、E和F绘制操作都是对应一个Draw Op。我们可以将Draw Op看作是一个绘制命令,它们按照前后顺序保存在应用程序窗口的Display List中。为了能够实现合并,这些Draw Op不是马上被执行,而是先通过一个Deferred Display List进行重排,将可以合并的Draw Op先进行合并,然后再对它们进行合并。重排的算法就是依次将原来保存在应用程序窗口的Display List的Draw Op添加到Deferred Display List中去。在添加的过程中,如果发现后一个Draw Op可以与前一个Draw Op进行合并,那么就对它们进行合并。

Deferred Display List内部维护了一个Hash Map数组,用来描述哪些Draw Op是如何合并的,如下所示:

class DeferredDisplayList { ...... enum OpBatchId { kOpBatch_None = 0, // Don't batch kOpBatch_Bitmap, kOpBatch_Patch, kOpBatch_AlphaVertices, kOpBatch_Vertices, kOpBatch_AlphaMaskTexture, kOpBatch_Text, kOpBatch_ColorText, kOpBatch_Count, // Add other batch ids before this }; ...... /** * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not * collide, which avoids the need to resolve mergeid collisions. */ TinyHashMapmMergingBatches[kOpBatch_Count]; ......}这个类定义在文件frameworks/base/libs/hwui/DeferredDisplayList.h中。

Deferred Display List内部维护的Hash Map数组由成员变量mMerginBatches指向。每一个Draw Op都有一个Batch ID和一个Merge ID。只有Batch ID和Merge ID相同的两个Draw Op才有可能被合并。我们这里说可能,是因为即使是Batch ID和Merge ID相同的两个Draw Op,如果它们之间存在一个具有不同Batch ID和Merge ID的Draw Op,并且绘制区域与它们重叠,那么就不能进行合并。

我们可以将Batch ID看作是一个用来决定两个Draw Op是否能够进行合并的一级Key。例如,九宫图绘制的Draw Op的Batch ID定义为kOpBatch_Patch,而文字绘制的Draw Op的Batch ID定义为kOpBatch_Text。通过这个一级Key,我们可以快速地确认两个Draw Op是否能够进行合并。这很显然,不同的Batch ID的Draw Op不能进行合并。

Merge ID是一个用来决定两个Draw Op是否能够进行合并的二级Key。两个Draw Op的Batch ID虽然相同,它们也未必能够进行合并。例如,对于两个kOpBatch_Patch Draw Op,如果一个使用了预加载的Drawable资源,另一个使用的不是预加载的Drawable资源,那么就不能进行合并。或者两者使用的都是预加载的Drawable资源,但是其中一个是透明的,另一个是不透明的。

可以合并的Draw Op维护在一个Draw Batch中。具体的维护过程是在将Display List的Draw Op转移到Defered Display List的过程进行的,如下所示:

1. 以Draw Op的Batch ID为索引,在Merging Batch数组中找到对应的Hash Map;

2. 以Draw Op的Merge ID为索引,在上一步找到的Hash Map中找到对应的Draw Batch;

3. 判断正在处理的Draw Op与它对应的Draw Batch的已经有的Draw Op是否能够进行合并,如果能够进行合并,就将它放进对应的Draw Batch去。

最后,就以Draw Batch为单位进行绘制,这样就可以使得可以进行合并的Draw Op可以进行合并渲染。

现在的一个问题,就是如何获得一个Draw Op的Batch ID和Merge ID。以九宫图绘制为例,它对应的Draw Op是一个DrawPatchOp,如下所示:

class DrawPatchOp : public DrawBoundedOp {public: DrawPatchOp(const SkBitmap* bitmap, const Res_png_9patch* patch,float left, float top, float right, float bottom, const SkPaint* paint): DrawBoundedOp(left, top, right, bottom, paint),mBitmap(bitmap), mPatch(patch), mGenerationId(0), mMesh(NULL),mAtlas(Caches::getInstance().assetAtlas) { mEntry = mAtlas.getEntry(bitmap); ...... }; ...... virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,const DeferredDisplayState& state) { deferInfo.batchId = DeferredDisplayList::kOpBatch_Patch; deferInfo.mergeId = getAtlasEntry() ? (mergeid_t) mEntry->getMergeId() : (mergeid_t) mBitmap; deferInfo.mergeable = state.mMatrix.isPureTranslate() && OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; deferInfo.opaqueOverBounds = isOpaqueOverBounds(state) && mBitmap->isOpaque(); }private: const SkBitmap* mBitmap; const Res_png_9patch* mPatch; ...... const AssetAtlas& mAtlas; ...... AssetAtlas::Entry* mEntry;};这个类定义在文件frameworks/base/libs/hwui/DisplayListOp.h中。

其中,成员变量mBitmap和mPatch描述的是九宫图绘制使用的是SkBitmap和九宫图信息。如果使用的SkBitmap是一个预加载的Drawable资源,那么就可以在我们前面描述用来描述预加载资源地图集纹理的AssetAtlas类中获得一个Entry,保存在成员变量mEntry中。

在将一个Draw Op从Display List转移到Defered Display List的过程中,会通过调用这个Draw Op的成员函数onDefer来获得合并相关的信息。例如,通过调用DrawPatchOp类的成员函数onDefer可以获得一个九宫图绘制的合并信息。这些合并信息通过一个DeferInfo对象来描述。

DeferInfo类有四个成员变量batchId、mergeId、mergeable和opaqueOverBounds。其中,第一个成员变量batchId和第二个mergeId描述的就是Batch ID和Merge ID。第三个成员变量mergeable描述的是Draw Op是否可以进行合并,这与当前的窗口渲染状态以及Draw Op本身的渲染属性有关。例如,对于九宫图的Draw Op来说,只有在当前窗口的变换矩阵只包含平移变换以及Draw Op使用的画笔的绘制模式为SkXfermode::kSrcOver_Mode时,才可以进行合并。第四个成员变量描述的是Draw Op是否会完全遮挡了排列在它前面的Draw Op。如果完全遮挡了,那么排列在它前面的Draw Op实际上是不需要进行绘制的。这又是一个渲染优化操作。

回到九宫图绘制这个Draw Op中,它的Batch ID的确定很简单,固定为kOpBatch_Patch。对于Merge ID,就相比复杂一些了。前面我们提到,这与九宫图使用的SkBitmap有关,而且还与这个SkBitmap透明与否有关。

如果九宫图使用的SkBitmap是一个预加载的Drawable资源,那么它就有一个对应的Entry对象,通过调用这个Entry对象的成员函数getMergeId可以获得我们需要的Merge ID。如果九宫图使用的SkBitmap不是一个预加载的Drawable资源,那么就用它使用的SkBitmap的地址作为Merge ID。很显然,一个使用了预加载Drawable资源和一个不使用预加载Drawable资源的九宫图绘制的Merge ID是不一样的,而且两个使用了不同的非预加载Drawable资源的九宫图绘制的Merge ID也是不一样的。

接下来我们再来看Entry类的成员函数getMergeId返回来的Merge ID是什么,如下所示:

class AssetAtlas { ...... struct Entry { ...... Texture* texture; ...... const AssetAtlas& atlas; ...... /** * Unique identifier used to merge bitmaps and 9-patches stored * in the atlas. */ const void* getMergeId() const {return texture->blend ? &atlas.mBlendKey : &atlas.mOpaqueKey; } ...... }; ...... const bool mBlendKey; const bool mOpaqueKey; ......}这个函数定义在文件frameworks/base/libs/hwui/AssetAtlas.h。

Entry类的成员变量texture描述的是一个预加载Drawable资源对应的Texture。前面在分析AssetAtlas类的成员函数createEntries时提到,这个Texture对象仅仅是一个委托对象,用来描述一个预加载Drawable资源在地图集纹理对应的那一部分内容。并且这个这个Texture对象有一个成员变量blend,用来描述一个预加载Drawable资源是透明还是不透明的。透明意味着在渲染时需要与当前已经绘制的内容进行合并,而不透明在渲染时直接覆盖在已经绘制的内容上就可以了。

从Entry类的成员函数getMergeId就可以看出,一个使用了预加载Drawable资源的九宫图绘制的Merge ID取决于预加载Drawable资源的透明度信息。如果是透明的,那么对应的九宫图绘制的Merge ID就是AssetAtlas类的成员变量mBlendkey的地址;否则的话,就是AssetAtlas类的成员变量mOpaqueKey的地址。

以上就是一个九宫图绘制的Batch ID和Merge ID的确定过程。接下来我们仍然是以九宫图绘制为例,说明若干个九宫图绘制合并为一个九宫图绘制的过程。

前面提到,可以合并的Draw Op最终会保存在一个Draw Batch中。对于九宫图绘制这个Drawp Op来说,它保存在的一个Draw Batch的具体类型为MergingDrawBatch,通过调用它的成员函数replay就可以执行保存它里面的九宫图绘制命令,如下所示:

class MergingDrawBatch : public DrawBatch { ...... virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) { ...... if (mOps.size() == 1) {return DrawBatch::replay(renderer, dirty, -1); } ...... DrawOp* p = mOps[0].op; ...... status_t status = op->multiDraw(renderer, dirty, mOps, mBounds); ...... return status; } ......}这个函数定义在文件frameworks/base/libs/hwui/DeferredDisplayList.cpp中。

一个MergingDrawBatch包含的所有Draw Op保存在父类DrawBatch的成员变量mOps中。如果只有一个Draw Op,那就按常规绘制就行了,这通过调用父类DrawBatch的成员函数replay实现。DrawBatch类的成员函数replay又是通过调用Draw Op的成员函数applyDraw来执行实际的渲染操作的。

如果一个MergingDrawBatch包含有有若干个Draw Op,那么就只会调用第一个Draw Op的成员函数multiDraw进行绘制,同时传递给这个成员函数的参数还包括其余的Draw Op。在我们这个例子中,调用的就是DrawPatchOp类的成员函数multiDraw,它的实现如下所示:

class DrawPatchOp : public DrawBoundedOp { ...... virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty,const Vector& ops, const Rect& bounds) { const DeferredDisplayState& firstState = *(ops[0].state); renderer.restoreDisplayState(firstState, true); // restore all but the clip // Batches will usually contain a small number of items so it's // worth performing a first iteration to count the exact number // of vertices we need in the new mesh uint32_t totalVertices = 0; for (unsigned int i = 0; i < ops.size(); i++) {totalVertices += ((DrawPatchOp*) ops[i].op)->getMesh(renderer)->verticesCount; } const bool hasLayer = renderer.hasLayer(); uint32_t indexCount = 0; TextureVertex vertices[totalVertices]; TextureVertex* vertex = &vertices[0]; // Create a mesh that contains the transformed vertices for all the // 9-patch objects that are part of the batch. Note that onDefer() // enforces ops drawn by this function to have a pure translate or // identity matrix for (unsigned int i = 0; i < ops.size(); i++) {DrawPatchOp* patchOp = (DrawPatchOp*) ops[i].op;const DeferredDisplayState* state = ops[i].state;const Patch* pMesh = patchOp->getMesh(renderer);uint32_t vertexCount = opMesh->verticesCount;if (vertexCount == 0) continue;// We use the bounds to know where to translate our vertices// Using patchOp->state.mBounds wouldn't work because these// bounds are clippedconst float tx = (int) floorf(state->mMatrix.getTranslateX() + patchOp->mLocalBounds.left + 0.5f);const float ty = (int) floorf(state->mMatrix.getTranslateY() + patchOp->mLocalBounds.top + 0.5f);// Copy & transform. all the vertices for the current operationTextureVertex* pVertices = opMesh->vertices;for (uint32_t j = 0; j < vertexCount; j++, opVertices++) { TextureVertex::set(vertex++,opVertices->x + tx, opVertices->y + ty,opVertices->u, opVertices->v);}// Dirty the current layer if possible. When the 9-patch does not// contain empty quads we can take a shortcut and simply set the// dirty rect to the object's bounds.if (hasLayer) { if (!opMesh->hasEmptyQuads) { renderer.dirtyLayer(tx, ty, tx + patchOp->mLocalBounds.getWidth(), ty + patchOp->mLocalBounds.getHeight()); } else { const size_t count = opMesh->quads.size(); for (size_t i = 0; i < count; i++) {const Rect& quadBounds = opMesh->quads[i];const float x = tx + quadBounds.left;const float y = ty + quadBounds.top;renderer.dirtyLayer(x, y, x + quadBounds.getWidth(), y + quadBounds.getHeight()); } }}indexCount += opMesh->indexCount; } return renderer.drawPatches(mBitmap, getAtlasEntry(), &vertices[0], indexCount, getPaint(renderer)); } ......};这个函数定义在文件frameworks/base/libs/hwui/DisplayListOp.h。

由于可以合并渲染的九宫图绘制使用的都是同一个纹理,即预加载地图集纹理,因此我们只要收集好各个九宫图绘制的纹理坐标,就可以一次性地调用参数renderer描述的一个OpenGLRenderer对象的成员函数drawPatches进行渲染即可,而不需要分开来对这些九宫图绘制进行渲染,这样就实现了合并渲染优化。

这样,我们就通过九宫图绘制说明了在应用程序窗口UI的硬件加速渲染中。合并渲染优化的执行过程,从中就可以看到预加载资源地图集所起到的作用。类似的可以进行合并渲染优化的绘制还有文字绘制。例如,在前面的图4中,ActionBar和Button的文字也是可以合并在一起进行渲染的,如图5所示:

图5 文字合并渲染优化

图5的左边是一个由英文字母合并而成的纹理,类似于由预加载Drawable资源合成的地图集纹理。在绘制图5右边的文字时,每一个字母的绘制内容都是从左边合成的英文字母纹理而来的,这一点也是类以于预加载Drawable资源的渲染方式。

应用程序窗口UI的硬件加速渲染中的合并渲染优化是一个复杂的操作。复杂的原因就在于判断两个渲染操作是否可以合并在一起执行,而决定两个渲染操作是否可以合并的因素又很多。这里我们只是通过九宫图的渲染来简单说明这一过程,需要更深入了解这些合并渲染优化的执行过程,可以按照我们这里提供的流程仔细研读一下源码。

至此,我们就分析完成预加载资源地图集服务的作用以实现原理了。总结来说,它的作用就是:

1. 在GPU这一级别实现资源共享;

2. 为合并渲染优化提供了可能。

在接下来的两篇文章中,我们继续分析应用程序窗口的Display List构建过程以及渲染过程。结合这两个过程,我们再回过头来阅读这篇文章,就可以更好地了解预加载资源地图集起到的上述两个作用。敬请关注!更多的信息也可以关注老罗的 :weibo.com/shengyangluo。

篇3:Android UI PullToRrefresh自定义下拉刷新动画

Android UI- PullToRrefresh自定义下拉刷新动画

如果觉得本文不错,麻烦投一票,博客之星投票地址:vote.blog.csdn.net/blogstar/details?username=wwj_748#content

本篇博文要给大家分享的是如何使用修改开源项目PullToRrefresh下拉刷新的动画,来满足我们开发当中特定的需求,我们比较常见的一种下拉刷新样式可能是以下这种:

就是下拉列表的时候两个箭头上下翻转,更改日期文本和刷新状态,这种是最普遍的一种模式,

另外一种是旋转动画,就是刷新的时候一个圈不停的旋转,类似南方周末阅读器(注:是小巫公司的一个产品,各位多多支持O(∩_∩)O):

github地址:github.com/devilWwj/Android-PullToRefresh

下载下来之后,import到工作空间,导入到目标项目中去,不清楚如何导入关联项目的童鞋请自行百度。

我这里要实现的一种效果是下拉刷新时播放一个帧动画

增加动画列表:

<?xml version=”1.0“ encoding=”utf-8“?>

修改下拉刷新布局:

/PullToRefresh/res/layout/pull_to_refresh_header_simple.xml

<?xml version=”1.0“ encoding=”utf-8“?>

增加自定义的加载布局

/PullToRefresh/src/com/handmark/pulltorefresh/library/internal/TweenAnimLoadingLayout.java

package com.handmark.pulltorefresh.library.internal;import com.handmark.pulltorefresh.library.R;import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;import com.handmark.pulltorefresh.library.PullToRefreshBase.Orientation;import android.content.Context;import android.content.res.TypedArray;import android.graphics.drawable.AnimationDrawable;import android.graphics.drawable.Drawable;import android.view.View;/** * @date /1/8 * @author wuwenjie * @desc 帧动画加载布局 */public class TweenAnimLoadingLayout extends LoadingLayout { private AnimationDrawable animationDrawable; public TweenAnimLoadingLayout(Context context, Mode mode, Orientation scrollDirection, TypedArray attrs) { super(context, mode, scrollDirection, attrs); // 初始化 mHeaderImage.setImageResource(R.drawable.refresh_anim); animationDrawable = (AnimationDrawable) mHeaderImage.getDrawable; } // 默认图片 @Override protected int getDefaultDrawableResId() { return R.drawable.loading1; } @Override protected void onLoadingDrawableSet(Drawable imageDrawable) { // NO-OP } @Override protected void onPullImpl(float scaleOfLayout) { // NO-OP } // 下拉以刷新 @Override protected void pullToRefreshImpl() { // NO-OP } // 正在刷新时回调 @Override protected void refreshingImpl() { // 播放帧动画 animationDrawable.start(); } // 释放以刷新 @Override protected void releaseToRefreshImpl() { // NO-OP } // 重新设置 @Override protected void resetImpl() { mHeaderImage.setVisibility(View.VISIBLE); mHeaderImage.clearAnimation(); }}

我们只要修改开源项目中的LodingLayout代码:

/PullToRefresh/src/com/handmark/pulltorefresh/library/internal/LoadingLayout.java

/******************************************************************************* * Copyright , Chris Banes. * * Licensed under the Apache License, Version 2.0 (the ”License“); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ”AS IS“ BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/package com.handmark.pulltorefresh.library.internal;import android.annotation.SuppressLint;import android.content.Context;import android.content.res.ColorStateList;import android.content.res.TypedArray;import android.graphics.Typeface;import android.graphics.drawable.AnimationDrawable;import android.graphics.drawable.Drawable;import android.text.TextUtils;import android.util.TypedValue;import android.view.Gravity;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.view.animation.Interpolator;import android.view.animation.LinearInterpolator;import android.widget.FrameLayout;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.ProgressBar;import android.widget.TextView;import com.handmark.pulltorefresh.library.ILoadingLayout;import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;import com.handmark.pulltorefresh.library.PullToRefreshBase.Orientation;import com.handmark.pulltorefresh.library.R;@SuppressLint(”ViewConstructor“)public abstract class LoadingLayout extends FrameLayout implements ILoadingLayout { static final String LOG_TAG = ”PullToRefresh-LoadingLayout“; static final Interpolator ANIMATION_INTERPOLATOR = new LinearInterpolator(); private FrameLayout mInnerLayout; protected final ImageView mHeaderImage; protected final ProgressBar mHeaderProgress; private boolean mUseIntrinsicAnimation; private final TextView mHeaderText; private final TextView mSubHeaderText; protected final Mode mMode; protected final Orientation mScrollDirection; private CharSequence mPullLabel; private CharSequence mRefreshingLabel; private CharSequence mReleaseLabel; public LoadingLayout(Context context, final Mode mode, final Orientation scrollDirection, TypedArray attrs) { super(context); mMode = mode; mScrollDirection = scrollDirection; switch (scrollDirection) { case HORIZONTAL: LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header_horizontal, this); break; case VERTICAL: default:// LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header_vertical, this); // 修改代码 LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header_simple, this); break; } mInnerLayout = (FrameLayout) findViewById(R.id.fl_inner); mHeaderText = (TextView) mInnerLayout.findViewById(R.id.pull_to_refresh_text); mHeaderProgress = (ProgressBar) mInnerLayout.findViewById(R.id.pull_to_refresh_progress); mSubHeaderText = (TextView) mInnerLayout.findViewById(R.id.pull_to_refresh_sub_text); mHeaderImage = (ImageView) mInnerLayout.findViewById(R.id.pull_to_refresh_image); FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mInnerLayout.getLayoutParams(); switch (mode) { case PULL_FROM_END: lp.gravity = scrollDirection == Orientation.VERTICAL ? Gravity.TOP : Gravity.LEFT; // Load in labels mPullLabel = context.getString(R.string.pull_to_refresh_from_bottom_pull_label); mRefreshingLabel = context.getString(R.string.pull_to_refresh_from_bottom_refreshing_label); mReleaseLabel = context.getString(R.string.pull_to_refresh_from_bottom_release_label); break; case PULL_FROM_START: default: lp.gravity = scrollDirection == Orientation.VERTICAL ? Gravity.BOTTOM : Gravity.RIGHT; // Load in labels mPullLabel = context.getString(R.string.pull_to_refresh_pull_label); mRefreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); mReleaseLabel = context.getString(R.string.pull_to_refresh_release_label); break; } if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderBackground)) { Drawable background = attrs.getDrawable(R.styleable.PullToRefresh_ptrHeaderBackground); if (null != background) { ViewCompat.setBackground(this, background); } }// if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderTextAppearance)) {// TypedValue styleID = new TypedValue();// attrs.getValue(R.styleable.PullToRefresh_ptrHeaderTextAppearance, styleID);// setTextAppearance(styleID.data);// }// if (attrs.hasValue(R.styleable.PullToRefresh_ptrSubHeaderTextAppearance)) {// TypedValue styleID = new TypedValue();// attrs.getValue(R.styleable.PullToRefresh_ptrSubHeaderTextAppearance, styleID);// setSubTextAppearance(styleID.data);// }//// // Text Color attrs need to be set after TextAppearance attrs// if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderTextColor)) {// ColorStateList colors = attrs.getColorStateList(R.styleable.PullToRefresh_ptrHeaderTextColor);// if (null != colors) {// setTextColor(colors);// }// }// if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderSubTextColor)) {// ColorStateList colors = attrs.getColorStateList(R.styleable.PullToRefresh_ptrHeaderSubTextColor);// if (null != colors) {// setSubTextColor(colors);// }// } // Try and get defined drawable from Attrs Drawable imageDrawable = null; if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawable)) { imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawable); } // Check Specific Drawable from Attrs, these overrite the generic // drawable attr above switch (mode) { case PULL_FROM_START: default: if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableStart)) { imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableStart); } else if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableTop)) { Utils.warnDeprecation(”ptrDrawableTop“, ”ptrDrawableStart“); imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableTop); } break; case PULL_FROM_END: if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableEnd)) { imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableEnd); } else if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableBottom)) { Utils.warnDeprecation(”ptrDrawableBottom“, ”ptrDrawableEnd\"); imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableBottom); } break; } // If we don‘t have a user defined drawable, load the default if (null == imageDrawable) { imageDrawable = context.getResources().getDrawable(getDefaultDrawableResId()); } // Set Drawable, and save width/height setLoadingDrawable(imageDrawable); reset(); } public final void setHeight(int height) { ViewGroup.LayoutParams lp = getLayoutParams(); lp.height = height; requestLayout(); } public final void setWidth(int width) { ViewGroup.LayoutParams lp = getLayoutParams(); lp.width = width; requestLayout(); } public final int getContentSize() { switch (mScrollDirection) { case HORIZONTAL: return mInnerLayout.getWidth(); case VERTICAL: default: return mInnerLayout.getHeight(); } } public final void hideAllViews() {// if (View.VISIBLE == mHeaderText.getVisibility()) {// mHeaderText.setVisibility(View.INVISIBLE);// }// if (View.VISIBLE == mHeaderProgress.getVisibility()) {// mHeaderProgress.setVisibility(View.INVISIBLE);// }// if (View.VISIBLE == mHeaderImage.getVisibility()) {// mHeaderImage.setVisibility(View.INVISIBLE);// }// if (View.VISIBLE == mSubHeaderText.getVisibility()) {// mSubHeaderText.setVisibility(View.INVISIBLE);// } } public final void onPull(float scaleOfLayout) { if (!mUseIntrinsicAnimation) { onPullImpl(scaleOfLayout); } } public final void pullToRefresh() {// if (null != mHeaderText) {// mHeaderText.setText(mPullLabel);// } // Now call the callback pullToRefreshImpl(); } public final void refreshing() { if (null != mHeaderText) { mHeaderText.setText(mRefreshingLabel); } if (mUseIntrinsicAnimation) { ((AnimationDrawable) mHeaderImage.getDrawable()).start(); } else { // Now call the callback refreshingImpl(); }// if (null != mSubHeaderText) {// mSubHeaderText.setVisibility(View.GONE);// } } public final void releaseToRefresh() {// if (null != mHeaderText) {// mHeaderText.setText(mReleaseLabel);// } // Now call the callback releaseToRefreshImpl(); } public final void reset() {// if (null != mHeaderText) {// mHeaderText.setText(mPullLabel);// } mHeaderImage.setVisibility(View.VISIBLE); if (mUseIntrinsicAnimation) { ((AnimationDrawable) mHeaderImage.getDrawable()).stop(); } else { // Now call the callback resetImpl(); }// if (null != mSubHeaderText) {// if (TextUtils.isEmpty(mSubHeaderText.getText())) {// mSubHeaderText.setVisibility(View.GONE);// } else {// mSubHeaderText.setVisibility(View.VISIBLE);// }// } } @Override public void setLastUpdatedLabel(CharSequence label) {// setSubHeaderText(label); } @Override public final void setLoadingDrawable(Drawable imageDrawable) { // Set Drawable mHeaderImage.setImageDrawable(imageDrawable); mUseIntrinsicAnimation = (imageDrawable instanceof AnimationDrawable); // Now call the callback onLoadingDrawableSet(imageDrawable); } @Override public void setPullLabel(CharSequence pullLabel) { mPullLabel = pullLabel; } @Override public void setRefreshingLabel(CharSequence refreshingLabel) { mRefreshingLabel = refreshingLabel; } @Override public void setReleaseLabel(CharSequence releaseLabel) { mReleaseLabel = releaseLabel; } @Override public void setTextTypeface(Typeface tf) { mHeaderText.setTypeface(tf); } public final void showInvisibleViews() {// if (View.INVISIBLE == mHeaderText.getVisibility()) {// mHeaderText.setVisibility(View.VISIBLE);// }// if (View.INVISIBLE == mHeaderProgress.getVisibility()) {// mHeaderProgress.setVisibility(View.VISIBLE);// } if (View.INVISIBLE == mHeaderImage.getVisibility()) { mHeaderImage.setVisibility(View.VISIBLE); }// if (View.INVISIBLE == mSubHeaderText.getVisibility()) {// mSubHeaderText.setVisibility(View.VISIBLE);// } } /** * Callbacks for derivative Layouts */ protected abstract int getDefaultDrawableResId(); protected abstract void onLoadingDrawableSet(Drawable imageDrawable); protected abstract void onPullImpl(float scaleOfLayout); protected abstract void pullToRefreshImpl(); protected abstract void refreshingImpl(); protected abstract void releaseToRefreshImpl(); protected abstract void resetImpl(); private void setSubHeaderText(CharSequence label) { if (null != mSubHeaderText) { if (TextUtils.isEmpty(label)) { mSubHeaderText.setVisibility(View.GONE); } else { mSubHeaderText.setText(label); // Only set it to Visible if we‘re GONE, otherwise VISIBLE will // be set soon if (View.GONE == mSubHeaderText.getVisibility()) { mSubHeaderText.setVisibility(View.VISIBLE); } } } } private void setSubTextAppearance(int value) { if (null != mSubHeaderText) { mSubHeaderText.setTextAppearance(getContext(), value); } } private void setSubTextColor(ColorStateList color) { if (null != mSubHeaderText) { mSubHeaderText.setTextColor(color); } } private void setTextAppearance(int value) { if (null != mHeaderText) { mHeaderText.setTextAppearance(getContext(), value); } if (null != mSubHeaderText) { mSubHeaderText.setTextAppearance(getContext(), value); } } private void setTextColor(ColorStateList color) { if (null != mHeaderText) { mHeaderText.setTextColor(color); } if (null != mSubHeaderText) { mSubHeaderText.setTextColor(color); } }}

最后就不要让我提供源码了,笔者这里只是提供一个思路,把刷新的头布局更改为我们的自定义布局就行了,

最终效果是下拉刷新的时候会有一个不停在闪的灯,这里没有录制gif动画,只提供一张截图吧:

篇4:Android启动流程分析 action的执行和service的启动

在前面的文章分析完init.rc的解析以后,我们知道现在action按照init.c的main函数中的秩序,维护了一条qlist的链表,listnode为action_qlist service也维护了一条链表,listnode为service_list, 那么,在android的启动过程中,action和service是如何被启动的呢? 我们接下来再回到我们一直分析的init.c的main函数中来看一下。 在main函数中,完成前面的解析工作之后,我们分析到了一个死循环,来看一下:

for(;;) { int nr, i, timeout = -1; execute_one_command(); restart_processes(); ..... }

这个for循环的处理有很多,但是我们现在暂时关心的只有这短短的两个函数。

execute_one_command();

restart_processes();

首先我们来分析execute_one_command函数,看着个函数的名字,我们就能明白这个函数的功能。 这个函数就是去执行一个command嘛。。 来看一下这个函数的实现:

void execute_one_command(void){ int ret, i; char cmd_str[256] = ; if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) { // 在第一次的启动中,因为都是NULL,所以肯定可以进入这个判断,如果不是第一次的话,因为得到cur_action或者cur_command都是null,并且如果这个command是当前action的最后一个command的话,会进入到下面的这个判断。 cur_action = action_remove_queue_head(); // 依次获取action从action_queue中 cur_command = NULL; // 在获取新的action之后,将cur_command设置为null if (!cur_action) // 如果没有action了,则返回return; INFO(processing action %p (%s), cur_action, cur_action->name); cur_command = get_first_command(cur_action); // 如果是一个新的action的话,会执行到这一步去获得first command } else { cur_command = get_next_command(cur_action, cur_command); // 仍然在action的内部链表中,如果仍然存在没有被获取到的command的话,则会去获得下一个command。 } if (!cur_command) // 如果获取到的command为空的话,会返回,反之,继续 return; ret = cur_command->func(cur_command->nargs, cur_command->args); // 会调用这个command的func区执行,执行的参数个数为nargs,命令为args if (klog_get_level() >= KLOG_INFO_LEVEL) { // Log的打印 for (i = 0; i < cur_command->nargs; i++) {strlcat(cmd_str, cur_command->args[i], sizeof(cmd_str));if (i < cur_command->nargs - 1) { strlcat(cmd_str, , sizeof(cmd_str));} } INFO(command '%s' action=%s status=%d (%s:%d), cmd_str, cur_action ? cur_action->name : , ret, cur_command->filename, cur_command->line); }}

其实这个逻辑是比较好理解的,我们要着重分析的仅仅是如何获取action以及command. 来看一下action_remove_queue_head这个函数:

struct action *action_remove_queue_head(void){ if (list_empty(&action_queue)) { // 首先我们去判断当前待执行的action是否已经为null,即是否还有action没有被执行 return 0; } else { struct listnode *node = list_head(&action_queue); // 如果仍然有未被执行的队列的话,就将node指向现在action_queue的头指针 struct action *act = node_to_item(node, struct action, qlist); // 取出action list_remove(node); // 将这个节点从整个action _queue的列表中删除 list_init(node); // 删除这个节点后,为了安全起见,将node自己指向自己,以避免出现野指针。 return act; // 返回已经查找到的action }}

我们可以看到,其实是从action_queue中拿每一个结构体的。 在拿到action之后呢?就要从action里面去拿command了。 来看一下下面的这两个函数:

static struct command *get_first_command(struct action *act) // 从一个actoin里面寻找其第一个command,所以只用传递action即可{ struct listnode *node;node = list_head(&act->commands); // 将node指向action的commands的结构体 if (!node || list_empty(&act->commands)) // 如果这个节点不存在,或者这个action的commands结构体为空,则返回null return NULL; return node_to_item(node, struct command, clist); // 返回第一个节点}static struct command *get_next_command(struct action *act, struct command *cmd) // 返回当前commands的下一个command{ struct listnode *node; node = cmd->clist.next; 指针向后移动next if (!node) // 如果不存在,则返回null return NULL; if (node == &act->commands) // 如果这个节点已经是头节点了,则返回null return NULL; return node_to_item(node, struct command, clist); // 返回next节点}

在获取到了command之后,我们会去调用command的方法:

ret = cur_command->func(cur_command->nargs, cur_command->args);

去执行command里面的每一个func。 但是,非常奇怪的是,执行完commands之后,service是怎么启动的呢? 我们再去init.rc里面一探究竟。

on boot .... class_start coreon nonencrypted class_start main class_start late_start

我们看到在action里面,会有一些commands是class_start, 而后面跟的参数,好像与我们service的class name 是一致的。

再回到init.rc里面看看service的部分呢?

service adbd /sbin/adbd --root_seclabel=u:r:su:s0 class core socket adbd stream 660 system system disabled seclabel u:r:adbd:s0# adbd on at boot in emulatoron property:ro.kernel.qemu=1 start adbdservice lmkd /system/bin/lmkd class core critical socket lmkd seqpacket 0660 system systemservice servicemanager /system/bin/servicemanager class core user system group system critical onrestart restart healthd onrestart restart zygote onrestart restart media onrestart restart surfaceflinger onrestart restart drm

从keywords里面,我们找到了对应的function:

./init/keywords.h:53: KEYWORD(class_start, COMMAND, 1, do_class_start)

来看一下do_class_start的实现:

int do_class_start(int nargs, char **args){ /* Starting a class does not start services * which are explicitly disabled. They must * be started individually. */ service_for_each_class(args[1], service_start_if_not_disabled); return 0;}

这个函数的实现很简单,仅仅是传递调用了service_for_each_class, 并且在传递service name的时候,多传递了一个参数为service_start_if_not_disable.

void service_for_each_class(const char *classname, void (*func)(struct service *svc)){ struct listnode *node; struct service *svc; list_for_each(node, &service_list) { // 遍历service的结构体,这里是不会重复的,因为service的name如果有重复的时候,在解析过程中就已经处理了 svc = node_to_item(node, struct service, slist); // 从slist里取出每一个结构体 if (!strcmp(svc->classname, classname)) { // 如果名字是匹配的话,就会进入这个判断func(svc); // 执行service_start_if_not_disable, 并且将当前的service结构体给传递进去 } }}

接下来要执行的就是service_start_if_not_disable了,我们来看一下具体的实现:

static void service_start_if_not_disabled(struct service *svc){ if (!(svc->flags & SVC_DISABLED)) { service_start(svc, NULL); } else { svc->flags |= SVC_DISABLED_START; }}

如果这个service被设置为disabled的话,就不会被启动,如果没有设置的话,我们会去启动这个service。 这里需要注意的是,在我们调用service_start的时候,我们会去将第二个形参置为NULL。 在service_start的时候,这个函数很长很场,但是可以根据注释,将其分为三个阶段。

void service_start(struct service *svc, const char *dynamic_args){ /// ****************************** start service 的第一个阶段 struct stat s; pid_t pid; int needs_console; int n; char *scon = NULL; int rc; /* starting a service removes it from the disabled or reset * state and immediately takes it out of the restarting * state if it was in there */ svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START)); // 这个service即将被启动,将其从disable或reset的状态给移除掉,置其为重新运行的状态 svc->time_started = 0; /* running processes require no additional work -- if * they're in the process of exiting, we've ensured * that they will immediately restart on exit, unless * they are ONESHOT */ if (svc->flags & SVC_RUNNING) { // 如果这个service仍然是运行态的话,即return return; } needs_console = (svc->flags & SVC_CONSOLE) ? 1 : 0; if (needs_console && (!have_console)) { ERROR(service '%s' requires console, svc->name); svc->flags |= SVC_DISABLED; return; } // 如果这个service的flags是初始console,但是这个已经启动了的话,就会设置当前的flags为disabled if (stat(svc->args[0], &s) != 0) { // 如果要执行的这个service的start的command不存在的话,返回error ERROR(cannot find '%s', disabling '%s', svc->args[0], svc->name); svc->flags |= SVC_DISABLED; return; } if ((!(svc->flags & SVC_ONESHOT)) && dynamic_args) { // 因为dynamic_args为null,所以这边不会进入这个判断 ERROR(service '%s' must be one-shot to use dynamic args, disabling, svc->args[0]); svc->flags |= SVC_DISABLED; return; } // ***********************************************************这里我们可以认为是第二个阶段,selinux是信息安全相关的操作,这边我们忽略掉 if (is_selinux_enabled() >0) { if (svc->seclabel) {scon = strdup(svc->seclabel);if (!scon) { ERROR(Out of memory while starting '%s', svc->name); return;} } else {char *mycon = NULL, *fcon = NULL;INFO(computing context for service '%s', svc->args[0]);rc = getcon(&mycon);if (rc < 0) { ERROR(could not get context while starting '%s', svc->name); return;}rc = getfilecon(svc->args[0], &fcon);if (rc < 0) { ERROR(could not get context while starting '%s', svc->name); freecon(mycon); return;}rc = security_compute_create(mycon, fcon, string_to_security_class(process), &scon);if (rc == 0 && !strcmp(scon, mycon)) { ERROR(Warning! Service %s needs a SELinux domain defined; please fix!, svc->name);}freecon(mycon);freecon(fcon);if (rc < 0) { ERROR(could not get context while starting '%s', svc->name); return;} } } // ***************************************** selinux的操作结束,进入到第三个阶段 NOTICE(starting '%s', svc->name); pid = fork(); // fork一个自进程,即所有从init.rc启动的service,都是一个子进程 if (pid == 0) { // pid = 0, 进入到子进程中 struct socketinfo *si; struct svcenvinfo *ei; char tmp[32]; int fd, sz; umask(077); if (properties_inited()) { get_property_workspace(&fd, &sz); // 得到属性存储空间的信息并加入到环境变量中sprintf(tmp, %d,%d, dup(fd), sz);add_environment(ANDROID_PROPERTY_WORKSPACE, tmp); } for (ei = svc->envvars; ei; ei = ei->next) // 将service自己声明的env加入到环境变量中add_environment(ei->name, ei->value); for (si = svc->sockets; si; si = si->next) { // 根据socket info设置socketint socket_type = ( !strcmp(si->type, stream) ? SOCK_STREAM :(!strcmp(si->type, dgram) ? SOCK_DGRAM : SOCK_SEQPACKET));int s = create_socket(si->name, socket_type, si->perm, si->uid, si->gid, si->socketcon ?: scon);if (s >= 0) { publish_socket(si->name, s);} } freecon(scon); scon = NULL; if (svc->ioprio_class != IoSchedClass_NONE) {if (android_set_ioprio(getpid(), svc->ioprio_class, svc->ioprio_pri)) { ERROR(Failed to set pid %d ioprio = %d,%d: %s, getpid(), svc->ioprio_class, svc->ioprio_pri, strerror(errno));} } if (needs_console) {setsid();open_console(); } else {zap_stdio(); }#if 0 for (n = 0; svc->args[n]; n++) {INFO(args[%d] = '%s', n, svc->args[n]); } for (n = 0; ENV[n]; n++) {INFO(env[%d] = '%s', n, ENV[n]); }#endif setpgid(0, getpid()); /* as requested, set our gid, supplemental gids, and uid */ if (svc->gid) { // 设置gidif (setgid(svc->gid) != 0) { ERROR(setgid failed: %s, strerror(errno)); _exit(127);} } if (svc->nr_supp_gids) {if (setgroups(svc->nr_supp_gids, svc->supp_gids) != 0) { ERROR(setgroups failed: %s, strerror(errno)); _exit(127);} } if (svc->uid) { // 设置uidif (setuid(svc->uid) != 0) { ERROR(setuid failed: %s, strerror(errno)); _exit(127);} } if (svc->seclabel) {if (is_selinux_enabled() >0 && setexeccon(svc->seclabel) < 0) { ERROR(cannot setexeccon('%s'): %s, svc->seclabel, strerror(errno)); _exit(127);} } if (!dynamic_args) { // 因为dynamic_args设置的为null,我们在第一次从init.rc启动的时候,一定会进入到这个判断,if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) { // !!! 执行当前的service的启动的命令,也就是说从这边开始,我们就可以理解为已经从init进程中,去像kernel执行init一样,就去执行各个service所对应的启动函数了! ERROR(cannot execve('%s'): %s, svc->args[0], strerror(errno));} } else {char *arg_ptrs[INIT_PARSER_MAXARGS+1];int arg_idx = svc->nargs;char *tmp = strdup(dynamic_args);char *next = tmp;char *bword;/* Copy the static arguments */memcpy(arg_ptrs, svc->args, (svc->nargs * sizeof(char *)));while((bword = strsep(&next, ))) { arg_ptrs[arg_idx++] = bword; if (arg_idx == INIT_PARSER_MAXARGS) break;}arg_ptrs[arg_idx] = '';execve(svc->args[0], (char**) arg_ptrs, (char**) ENV); } _exit(127); } freecon(scon); if (pid < 0) { ERROR(failed to start '%s', svc->name); svc->pid = 0; return; } svc->time_started = gettime(); svc->pid = pid; svc->flags |= SVC_RUNNING; if (properties_inited()) notify_service_state(svc->name, running);}

终于结束了漫长的init进程的分析,估计这十篇文章可以基本概括了init进程启动过程中的每一个细节。 但是,这样是不够的,我们需要接下来继续看一下,android系统启动流程所继续的其他部分。 接下来,也就是我们启动过程中常见的zygote了!

相关专题 应用程序过程