本篇文章是自己的一个学习笔记,记录了 Android 5.0 中 hwui 中的 RenderThread 的简单工作流程。由于是学习笔记,所以其中一些细节不会太详细,我只是将大概的流程走一遍,将其工作流标注出来,下次遇到问题的时候就可以知道去哪里查。
下图是我用 Systrace 抓取的一个应用启动的时候 RenderThread 的第一次 Draw 的 Trace 图,从这里面的顺序来看 RenderThread 的流程。熟悉应用启动流程的话应该知道,只有当第一次 DrawFrame 完成之后,整个应用的界面才会显示在手机上,在这之前,用户看到的是应用的 StartingWindow 的界面。
应用程序的每一帧是从接收到 VSYNC 信号开始进行计算和绘制的,这要从 Choreographer 这个类说起了,不过由于篇幅原因,我们直接看一帧的绘制调用关系链即可:
Choreographer 的 drawFrame 会调用到 ViewRootImpl 的 performTraversals 方法,而 performTraversals 方法最终会调用到performDraw() 方法, performDraw 又会调用到 draw(boolean fullRedrawNeeded) 方法,这个 draw 方法是 ViewRootImpl 的私有方法,和我们熟知的那个draw并不是同一个方法
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
mIsAnimating = false;
boolean invalidateRoot = false;
if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
mHardwareYOffset = yOffset;
mHardwareXOffset = xOffset;
mAttachInfo.mHardwareRenderer.invalidateRoot();
}
mResizeAlpha = resizeAlpha;
dirty.setEmpty();
mBlockResizeBuffer = false;
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
}
如果是走硬件绘制路线的话,则会走这一条先,之后就会调用 mHardwareRenderer 的 draw 方法,这里的 mHardwareRenderer 指的是 ThreadedRenderer ,其 Draw 函数如下:
@Override
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
attachInfo.mIgnoreDirtyState = true;
long frameTimeNanos = mChoreographer.getFrameTimeNanos();
attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS;
long recordDuration = 0;
if (mProfilingEnabled) {
recordDuration = System.nanoTime();
}
updateRootDisplayList(view, callbacks);
if (mProfilingEnabled) {
recordDuration = System.nanoTime() - recordDuration;
}
attachInfo.mIgnoreDirtyState = false;
// register animating rendernodes which started animating prior to renderer
// creation, which is typical for animators started prior to first draw
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();
}
}
这个函数里面的 updateRootDisplayList(view, callbacks) ;即 getDisplayList 操作。接下来就是比较重要的一个操作:
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,
recordDuration, view.getResources().getDisplayMetrics().density);
可以看出这是一个阻塞操作,等Native层完成后,拿到返回值后才会进行下一步的操作。
其Native代码在android_view_ThreadedRenderer.cpp中,对应的实现代码如下:
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density) {
RenderProxy* proxy = reinterpret_cast(proxyPtr);
return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density);
}
RenderProxy的路径位于frameworks/base/libs/hwui/renderthread/RenderProxy.cpp
int RenderProxy::syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos, float density) { mDrawFrameTask.setDensity(density); return mDrawFrameTask.drawFrame(frameTimeNanos, recordDurationNanos); }
其中 mDrawFrameTask 是一个 DrawFrameTask 对象,其路径位于frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp,其中drawFrame代码:
int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos) {
mSyncResult = kSync_OK;
mFrameTimeNanos = frameTimeNanos;
mRecordDurationNanos = recordDurationNanos;
postAndWait();
// Reset the single-frame data
mFrameTimeNanos = 0;
mRecordDurationNanos = 0;
return mSyncResult;
}
其中 postAndWait() 的实现如下:
void DrawFrameTask::postAndWait() {
AutoMutex _lock(mLock);
mRenderThread->queue(this);
mSignal.wait(mLock);
}
就是将一个 DrawFrameTask 放入到了 mRenderThread 中,其中 queue 方法实现如下:
void RenderThread::queue(RenderTask* task) { AutoMutex _lock(mLock); mQueue.queue(task); if (mNextWakeup && task->mRunAt < mNextWakeup) { mNextWakeup = 0; mLooper->wake(); } }
其中 mQueue 是一个 TaskQueue 对象,其
void TaskQueue::queue(RenderTask* task) {
// Since the RenderTask itself forms the linked list it is not allowed
// to have the same task queued twice
LOG_ALWAYS_FATAL_IF(task->mNext || mTail == task, "Task is already in the queue!");
if (mTail) {
// Fast path if we can just append
if (mTail->mRunAt <= task->mRunAt) {
mTail->mNext = task;
mTail = task;
} else {
// Need to find the proper insertion point
RenderTask* previous = 0;
RenderTask* next = mHead;
while (next && next->mRunAt <= task->mRunAt) {
previous = next;
next = next->mNext;
}
if (!previous) {
task->mNext = mHead;
mHead = task;
} else {
previous->mNext = task;
if (next) {
task->mNext = next;
} else {
mTail = task;
}
}
}
} else {
mTail = mHead = task;
}
}
接着看 RenderThread 之前的 queue 方法,
void Looper::wake() { ssize_t nWrite; do { nWrite = write(mWakeWritePipeFd, "W", 1); } while (nWrite == -1 && errno == EINTR); if (nWrite != 1) { if (errno != EAGAIN) { ALOGW("Could not write wake signal, errno=%d", errno); } } }
wake 函数则更为简单,仅仅向管道的写端写入一个字符“W”,这样管道的读端就会因为有数据可读而从等待状态中醒来。
接下来会到哪里去,我们首先要熟悉一下RenderThread,RenderThread是继承自Thread的,这个Thread是utils/Thread.h,RenderThread的初始化函数
RenderThread::RenderThread() : Thread(true), Singleton()
, mNextWakeup(LLONG_MAX)
, mDisplayEventReceiver(0)
, mVsyncRequested(false)
, mFrameCallbackTaskPending(false)
, mFrameCallbackTask(0)
, mRenderState(NULL)
, mEglManager(NULL) {
mFrameCallbackTask = new DispatchFrameCallbacks(this);
mLooper = new Looper(false);
run("RenderThread");
}
其 run 方法在 Thread 中有说明:
// Start the thread in threadLoop() which needs to be implemented.
virtual status_t run( const char* name = 0,
int32_t priority = PRIORITY_DEFAULT,
size_t stack = 0);
即启动 threadLoop 函数,我们来看 RenderThread 的 threadLoop 函数,这个函数比较重要:
bool RenderThread::threadLoop() { #if defined(HAVE_PTHREADS) setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY); #endif initThreadLocals(); int timeoutMillis = -1; for (;;) { int result = mLooper->pollOnce(timeoutMillis); LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR, "RenderThread Looper POLL_ERROR!"); 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; }
可以看到,一个 for 循环是一个无限循环,而其中 pollOnce 是一个阻塞函数,直到我们上面调用了 mLooper->wake() 之后,会继续往下走,走到 while 循环中:
while (RenderTask* task = nextTask(&nextWakeup)) { task->run(); // task may have deleted itself, do not reference it again }
会将 RenderTask 取出来执行其 run 方法,经过前面的流程我们知道这个 RenderTask 是一个 DrawFrameTask ,其run方法如下:
void DrawFrameTask::run() { ATRACE_NAME("DrawFrame"); mContext->profiler().setDensity(mDensity); mContext->profiler().startFrame(mRecordDurationNanos); bool canUnblockUiThread; bool canDrawThisFrame; { TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState()); canUnblockUiThread = syncFrameState(info); canDrawThisFrame = info.out.canDrawThisFrame; } // Grab a copy of everything we need CanvasContext* context = mContext; // From this point on anything in "this" is *UNSAFE TO ACCESS* if (canUnblockUiThread) { unblockUiThread(); } if (CC_LIKELY(canDrawThisFrame)) { context->draw(); } if (!canUnblockUiThread) { unblockUiThread(); } }
上面说到了 DrawFrameTask 的 run 方法,这里 run 方法中的执行的方法即我们在最前面那张图中所示的部分(即文章最前面那张图),下面的流程就是那张图中的函数调用,我们结合代码和图,一部分一部分来走整个 DrawFrame 的流程:
第一个比较重要的函数是 syncFrameState ,从函数名就可以知道, syncFrameState 的作用就是同步 frame 信息,将 Java 层维护的 frame 信息同步到 RenderThread中。
Main Thread 和Render Thread 都各自维护了一份应用程序窗口视图信息。各自维护了一份应用程序窗口视图信息的目的,就是为了可以互不干扰,进而实现最大程度的并行。其中,Render Thread维护的应用程序窗口视图信息是来自于 Main Thread 的。因此,当Main Thread 维护的应用程序窗口信息发生了变化时,就需要同步到 Render Thread 去。
所以查看代码就可以知道有两个 RenderNode,一个在 hwui 中,一个在 View 中。简单来说,同步信息就是将 Java 层的 RenderNode 中的信息同步到 hwui 中的 RenderNode 中。 注意syncFrameState的返回值赋给了 canUnblockUiThread ,从名字可以看出这个 canUnblockUiThread 的作用是判断是否唤醒 Main Thread ,也就是说如果返回为 true 的话,会提前唤醒主线程来执行其他的事情,而不用等到 draw 完成后再去唤醒 Main Thread。 这也是 Android 5.0 和 Android 4.x 最大的区别了。
bool DrawFrameTask::syncFrameState(TreeInfo& info) { mRenderThread->timeLord().vsyncReceived(mFrameTimeNanos); mContext->makeCurrent(); Caches::getInstance().textureCache.resetMarkInUse(); for (size_t i = 0; i < mLayers.size(); i++) { mContext->processLayerUpdate(mLayers[i].get()); } mLayers.clear(); mContext->prepareTree(info); if (info.out.hasAnimations) { if (info.out.requiresUiRedraw) { mSyncResult |= kSync_UIRedrawRequired; } } // If prepareTextures is false, we ran out of texture cache space return info.prepareTextures; }
首先是makeCurrent,这里的mContext是一个CanvasContext对象,其makeCurrent实现如下:
void CanvasContext::makeCurrent() {
// In the meantime this matches the behavior of GLRenderer, so it is not a regression
mHaveNewSurface |= mEglManager.makeCurrent(mEglSurface);
}
mEglManager是一个EglManager对象,其实现为:
bool EglManager::makeCurrent(EGLSurface surface) {
if (isCurrent(surface)) return false;
if (surface == EGL_NO_SURFACE) {
// If we are setting EGL_NO_SURFACE we don't care about any of the potential
// return errors, which would only happen if mEglDisplay had already been
// destroyed in which case the current context is already NO_CONTEXT
TIME_LOG("eglMakeCurrent", eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
} else {
EGLBoolean success;
TIME_LOG("eglMakeCurrent", success = eglMakeCurrent(mEglDisplay, surface, surface, mEglContext));
if (!success) {
LOG_ALWAYS_FATAL("Failed to make current on surface %p, error=%s",
(void*)surface, egl_error_str());
}
}
mCurrentSurface = surface;
return true;
}
这里会判断mCurrentSurface == surface,如果成立,则不用再初始化操作,如果是另外一个surface。,则会执行eglMakeCurrent,来重新创建上下文。
makeCurrent之后,会调用mContext->prepareTree(info),其实现如下:
void CanvasContext::prepareTree(TreeInfo& info) { mRenderThread.removeFrameCallback(this); 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. TIME_LOG("nativeWindowQuery", 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); } } }
其中 mRootRenderNode->prepareTree(info) 又是最重要的。回到Java层,我们知道 ThreadedRenderer 在初始化时,初始化了一个指针
long rootNodePtr = nCreateRootRenderNode();
这个RootRenderNode也就是一个根Node,
mRootNode = RenderNode.adopt(rootNodePtr);
然后会创建一个 mNativeProxy 指针,在 Native 层初始化一个 RenderProxy 对象,将 rootNodePtr 传给 RenderProxy 对象,这样在 RenderProxy 我们就可以得到这个对象的指针了。其中 CanvasContext 也是在 RenderProxy 对象初始化的时候被初始化的,初始化的时候将 rootNodePtr 传给了 CanvasContext 对象。
我们之前提到 ThreadedRenderer 的 draw 方法中首先会调用updateRootDisplayList,即我们熟悉的 getDisplayList 。这个方法中,其实也分为两个步骤,第一个步骤是 updateViewTreeDisplayList,第二个步骤是将根 Node 加入到 DrawOp 中:
canvas.insertReorderBarrier();
canvas.drawRenderNode(view.getDisplayList());
canvas.insertInorderBarrier();
其最终实现在
status_t DisplayListRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t flags) {
LOG_ALWAYS_FATAL_IF(!renderNode, "missing rendernode");
// dirty is an out parameter and should not be recorded,
// it matters only when replaying the display list
DrawRenderNodeOp* op = new (alloc()) DrawRenderNodeOp(renderNode, flags, *currentTransform());
addRenderNodeOp(op);
return DrawGlInfo::kStatusDone;
}
再回到我们之前的 CanvasContext.prepareTree 中提到的 mRootRenderNode->prepareTree(info),这时候这里的 mRootRenderNode 就是 CanvasContext 初始化是传进来的。
其实现在 RenderNode.cpp 中:
void RenderNode::prepareTree(TreeInfo& info) {
prepareTreeImpl(info);
}
void RenderNode::prepareTreeImpl(TreeInfo& info) {
TT_START_MARK(getName());
info.damageAccumulator->pushTransform(this);
if (info.mode == TreeInfo::MODE_FULL) {
pushStagingPropertiesChanges(info); //同步当前正在处理的Render Node的Property
}
uint32_t animatorDirtyMask = 0;
if (CC_LIKELY(info.runAnimations)) {
animatorDirtyMask = mAnimatorManager.animate(info);//执行动画相关的操作
}
prepareLayer(info, animatorDirtyMask);
if (info.mode == TreeInfo::MODE_FULL) {
pushStagingDisplayListChanges(info); //同步当前正在处理的Render Node的Display List
}
prepareSubTree(info, mDisplayListData); //同步当前正在处理的Render Node的Display List引用的Bitmap,以及当前正在处理的Render Node的子Render Node的Display List等信息
pushLayerUpdate(info); //检查当前正在处理的Render Node是否设置了Layer。如果设置了的话,就对这些Layer进行处理
info.damageAccumulator->popTransform();
TT_END_MARK();
}
这里所涉及到的进一步的具体操作大家可以自行去看代码。
执行完syncFrameState之后,接下来就是执行draw
if (CC_LIKELY(canDrawThisFrame)) {
context->draw();
}
CanvasContext的draw函数是一个核心函数,其位置在 frameworks/base/libs/hwui/OpenGLRenderer.cpp ,其实现如下:
void CanvasContext::draw() {
profiler().markPlaybackStart();
SkRect dirty;
mDamageAccumulator.finish(&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();
if (status & DrawGlInfo::kStatusDrew) {
swapBuffers();
}
profiler().finishFrame();
/// M: enable to get overdraw count
if (CC_UNLIKELY(g_HWUI_debug_overdraw)) {
if (!mDebugOverdrawLayer) {
mDebugOverdrawLayer = LayerRenderer::createRenderLayer(mRenderThread.renderState(),
mCanvas->getWidth(), mCanvas->getHeight());
} else if (mDebugOverdrawLayer->layer.getWidth() != mCanvas->getWidth() ||
mDebugOverdrawLayer->layer.getHeight() != mCanvas->getHeight()) {
if (!LayerRenderer::resizeLayer(mDebugOverdrawLayer, mCanvas->getWidth(), mCanvas->getHeight())) {
LayerRenderer::destroyLayer(mDebugOverdrawLayer);
mDebugOverdrawLayer = NULL;
}
}
......
}
首先来看eglBeginFrame的实现
void EglManager::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) {
makeCurrent(surface);
if (width) {
eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, width);
}
if (height) {
eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height);
}
eglBeginFrame(mEglDisplay, surface);
}
makeCurrent是用来管理上下文,eglBeginFrame主要是校验参数的合法性。
status_t status;
if (!dirty.isEmpty()) {
status = mCanvas->prepareDirty(dirty.fLeft, dirty.fTop,
dirty.fRight, dirty.fBottom, mOpaque);
} else {
status = mCanvas->prepare(mOpaque);
}
这里的mCanvas是一个OpenGLRenderer对象,其prepareDirty实现
//TODO:增加函数功能描述
status_t OpenGLRenderer::prepareDirty(float left, float top,
float right, float bottom, bool opaque) {
setupFrameState(left, top, right, bottom, opaque);
// Layer renderers will start the frame immediately
// The framebuffer renderer will first defer the display list
// for each layer and wait until the first drawing command
// to start the frame
if (currentSnapshot()->fbo == 0) {
syncState();
updateLayers();
} else {
return startFrame();
}
return DrawGlInfo::kStatusDone;
}
Rect outBounds;
status |= mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
接下来就是调用OpenGLRenderer的drawRenderNode方法进行绘制
status_t OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t replayFlags) {
status_t status;
// All the usual checks and setup operations (quickReject, setupDraw, etc.)
// will be performed by the display list itself
if (renderNode && renderNode->isRenderable()) {
// compute 3d ordering
renderNode->computeOrdering();
if (CC_UNLIKELY(mCaches.drawDeferDisabled)) { //判断是否不重排序
status = startFrame();
ReplayStateStruct replayStruct(*this, dirty, replayFlags);
renderNode->replay(replayStruct, 0);
return status | replayStruct.mDrawGlStatus;
}
// 需要重新排序
bool avoidOverdraw = !mCaches.debugOverdraw && !mCountOverdraw; // shh, don't tell devs!
DeferredDisplayList deferredList(*currentClipRect(), avoidOverdraw);
DeferStateStruct deferStruct(deferredList, *this, replayFlags);
renderNode->defer(deferStruct, 0); //递归进行重排操作
flushLayers(); // 首先执行设置了 Layer 的子 Render Node 的绘制命令,以便得到一个对应的FBO
status = startFrame(); //执行一些诸如清理颜色绘冲区等基本操作
status = deferredList.flush(*this, dirty) | status;
return status;
}
// Even if there is no drawing command(Ex: invisible),
// it still needs startFrame to clear buffer and start tiling.
return startFrame();
}
这里的 renderNode 是一个 Root Render Node,
可以看到,到了这里虽然只是开始,但是其实已经结束了,这个函数里面最重要的几步:
renderNode->defer(deferStruct, 0); //进行重排序
flushLayers(); 首先执行设置了 Layer 的子 Render Node 的绘制命令,以便得到一个对应的FBO
status = deferredList.flush(*this, dirty) | status; //对deferredList中的绘制命令进行真正的绘制操作
这几个是渲染部分真正的核心部分,其中的代码细节需要自己去研究。老罗在这部分讲的很细,有空可以去看看他的文章 Android应用程序UI硬件加速渲染的Display List渲染过程分析 .
if (status & DrawGlInfo::kStatusDrew) {
swapBuffers();
}
其核心就是调用EGL的 eglSwapBuffers(mEglDisplay, surface), duration)函数。
profiler().finishFrame();
主要是记录时间信息。
鉴于我比较懒,而且总结能力不如老罗,就直接把他的总结贴过来了。RenderThread的总的流程如下:
将Main Thread维护的Display List同步到Render Thread维护的Display List去。这个同步过程由Render Thread执行,但是Main Thread会被阻塞住。
如果能够完全地将Main Thread维护的Display List同步到Render Thread维护的Display List去,那么Main Thread就会被唤醒,此后Main Thread和Render Thread就互不干扰,各自操作各自内部维护的Display List;否则的话,Main Thread就会继续阻塞,直到Render Thread完成应用程序窗口当前帧的渲染为止。
Render Thread在渲染应用程序窗口的Root Render Node的Display List之前,首先将那些设置了Layer的子Render Node的Display List渲染在各自的一个FBO上,接下来再一起将这些FBO以及那些没有设置Layer的子Render Node的Display List一起渲染在Frame Buffer之上,也就是渲染在从Surface Flinger请求回来的一个图形缓冲区上。这个图形缓冲区最终会被提交给Surface Flinger合并以及显示在屏幕上。
第2步能够完全将Main Thread维护的Display List同步到Render Thread维护的Display List去很关键,它使得Main Thread和Render Thread可以并行执行,这意味着Render Thread在渲染应用程序窗口当前帧的Display List的同时,Main Thread可以去准备应用程序窗口下一帧的Display List,这样就使得应用程序窗口的UI更流畅。
注意最后一段,在 Android 4.x 时代,没有RenderThread的时代,只有 Main Thread ,也就是说 必须要等到 Draw 完成后,才会去准备下一帧的数据,如下图:
Android5.0 之后,如老罗所说,有两种情况,
可以看到第二张图中,Render Thread 并没有绘制完成,但是由于其提前唤醒了 Main Thread ,所以 Main Thread 在下一个Vsync信号到来的时候,响应了Vsync事件,开始准备下一帧。此时虽然由于第一帧绘制时间过长,导致掉了一帧,但是第二帧没有收到任何影响。
1 概述
Android从3.0(API Level 11)开始,在绘制View的时候支持硬件加速,充分利用GPU的特性,使得绘制更加平滑。
实质上就是Android3.0以前,几乎所有的图形绘制都是由Skia完成,Skia是一个向量绘图库,使用CPU来进行运算;所以从Android3.0 开始,Google用hwui取代了Skia,准确的说,是推荐取代,因为Opengl的支持不完全,有少量图形api仍由Skia完成,多数view的绘制通过HWUI模块使用openGL的函数来实现。
由于硬件加速自身并非完美无缺,所以Android提供选项来打开或者关闭硬件加速,默认是关闭。一般我们会在两个级别上打开或者关闭硬件加速:
Application级别:
Activity级别:
下面我们从代码级别来理解系统是如何实现硬件加速的。
在开始分析之前,我们先来看下ViewRootImpl中的一个基本流程图:
如上图所示,其实过程2就是打开硬件加速的过程,在ViewRootImpl的SetView函数中,通过调用enableHardwareAcceleration函数来开启硬件加速,我们来看下enableHardwareAcceleration的代码:
- private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
- mAttachInfo.mHardwareAccelerated = false;
- mAttachInfo.mHardwareAccelerationRequested = false;
- 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 (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled
- && forceHwAccelerated)) {
- // Don't enable hardware acceleration when we're not on the main thread
- if (!HardwareRenderer.sSystemRendererDisabled &&
- Looper.getMainLooper() != Looper.myLooper()) {
- Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware "
- + "acceleration outside of the main thread, aborting");
- return;
- }
- if (mAttachInfo.mHardwareRenderer != null) {
- mAttachInfo.mHardwareRenderer.destroy(true);
- }
- final boolean translucent = attrs.format != PixelFormat.OPAQUE;
- mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent);
- if (mAttachInfo.mHardwareRenderer != null) {
- mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString());
- mAttachInfo.mHardwareAccelerated =
- mAttachInfo.mHardwareAccelerationRequested = true;
- }
- } else if (fakeHwAccelerated) {
- // The window had wanted to use hardware acceleration, but this
- // is not allowed in its process. By setting this flag, it can
- // still render as if it was accelerated. This is basically for
- // the preview windows the window manager shows for launching
- // applications, so they will look more like the app being launched.
- mAttachInfo.mHardwareAccelerationRequested = true;
- }
- }
- }
上面代码不长,而且注释很完善,所以我们主要来看下函数的最主体部分即可。
- static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) {
- switch (glVersion) {
- case 2:
- return Gl20Renderer.create(translucent);
- }
- throw new IllegalArgumentException("Unknown GL version: " + glVersion);
- }
- static HardwareRenderer create(boolean translucent) {
- if (GLES20Canvas.isAvailable()) {
- return new Gl20Renderer(translucent);
- }
- return null;
- }
- GlRenderer(int glVersion, boolean translucent) {
- mGlVersion = glVersion;
- mTranslucent = translucent;
- loadSystemProperties(null);
- }
我们发现这段代码最主要的作用就是创建了一个Gl20Renderer(继承自GlRenderer和HardwareRenderer)。创建时调用了通过loadSystemProperties函数加载了一些属性和调试选项,这里不展开,有兴趣可以详细研究一下。
创建好的Gl20Renderer保存在了mAttachInfo.mHardwareRenderer中,后面我们在使用硬件加速时就会用到。
从前面图中我们可以知道,在ViewRootImpl绘制时,调用了draw函数。有两种选择,一种是软件绘制,一种是硬件绘制。
- if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
- // Draw with hardware renderer.
- mIsAnimating = false;
- mHardwareYOffset = yoff;
- mResizeAlpha = resizeAlpha;
- mCurrentDirty.set(dirty);
- dirty.setEmpty();
- attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
- animating ? null : mCurrentDirty);
- } else {
- // If we get here with a disabled & requested hardware renderer, something went
- // wrong (an invalidate posted right before we destroyed the hardware surface
- // for instance) so we should just bail out. Locking the surface with software
- // rendering at this point would lock it forever and prevent hardware renderer
- // from doing its job when it comes back.
- // Before we request a new frame we must however attempt to reinitiliaze the
- // hardware renderer if it's in requested state. This would happen after an
- // eglTerminate() for instance.
- if (attachInfo.mHardwareRenderer != null &&
- !attachInfo.mHardwareRenderer.isEnabled() &&
- attachInfo.mHardwareRenderer.isRequested()) {
- try {
- attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
- mHolder.getSurface());
- } catch (OutOfResourcesException e) {
- handleOutOfResourcesException(e);
- return;
- }
- mFullRedrawNeeded = true;
- scheduleTraversals();
- return;
- }
- if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
- return;
- }
- }
- }
软件绘制不在我们分析范围内,这里我们将在下一小节中来研究一下attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,animating ? null : mCurrentDirty)这个调用。
我们前面已经知道mHardwareRenderer其实是一个Gl20Renderer,那么我们来看下它的draw函数。
- void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
- Rect dirty) {
- if (canDraw()) {
- if (surfaceState != SURFACE_STATE_ERROR) {
- HardwareCanvas canvas = mCanvas;
- attachInfo.mHardwareCanvas = canvas;
- dirty = beginFrame(canvas, dirty, surfaceState);
- DisplayList displayList = buildDisplayList(view, canvas);
- int saveCount = 0;
- int status = DisplayList.STATUS_DONE;
- try {
- status = prepareFrame(dirty);
- if(mSurface.isValid() && dirty != null) {
- mSurface.setDirtyRect(dirty);
- }
- saveCount = canvas.save();
- callbacks.onHardwarePreDraw(canvas);
- if (displayList != null) {
- status |= drawDisplayList(attachInfo, canvas, displayList, status);
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, "An error has occurred while drawing:", e);
- } finally {
- callbacks.onHardwarePostDraw(canvas);
- canvas.restoreToCount(saveCount);
- view.mRecreateDisplayList = false;
- if (mDrawDelta > 0) {
- mFrameCount++;
- debugOverdraw(attachInfo, dirty, canvas, displayList);
- debugDirtyRegions(dirty, canvas);
- drawProfileData(attachInfo);
- }
- }
- onPostDraw();
- swapBuffers(status);
- }
- }
- }
上面这几十行代码,就是硬件绘制的最重要的几个流程调用,我们看到draw函数主要分为如下几步,beginFrame,buildDisplayList,prepareFrame,drawDisplayList,onHardwarePostDraw,onPostDraw,swapBuffers。整个详细的调用过程如下图:
从上图中我们可以看出,整个内部详细的调用过程还是比较复杂的,我们这里仔细的一一讲解一下。
这个函数的作用很简单:Notifies EGL that the frame is about to be rendered。
功能主要通过android_view_HardwareRenderer_beginFrame这个JNI函数实现:
- static void android_view_HardwareRenderer_beginFrame(JNIEnv* env, jobject clazz,
- jintArray size) {
- EGLDisplay display = eglGetCurrentDisplay();
- EGLSurface surface = eglGetCurrentSurface(EGL_DRAW);
- if (size) {
- EGLint value;
- jint* storage = env->GetIntArrayElements(size, NULL);
- eglQuerySurface(display, surface, EGL_WIDTH, &value);
- storage[0] = value;
- eglQuerySurface(display, surface, EGL_HEIGHT, &value);
- storage[1] = value;
- env->ReleaseIntArrayElements(size, storage, 0);
- }
- eglBeginFrame(display, surface);
在BeginFrame() 里获取EGLDisplay(用于显示) 和一个EGLSurface,OpenGL将在这个Surface上进行绘图。
eglBeginFrame主要是校验参数的合法性。
buildDisplayList是一个很重要也比较复杂的函数,通过这个函数,我们真正完成了视图绘制的录制工作,并将操作指令保存到了native层的DisplayList中,我们这里重点讲一下。
- private DisplayList buildDisplayList(View view, HardwareCanvas canvas) {
- if (mDrawDelta <= 0) {//如果根本没有新的绘制发生,使用之前的displaylist即可
- return view.mDisplayList;
- }
- view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
- == View.PFLAG_INVALIDATED;
- view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
- long buildDisplayListStartTime = startBuildDisplayListProfiling();
- canvas.clearLayerUpdates();
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
- DisplayList displayList = view.getDisplayList();
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- endBuildDisplayListProfiling(buildDisplayListStartTime);
- return displayList;
- }
其实主要逻辑是通过view类的getDisplayList()方法实现的,这个方法通过递归的方式获得了整个View树的DisplayList:
- private DisplayList getDisplayList(DisplayList displayList, boolean isLayer) {
- if (((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 ||
- displayList == null || !displayList.isValid() ||
- (!isLayer && mRecreateDisplayList))) {
- // Don't need to recreate the display list, just need to tell our
- // children to restore/recreate theirs
- if (displayList != null && displayList.isValid() &&
- !isLayer && !mRecreateDisplayList) {
- mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
- mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- dispatchGetDisplayList();
- return displayList;
- }
- if (!isLayer) {
- // If we got here, we're recreating it. Mark it as such to ensure that
- // we copy in child display lists into ours in drawChild()
- mRecreateDisplayList = true;
- }
- if (displayList == null) {
- displayList = mAttachInfo.mHardwareRenderer.createDisplayList(getClass().getName());
- // If we're creating a new display list, make sure our parent gets invalidated
- // since they will need to recreate their display list to account for this
- // new child display list.
- invalidateParentCaches();
- }
- boolean caching = false;
- int width = mRight - mLeft;
- int height = mBottom - mTop;
- int layerType = getLayerType();
- final HardwareCanvas canvas = displayList.start(width, height);
- ...
上面函数相当长,我们首先分析前半部分。暂时只看displaylist新创建时会走的逻辑:函数前半部分的流程基本是createDisplayList,invalidateParentCaches,displayList.start,我们来一点点分析。
首先我们要创建一个displaylist,这里实际上只是创建了一个GLES20DisplayList类。
- public DisplayList createDisplayList(String name) {
- return new GLES20DisplayList(name);
- }
GLES20DisplayList是DisplayList的子类,这是一个影子类,没有实际用途,主要用来管理Native层的DisplayList的销毁。Native层的DisplayList在后面的start函数中才被真正创建。
这个函数的作用主要是,一旦我们重建了DisplayList,那么这个view必须清空之前的缓存,我们通过这个函数调用来清空缓存。
- protected void invalidateParentCaches() {
- if (mParent instanceof View) {
- ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
- }
- }
下面看下displayList.start函数:
- public HardwareCanvas start(int width, int height) {
- mValid = false;
- mCanvas = GLES20RecordingCanvas.obtain(this);
- mCanvas.start();
- mCanvas.setViewport(width, height);
- // The dirty rect should always be null for a display list
- mCanvas.onPreDraw(null);
- return mCanvas;
- }
start函数真正开始构建DisplayList的上下文,先来看下obtain函数:
- static GLES20RecordingCanvas obtain(GLES20DisplayList displayList) {
- GLES20RecordingCanvas canvas = sPool.acquire();
- if (canvas == null) {
- canvas = new GLES20RecordingCanvas();
- }
- canvas.mDisplayList = displayList;
- return canvas;
- }
obtain函数的作用从名字中就可以看出来,主要作用是获取到一个GLES20RecordingCanvas,这个类是 HardwareCanvas和GLES20Canvas的子类。
HardwareCanvas是一个抽象类,如果系统属性和应用程序指定使用硬件加速(现已成为默认),它将会被View(通过AttachInfo,如 下图所示) 引用来完成所有的图形绘制工作。GLES20Canvas 则是hardwareCanvas的实现,但它也只是一层封装而已,真正的实现在Native 层,通过jni (andriod_view_gles20Canvas.cpp)接口来访问底层的Renderer, 进而执行OpenGL的命令。 此外,GLES20Canvas还提供了一些静态接口,用于创建各类Renderer对象。
GLES20RecordingCanvas 继承GLES20Canvas, 通过它调用的OpenGL命令将会存储在DisplayList里面,而不会立即执行。
- protected GLES20Canvas(boolean record, boolean translucent) {
- mOpaque = !translucent;
- if (record) {
- mRenderer = nCreateDisplayListRenderer();
- } else {
- mRenderer = nCreateRenderer();
- }
- setupFinalizer();
- }
我们看到GLES20Canvas在创建时,由于我们现在需要录制命令,所以我们这里调用了nCreateDisplayListRenderer函数,创建的真正的用于渲染的本地对象DisplayListRenderer:
- static OpenGLRenderer* android_view_GLES20Canvas_createDisplayListRenderer(JNIEnv* env,
- jobject clazz) {
- return new DisplayListRenderer;
- }
DisplayListRenderer是OpenGLRenderer的子类,相比于OpenGLRenderer是直接绘制,DisplayListRenderer则是将绘制命令保存在它内部的mDisplayListData中,mDisplayListData的类型是DisplayListData:
- class DisplayListData : public LightRefBase
{ - public:
- LinearAllocator allocator;
- Vector
displayListOps; - };
我们看到这里的重点必然是这个容器displayListOps,一定是它存储了绘制命令DisplayListOp,至于DisplayListOp的详细内容,我们后面再展开。
这样,通过nCreateDisplayListRenderer函数,我们创建了一个本地的DisplayListRenderer对象,他会将绘制命令保存在一个DisplayListData类的DisplayListOp容器里。
现在我们再次回到GLES20Canvas的创建过程中来,调用完nCreateDisplayListRenderer后,程序又继续调用了setupFinalizer,这个我们从名字就可以猜出来这是用于这个canvas回收的,这里我们就不再展开。
回到displayList.start函数中来,创建好GLES20RecordingCanvas之后,继续调用了它的start函数。这个函数主要是清空自己和它的孩子的所有display的引用,这里不再展开。
继续调用setViewport来设置视口:
- public void setViewport(int width, int height) {
- mWidth = width;
- mHeight = height;
- nSetViewport(mRenderer, width, height);
- }
- static void android_view_GLES20Canvas_setViewport(JNIEnv* env, jobject clazz,
- OpenGLRenderer* renderer, jint width, jint height) {
- renderer->setViewport(width, height);
- }
- void DisplayListRenderer::setViewport(int width, int height) {
- mOrthoMatrix.loadOrtho(0, width, height, 0, -1, 1);
- mWidth = width;
- mHeight = height;
- }
- void Matrix4::loadOrtho(float left, float right, float bottom, float top, float near, float far) {
- loadIdentity();
- data[kScaleX] = 2.0f / (right - left);
- data[kScaleY] = 2.0f / (top - bottom);
- data[kScaleZ] = -2.0f / (far - near);
- data[kTranslateX] = -(right + left) / (right - left);
- data[kTranslateY] = -(top + bottom) / (top - bottom);
- data[kTranslateZ] = -(far + near) / (far - near);
- mType = kTypeTranslate | kTypeScale | kTypeRectToRect;
- }
我们看到上层调用了视口,下层实际上还是通过DisplayListRenderer做了具体实现,主要是做了一个正交投影,具体计算我们这里暂时先不研究。
其实大多数opengl在Canvas层面上的调用都是这样,底层通过render实现了类似OpenGl的实现,后面类似函数我们将不再展开。
GLES20RecordingCanvas.onPreDraw函数其实也很简单,先来看代码:
- public int onPreDraw(Rect dirty) {
- if (dirty != null) {
- return nPrepareDirty(mRenderer, dirty.left, dirty.top, dirty.right, dirty.bottom,
- mOpaque);
- } else {
- return nPrepare(mRenderer, mOpaque);
- }
- }
- static int android_view_GLES20Canvas_prepare(JNIEnv* env, jobject clazz,
- OpenGLRenderer* renderer, jboolean opaque) {
- return renderer->prepare(opaque);
- }
- status_t OpenGLRenderer::prepare(bool opaque) {
- return prepareDirty(0.0f, 0.0f, mWidth, mHeight, opaque);
- }
- status_t OpenGLRenderer::prepareDirty(float left, float top,
- float right, float bottom, bool opaque) {
- setupFrameState(left, top, right, bottom, opaque);
- // Layer renderers will start the frame immediately
- // The framebuffer renderer will first defer the display list
- // for each layer and wait until the first drawing command
- // to start the frame
- if (mSnapshot->fbo == 0) {
- syncState();
- updateLayers();
- } else {
- return startFrame();
- }
- return DrawGlInfo::kStatusDone;
- }
本身这个onPreDraw其实是很简单的,无非是通过canvas来设置要显示的脏区域。最终逻辑依然是通过native层的OpenGLRenderer来实现,当然,这个实现最后变得有点复杂,因为这里又引入了一个新的概念,SnapShot:
- void OpenGLRenderer::setupFrameState(float left, float top,
- float right, float bottom, bool opaque) {
- mCaches.clearGarbage();
- mOpaque = opaque;
- mSnapshot = new Snapshot(mFirstSnapshot,
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
- mSnapshot->fbo = getTargetFbo();
- mSaveCount = 1;
- mSnapshot->setClip(left, top, right, bottom);
- mTilingClip.set(left, top, right, bottom);
- }
我们这章引入的新概念实在过多,因此我们这里先跳过这个概念,可以参考这篇博客来先了解一下这个快照的概念,我们后面将单独开章节讲这个问题。
由于getDisplayList实在是逻辑比较长,涉及的新概念比较多,我们这里暂时先暂停一下,回顾下前半部分的逻辑::函数前半部分的流程基本是createDisplayList,invalidateParentCaches,displayList.start。createDisplayList的作用是创建了一个Java层的GLES20DisplayList,而displayList.start重点则是创建了一个native层GLES20RecordingCanvas,在创建Canvas的同时创建了DisplayListRenderer,DisplayListRenderer将需要执行的命令保存在内部的displaylist列表中,然后设置了视口,设置了脏区域,这里我们跳过了Snapshot这个新概念。
下面我们继续来看下getDisplayList后半段的代码。
- {
- ...
- try {
- if (!isLayer && layerType != LAYER_TYPE_NONE) {
- if (layerType == LAYER_TYPE_HARDWARE) {
- final HardwareLayer layer = getHardwareLayer();
- if (layer != null && layer.isValid()) {
- canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
- } else {
- if (layer != null && !layer.isValid()) Log.e("ViewSystem", "View #2 getHardwareLayer() is not valid.");
- canvas.saveLayer(0, 0, mRight - mLeft, mBottom - mTop, mLayerPaint,
- Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
- Canvas.CLIP_TO_LAYER_SAVE_FLAG);
- }
- caching = true;
- } else {
- buildDrawingCache(true);
- Bitmap cache = getDrawingCache(true);
- if (cache != null) {
- canvas.drawBitmap(cache, 0, 0, mLayerPaint);
- caching = true;
- }
- }
- } else {
- computeScroll();
- canvas.translate(-mScrollX, -mScrollY);
- if (!isLayer) {
- mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
- mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- }
- // Fast path for layouts with no backgrounds
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
- dispatchDraw(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().draw(canvas);
- }
- } else {
- draw(canvas);
- }
- }
- }
- ...
上面这段代码涉及到两条分支,第一条分支又涉及到了新概念,layerType和HardwareLayer,这条分支比较复杂,我们单独放在下一章中单独讲解,我们现在重点看下第二条分支的代码:
- {
- ...
- else {
- computeScroll();
- canvas.translate(-mScrollX, -mScrollY);
- if (!isLayer) {
- mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
- mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- }
- // Fast path for layouts with no backgrounds
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
- dispatchDraw(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().draw(canvas);
- }
- } else {
- draw(canvas);
- }
- }
- }
computeScroll计算滚动位置,和Graphic关系不大,不展开。
translate函数我们在上一节 Android 4.4 Graphic系统详解(4) 一个view的绘制之旅 中已经讲过了,这里不再展开。
其实就是创建了一个Op类的对象,然后将它保存在了DisplayListData的displayListOps容器里面,便于后面的执行。
回到view类getdisplaylist函数中来,说完了translate,下面就是重要的绘制生成流程了。
- {
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
- dispatchDraw(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().draw(canvas);
- }
- } else {
- draw(canvas);
- }
- }
其中dispatchDraw是通知view的所有孩子进行绘制(?),其实流程应该都是类似的,因此我们直接看当前view的draw流程,此函数将把View自身及其所有的子子孙孙绘制到给定的画布上。其画图过程主要分为以下六步:
1) 画背景
2) 如果需要,保存画布的层为未来的淡入淡出做好准备
3) 画View本身的内容
4) 画它的孩子
5) 如果需要,画淡入淡出的边缘并恢复层
6) 画装饰部分(如:滚动条)
上述六步中,2,5步常规情况下不需要,因此我们暂时跳过,只重点分析1,3,6步。
- public void draw(Canvas canvas) {
- // Step 1, draw the background, if needed
- int saveCount;
- if (!dirtyOpaque) {
- final Drawable background = mBackground;
- if (background != null) {
- final int scrollX = mScrollX;
- final int scrollY = mScrollY;
- if (mBackgroundSizeChanged) {
- background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
- mBackgroundSizeChanged = false;
- }
- if ((scrollX | scrollY) == 0) {
- background.draw(canvas);
- } else {
- canvas.translate(scrollX, scrollY);
- background.draw(canvas);
- canvas.translate(-scrollX, -scrollY);
- }
- }
- }
- // skip step 2 & 5 if possible (common case)
- final int viewFlags = mViewFlags;
- boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
- boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
- if (!verticalEdges && !horizontalEdges) {
- // Step 3, draw the content
- if (!dirtyOpaque) onDraw(canvas);
- // Step 4, draw the children
- dispatchDraw(canvas);
- // Step 6, draw decorations (scrollbars)
- onDrawScrollBars(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().dispatchDraw(canvas);
- }
- // we're done...
- return;
- }
- }
绘制背景很简单,只是调用了 background.draw(canvas)函数,其中background是一个Drawable类型,关于Drawable的绘制,依然可以参考“一个view的绘制之旅”一章中的Drawable的绘制小节讲解,这里不再展开。
这一过程比较复杂,其实也就是我们在上一章“一个view的绘制之旅”中讲到的全部内容,具体内容可参考上一章。
绘制滚动条的代码比较长,但是按照我们一贯的习惯,我们只关注和Graphic相关的部分,在onDrawScrollBars函数中有横向和纵向ScrollBar的区别,但是对于下层的Graphic并无这种区别,都是调用ScrollBarDrawable的draw函数:
- public void draw(Canvas canvas) {
- boolean drawTrack = true;
- Rect r = getBounds();
- if (drawTrack) {
- drawTrack(canvas, r, vertical);
- }
- if (drawThumb) {
- int size = vertical ? r.height() : r.width();
- int thickness = vertical ? r.width() : r.height();
- int length = Math.round((float) size * extent / range);
- int offset = Math.round((float) (size - length) * mOffset / (range - extent));
- drawThumb(canvas, r, offset, length, vertical);
- }
- }
drawThumb中逻辑转来转去,最终其实还是在drawThumb中再次调用了Drawable的draw函数,这里不再详述。
最终,通过漫长的过程,我们终于完成了displaylist的构建,在完成了buildDisplayList的调用之后,我们回到Gl20Renderer类,继续来看下的draw函数,接下来就该Displaylist的真正执行了。