这篇博客参考老罗的文章:http://blog.csdn.net/luoshengyang/article/details/45601143。
1.JankTracker的初始化流程
上一篇博客详细分析了GraphicsStatsService的工作流程,还遗留了一个问题就是各种卡顿类型的信息具体是怎么统计的。在回答这个问题之前我们先来看另外一个疑问:
上一篇博客中说过,JankTracker是在initThreadLocal()中被初始化的:
void RenderThread::initThreadLocals() { nsecs_t frameIntervalNanos = static_cast(1000000000 / mDisplayInfo.fps); mTimeLord.setFrameInterval(frameIntervalNanos); initializeDisplayEventReceiver(); mEglManager = new EglManager(*this); mRenderState = new RenderState(*this); mJankTracker = new JankTracker(frameIntervalNanos);}
这个函数定义在文件frameworks/base/libs/hwui/renderthread/RenderThread.cpp中。
那么这initThreadLocal()怎么被调用的呢?让我们从硬件渲染的初始化流程开始说起。
根据老罗的博客,Activity组件在创建的过程中,也就是在其生命周期函数onCreate的调用过程中,一般会通过调用另外一个成员函数 setContentView创建和初始化关联的窗口视图,最后通过调用ViewRoot类的成员函数setView完成这一过程。到了Android 4.0之后,ViewRoot类的名字改成了ViewRootImpl,它们的作用仍然一样的。
Android应用程序UI硬件加速渲染环境的初始化过程是在ViewRootImpl类的成员函数setView开始,如下:
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { ...... public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; ...... if (view instanceof RootViewSurfaceTaker) { mSurfaceHolderCallback = ((RootViewSurfaceTaker)view).willYouTakeTheSurface(); if (mSurfaceHolderCallback != null) { mSurfaceHolder = new TakenSurfaceHolder(); mSurfaceHolder.setFormat(PixelFormat.UNKNOWN); } } ...... // If the application owns the surface, don't enable hardware acceleration if (mSurfaceHolder == null) { enableHardwareAcceleration(attrs); } ...... } } } ......}
这个函数定义在文件frameworks/base/core/java/android/view/ViewRootImpl.java中。
参数view描述的是当前正在创建的Activity窗口的顶级视图。如果它实现了RootViewSurfaceTaker接口,并且通过该接口的成 员函数willYouTakeTheSurface提供了一个SurfaceHolder.Callback2接口,那么就表明应用程序想自己接管对窗口 的一切渲染操作。这样创建出来的Activity窗口就类似于一个SurfaceView一样,完全由应用程序自己来控制它的渲染。
基本上我们是不会将一个Activity窗口当作一个SurfaceView来使用的,因此在ViewRootImpl类的成员变量 mSurfaceHolder将保持为null值,这样就会导致ViewRootImpl类的成员函数 enableHardwareAcceleration被调用为判断是否需要为当前创建的Activity窗口启用硬件加速渲染。
private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) { mAttachInfo.mHardwareAccelerated = false; mAttachInfo.mHardwareAccelerationRequested = false; // Don't enable hardware acceleration when the application is in compatibility mode if (mTranslator != null) return; // Try to enable hardware acceleration if requested final boolean hardwareAccelerated = (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; if (hardwareAccelerated) { if (!HardwareRenderer.isAvailable()) { return; } // Persistent processes (including the system) should not do // accelerated rendering on low-end devices. In that case, // sRendererDisabled will be set. In addition, the system process // itself should never do accelerated rendering. In that case, both // sRendererDisabled and sSystemRendererDisabled are set. When // sSystemRendererDisabled is set, PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED // can be used by code on the system process to escape that and enable // HW accelerated drawing. (This is basically for the lock screen.) final boolean fakeHwAccelerated = (attrs.privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED) != 0; final boolean forceHwAccelerated = (attrs.privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED) != 0; if (fakeHwAccelerated) { // This is exclusively for the preview windows the window manager // shows for launching applications, so they will look more like // the app being launched. mAttachInfo.mHardwareAccelerationRequested = true; } else if (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled && forceHwAccelerated)) { if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.destroy(); } final boolean translucent = attrs.format != PixelFormat.OPAQUE; mAttachInfo.mHardwareRenderer = HardwareRenderer.create(mContext, translucent); if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString()); mAttachInfo.mHardwareAccelerated = mAttachInfo.mHardwareAccelerationRequested = true; } } } }
这个函数定义在文件frameworks/base/core/java/android/view/ViewRootImpl.java中。
这里面的大部分操作都是在处理是否开始硬件加速。虽然硬件加速渲染是个好东西,但是也不是每一个需要绘制UI的进程都必需的。这样做是考虑到两个因素。第一个因素是并不是所有的Canvas API都可以被GPU支持。如果应用程序使用到了这些不被GPU支持的API,那么就需要禁用硬件加速渲染。第二个因素是支持硬件加速渲染的代价是增加了 内存开销。例如,只是硬件加速渲染环境初始化这一操作,就要花掉8M的内存。所以有的进程就不适合开启硬件加速,主要是Persistent进程和System进程,详细看老罗博客。
上述代码中重点的是这句:
mAttachInfo.mHardwareRenderer = HardwareRenderer.create(mContext, translucent);
如果当前创建的窗口支持硬件加速渲染,那么就会调用HardwareRenderer类的静态成员函数create创建一个 HardwareRenderer对象,并且保存在与该窗口关联的一个AttachInfo对象的成员变量mHardwareRenderer 中。这个HardwareRenderer对象以后将负责执行窗口硬件加速渲染的相关操作。
那么这个HardwareRenderer是何方神圣呢?让我们继续往下看。
public abstract class HardwareRenderer { ...... static HardwareRenderer create(Context context, boolean translucent) { HardwareRenderer renderer = null; if (GLES20Canvas.isAvailable()) { renderer = new ThreadedRenderer(context, translucent); } return renderer; } ......}
这个函数定义在文件frameworks/base/core/java/android/view/HardwareRenderer.java。
可以看到,如果当前设备支持GLES2.0,才能开启硬件加速,通过ThreadedRenderer来完成这个加速任务。这个类继承于HardwareRenderer。这意思着Android硬件加速目前只支持GLES?
接下来我们就继续分析ThreadedRenderer对象的创建过程,如下所示:
public class ThreadedRenderer extends HardwareRenderer { ...... private long mNativeProxy; ...... private RenderNode mRootNode; ...... ThreadedRenderer(Context context, boolean translucent) { ...... long rootNodePtr = nCreateRootRenderNode(); mRootNode = RenderNode.adopt(rootNodePtr); ...... mNativeProxy = nCreateProxy(translucent, rootNodePtr); AtlasInitializer.sInstance.init(context, mNativeProxy); ...... } ......}
可以看到,ThreadedRenderer在构造函数中主要干了三件事,新建了一个RootRenderNode,一个ThreadProxy和调用AtlasInitializer进行资源集的初始化。
这里我们先看看第二个,因为这玩意儿上一篇博客我们好像看过。很明显这是一个JNI调用,Native层对应的实现是:
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中。
这里利用了上面生成的rootRenderNode来生成了一个RenderProxy,看来这个rootRenderNode还是挺重要的,以后有机会再看吧,先看看RenderProxy的构造函数:
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类有三个重要的成员变量mRenderThread、mContext和mDrawFrameTask,它们的类型分别为 RenderThread、CanvasContext和DrawFrameTask。其中,mRenderThread描述的就是Render Thread,mContext描述的是一个画布上下文,mDrawFrameTask描述的是一个用来执行渲染任务的Task。接下来我们先重点分析RenderThread的初始化过程。
从构造函数中可以看出,mRenderThread的默认值是RenderThread::getInstance(),这个应该是个单例模式,也就是说在一个Android应用程序进程中,只有一个Render Thread存在。
继续看RenderThread的构造过程:
RenderThread::RenderThread() : Thread(true), Singleton() ...... { mFrameCallbackTask = new DispatchFrameCallbacks(this); mLooper = new Looper(false); run("RenderThread");}
这个函数定义在文件frameworks/base/libs/hwui/renderthread/RenderThread.cpp中。
这里同样也干了三件事情,新建了一个DispatchFrameCallbacks,一个Looper和调用了run函数。
DispatchFrameCallbacks对象,用来描述一个帧绘制任务。下面描述RenderThread的运行模型时,我们再详细分析。RenderThread类的成员变量mLooper指向一个Looper对象,RenderThread通过它来创建一个消息驱动运行模型,类似于Main Thread的消息驱动运行模型。
RenderThread类是从Thread类继承下来的,当我们调用它的成员函数run的时候,就会创建一个新的线程。这个新的线程的入口点函数为RenderThread类的成员函数threadLoop,它的实现如下所示:
bool RenderThread::threadLoop() { ....... initThreadLocals(); int timeoutMillis = -1; for (;;) { int result = mLooper->pollOnce(timeoutMillis); ...... nsecs_t nextWakeup; // Process our queue, if we have anything while (RenderTask* task = nextTask(&nextWakeup)) { task->run(); // task may have deleted itself, do not reference it again } if (nextWakeup == LLONG_MAX) { timeoutMillis = -1; } else { nsecs_t timeoutNanos = nextWakeup - systemTime(SYSTEM_TIME_MONOTONIC); timeoutMillis = nanoseconds_to_milliseconds(timeoutNanos); if (timeoutMillis < 0) { timeoutMillis = 0; } } if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) { drainDisplayEventQueue(true); mFrameCallbacks.insert( mPendingRegistrationFrameCallbacks.begin(), mPendingRegistrationFrameCallbacks.end()); mPendingRegistrationFrameCallbacks.clear(); requestVsync(); } } return false;}
这个函数定义在文件frameworks/base/libs/hwui/renderthread/RenderThread.cpp中。
在这里终于看了我们索要找的initThreadLocals()!很激动有木有~也就是说在硬件初始化渲染的时候,即当一个窗口视图初始化的时候,其对应的JankTracker就已经被建立起来了,等到窗口视图真正渲染的时候,用来统计渲染信息。
2.渲染统计信息的收集过程
接上一篇博客,要弄懂渲染信息的收集过程,我们就得知道JankTracker::addFrame是在哪里被调用的。首先让我们回头去看看这个函数:
void JankTracker::addFrame(const FrameInfo& frame) { mData->totalFrameCount++; using namespace FrameInfoIndex; // Fast-path for jank-free frames int64_t totalDuration = frame[kFrameCompleted] - frame[kIntendedVsync]; uint32_t framebucket = frameCountIndexForFrameTime( totalDuration, (sizeof(mData->frameCounts) / sizeof(mData->frameCounts[0])) ); //keep the fast path as fast as possible if (CC_LIKELY(totalDuration < mFrameInterval)) { mData->frameCounts[framebucket]++; return; } //exempt this frame, so drop it if (frame[kFlags] & EXEMPT_FRAMES_FLAGS) { return; } mData->frameCounts[framebucket]++; mData->jankFrameCount++; for (int i = 0; i < NUM_BUCKETS; i++) { int64_t delta = frame[COMPARISONS[i].end] - frame[COMPARISONS[i].start]; if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) { mData->jankTypeCounts[i]++; } }}
我们可以发现,这个函数有一个重要的参数FrameInfo类型的frame!这好像还是个数组。统计信息就是直接从它身上取出来的。赶紧地,我们去看看这个类:
class FrameInfo {public: void importUiThreadInfo(int64_t* info); void markSyncStart() { mFrameInfo[FrameInfoIndex::kSyncStart] = systemTime(CLOCK_MONOTONIC); } void markIssueDrawCommandsStart() { mFrameInfo[FrameInfoIndex::kIssueDrawCommandsStart] = systemTime(CLOCK_MONOTONIC); } void markSwapBuffers() { mFrameInfo[FrameInfoIndex::kSwapBuffers] = systemTime(CLOCK_MONOTONIC); } void markFrameCompleted() { mFrameInfo[FrameInfoIndex::kFrameCompleted] = systemTime(CLOCK_MONOTONIC); } int64_t operator[](FrameInfoIndexEnum index) const { if (index == FrameInfoIndex::kNumIndexes) return 0; return mFrameInfo[static_cast (index)]; } int64_t operator[](int index) const { if (index < 0 || index >= FrameInfoIndex::kNumIndexes) return 0; return mFrameInfo[static_cast (index)]; }private: int64_t mFrameInfo[FrameInfoIndex::kNumIndexes];};
这个类定义在frameworks/base/libs/hwui/FrameInfo.h文件中
可以看到,FrameInfo里面有很多markXXXStart的函数,这些函数的功能都是同样的,就是记录系统现在的时间放在mFrameInfo的不同位置中!很明显,这是在记录一个帧每一个渲染阶段的开始时间,以便后来做卡顿的统计。
同时在同一个文件中,还有另外一个类UiFrameInfoBuilder
class ANDROID_API UiFrameInfoBuilder {public: UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) { memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t)); } UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync) { mBuffer[FrameInfoIndex::kVsync] = vsyncTime; mBuffer[FrameInfoIndex::kIntendedVsync] = intendedVsync; return *this; } UiFrameInfoBuilder& addFlag(FrameInfoFlagsEnum flag) { mBuffer[FrameInfoIndex::kFlags] |= static_cast(flag); return *this; }private: int64_t* mBuffer;};
同样这里的setVsync函数记录了kVsync和kIntendedVsync的时间。因此,现在的问题就变成了这个函数和上面的那些markXXXStart函数是在什么时候被调用的?
通过搜索,我们可以发现,这些函数都是在同一个类中被调用的,那就是CanvasContext!主要涉及到两个函数:CanvasContext::prepareTree和CanvasContext::draw。下面我们看看这个三个函数,我们按时间的先后一个个看:
// Called by choreographer to do an RT-driven animationvoid CanvasContext::doFrame() { ... int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE]; UiFrameInfoBuilder(frameInfo) .addFlag(FrameInfoFlags::kRTAnimation) .setVsync(mRenderThread.timeLord().computeFrameTimeNanos(), mRenderThread.timeLord().latestVsync()); TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState()); prepareTree(info, frameInfo); if (info.out.canDrawThisFrame) { draw(); }}
这个doFrame函数定义在frameworks/base/libs/hwui/renderthread/CanvasContext.cpp中。
该函数首先新建了一个frameInfo数组来存放帧的各种渲染信息,大小是UI_THREAD_FRAME_INFO_SIZE,其实是9,具体的定义如下:
#define UI_THREAD_FRAME_INFO_SIZE 9HWUI_ENUM(FrameInfoIndex, kFlags = 0, kIntendedVsync, kVsync, kOldestInputEvent, kNewestInputEvent, kHandleInputStart, kAnimationStart, kPerformTraversalsStart, kDrawStart, // End of UI frame info kSyncStart, kIssueDrawCommandsStart, kSwapBuffers, kFrameCompleted, // Must be the last value! kNumIndexes);
这个枚举定义在frameworks/base/libs/hwui/FrameInfo.h中
这个9其实就是下面的HWUI_ENUM枚举类型前9个的意思,可以从注释中看到,这9个是UI帧的信息。
然后doFrame调用了UiFrameInfoBuilder来添加两个重要的时间,kVsync和kIntendedVsync,这两个是都是通过调用mRenderThread.timeLord()的相关函数来获得。再接着定义了一个TreeInfo类型的info,并和frameInfo传给了prepareTree。下面我们看看prepareTree。
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo) { mRenderThread.removeFrameCallback(this); mCurrentFrameInfo = &mFrames.next(); mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo); mCurrentFrameInfo->markSyncStart(); info.damageAccumulator = &mDamageAccumulator; info.renderer = mCanvas; if (mPrefetechedLayers.size() && info.mode == TreeInfo::MODE_FULL) { info.canvasContext = this; } mAnimationContext->startFrame(info.mode); mRootRenderNode->prepareTree(info); mAnimationContext->runRemainingAnimations(info); if (info.canvasContext) { freePrefetechedLayers(); } ... 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中,
这个函数首先调用mFrames.next()取出了一个mCurrentFrameInfo,然后通过importUiThreadInfo导入传过来的那个uiFrameInfo,然后调用markSyncStart()记录了SyncStart的时间点,表明Sync已经开始了,紧接着才开始进行真正的业务处理,例如处理一些相关的动画,和调用mRootRenderNode->prepareTree(info)来真正准备ViewTree,这应该是一个上传视图到GPU的过程。
prepareTree完成了以后,返回到frameInfo中,如果这个准备是成功的,下面就可以调用Draw函数马上开始绘制啦:
void CanvasContext::draw() { ... mCurrentFrameInfo->markIssueDrawCommandsStart(); SkRect dirty; mDamageAccumulator.finish(&dirty); EGLint width, height; mEglManager.beginFrame(mEglSurface, &width, &height); if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) { mCanvas->setViewport(width, height); dirty.setEmpty(); } else if (!mBufferPreserved || mHaveNewSurface) { dirty.setEmpty(); } else { if (!dirty.isEmpty() && !dirty.intersect(0, 0, width, height)) { ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?", SK_RECT_ARGS(dirty), width, height); dirty.setEmpty(); } profiler().unionDirty(&dirty); } status_t status; if (!dirty.isEmpty()) { status = mCanvas->prepareDirty(dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom, mOpaque); } else { status = mCanvas->prepare(mOpaque); } Rect outBounds; status |= mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds); profiler().draw(mCanvas); mCanvas->finish(); profiler().markPlaybackEnd(); // Even if we decided to cancel the frame, from the perspective of jank // metrics the frame was swapped at this point mCurrentFrameInfo->markSwapBuffers(); if (status & DrawGlInfo::kStatusDrew) { swapBuffers(); } else { mEglManager.cancelFrame(); } // TODO: Use a fence for real completion? mCurrentFrameInfo->markFrameCompleted(); mJankTracker.addFrame(*mCurrentFrameInfo); mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo); profiler().finishFrame();}
这个函数定义在frameworks/base/libs/hwui/renderthread/CanvasContext.cpp中,
一进来,就调用了markIssueDrawCommandsStart()记录绘制命令开始的时间,然后做了一大堆的工作,都是在设置一个dirty和mCanvas,弄好了以后调用mCanvas->drawRenderNode、profiler().draw(mCanvas),mCanvas->finish()标志着绘制工作的完成。
随后,调用markSwapBuffers()来记录开始交换缓冲区的时间,这是要开始显示了么?接着调用swapBuffers()来进行真正的缓冲交换工作,交换结束以后,这一个帧的绘制就全部完成了,调用markFrameCompleted()来记录绘制的结束时间,最后这些记录在mCurrentFrameInfo的帧信息添加到我们的mJankTracker中,这样,JankTracker就能统计渲染信息啦。
这样,大部分的时间节点的记录已经是清除的了,除了两个:kVsync和kIntendedVsync,前面说过,这两个函数是由mRenderThread.timeLord().computeFrameTimeNanos()和mRenderThread.timeLord(). latestVsync()取得的。
nsecs_t TimeLord::computeFrameTimeNanos() { // Logic copied from Choreographer.java nsecs_t now = systemTime(CLOCK_MONOTONIC); nsecs_t jitterNanos = now - mFrameTimeNanos; if (jitterNanos >= mFrameIntervalNanos) { nsecs_t lastFrameOffset = jitterNanos % mFrameIntervalNanos; mFrameTimeNanos = now - lastFrameOffset; } return mFrameTimeNanos;}
这个函数定义在frameworks/base/libs/hwui/renderthread/TimeLord.cpp
可以看到这个函数除了特殊情况下做了修正之外,是直接返回mFrameTimeNanos的。所以要看一下这个mFrameTimeNanos是怎么被赋值的。就在同一个文件中:
bool TimeLord::vsyncReceived(nsecs_t vsync) { if (vsync > mFrameTimeNanos) { mFrameTimeNanos = vsync; return true; } return false;}
mFrameTimeNanos被初始化为0,所以当这个函数被调用的时候,vsync > mFrameTimeNanos成立,所以mFrameTimeNanos被赋值为vsync,从函数名我们可以知道mFrameTimeNanos记录的就是接收到vsync的时间,即kVsync的时间。