EN

Android hwui 中 RenderThread 工作流程

字数统计: 5k阅读时长: 23 min
2015/08/12
loading

前言

本篇文章是自己的一个学习笔记,记录了 Android 5.0 中 hwui 中的 RenderThread 的简单工作流程。由于是学习笔记,所以其中一些细节不会太详细,我只是将大概的流程走一遍,将其工作流标注出来,下次遇到问题的时候就可以知道去哪里查。

下图是我用 Systrace 抓取的应用启动时 RenderThread 第一次 Draw 的历史 Trace。它能帮助观察线程内的执行顺序,但 DrawFrame 区间结束不等于画面已显示:还需要核对 GPU 完成、缓冲区消费、合成和显示的时点。启动期间可能先看到 StartingWindow,它的退出也不能仅凭这段 CPU Trace 判断。

版本说明(2026-10-13 核查):本文保留 Android 5.0 的历史流程,源码以 AOSP android-5.0.0_r1 为基准。原截图含设备侧插桩,事件名称不必与 AOSP 完全相同;下方补全了原稿的省略片段,并统一到此标签。旧 OpenGLRenderer、DisplayListRenderer 和 EGL 细节已过时,不能直接套到当前系统;文末补充 Android 16 的对应实现和 Android 12 起的 FrameTimeline。本文讨论 View 硬件加速路径,不涵盖所有自定义 Surface 渲染。

RenderThread Draw first frame

从Java层说起

有待处理的帧回调时,Choreographer 通常按 VSYNC 节奏调度 UI 工作;不是每个 VSYNC 都必然重绘所有 View。这里从一次已调度的 traversal 看绘制调用链:

绘制关系链

入口是 Choreographer.doFrame(),不是 drawFrame()。它执行 CALLBACK_TRAVERSAL 阶段的回调,经 ViewRootImpl.TraversalRunnable.run() → doTraversal() → performTraversals(),在需要绘制时进入 performDraw() → draw(boolean fullRedrawNeeded)。最后这个方法是 ViewRootImpl 的私有方法,不是自定义 View 常覆写的 onDraw()。

下面是该方法中进入硬件绘制的分支,外层控制流略去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
            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()。本文路径中的 renderer 是 ThreadedRenderer,其 draw 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@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) 更新或复用 View 树的 Display List,并按需记录根节点;Trace 中对应 getDisplayList。它记录绘制命令,不等于 GPU 此时已经绘制像素。接着调用:

1
2
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,
recordDuration, view.getResources().getDisplayMetrics().density);

UI 线程会在 Native 层的 postAndWait() 等待 RenderThread 发信号,拿到 syncResult 才返回。但等待可能在 context->draw() 之前结束,不是一直等到 GPU 完成或画面上屏。具体唤醒位置见后面的 DrawFrameTask::run()。

Native层

其Native代码在android_view_ThreadedRenderer.cpp中,对应的实现代码如下:

1
2
3
4
5
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density);
}

RenderProxy的路径位于frameworks/base/libs/hwui/renderthread/RenderProxy.cpp

1
2
3
4
5
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代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos) {
LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");

mSyncResult = kSync_OK;
mFrameTimeNanos = frameTimeNanos;
mRecordDurationNanos = recordDurationNanos;
postAndWait();

// Reset the single-frame data
mFrameTimeNanos = 0;
mRecordDurationNanos = 0;

return mSyncResult;
}

其中 postAndWait() 的实现如下:

1
2
3
4
5
void DrawFrameTask::postAndWait() {
AutoMutex _lock(mLock);
mRenderThread->queue(this);
mSignal.wait(mLock);
}

这把当前 DrawFrameTask 放入 RenderThread 的任务队列。UI 线程随后在条件变量上等待,等待期间释放 mLock;恢复后重新持有锁。RenderThread 的 queue 实现如下:

1
2
3
4
5
6
7
8
void RenderThread::queue(RenderTask* task) {
AutoMutex _lock(mLock);
mQueue.queue(task);
if (mNextWakeup && task->mRunAt < mNextWakeup) {
mNextWakeup = 0;
mLooper->wake();
}
}

mQueue 是 TaskQueue,按任务的 mRunAt 排序入队;同一个任务对象不能重复排队。其 queue 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 方法,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ wake", this);
#endif

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);
}
}
}

在这个 Android 5.0 标签中,wake() 向唤醒管道写入字符 W,使监听它的 Looper 可以退出等待。并非每次入队都要写管道:上面的 queue 仅在新任务早于当前下一次唤醒时才调用它。

HWUI-RenderThread

接下来会到哪里去,我们首先要熟悉一下RenderThread,RenderThread是继承自Thread的,这个Thread是utils/Thread.h,RenderThread的初始化函数

1
2
3
4
5
6
7
8
9
10
11
12
RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
, 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 中有说明:

1
2
3
4
// 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 函数,这个函数比较重要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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(timeoutMillis) 可以因唤醒管道、其他文件描述符事件或超时而返回;超时为 0 时不阻塞,并非只能等 wake()。之后 nextTask() 取出已到执行时间的任务:

1
2
3
4
while (RenderTask* task = nextTask(&nextWakeup)) {
task->run();
// task may have deleted itself, do not reference it again
}

任务队列不只存放绘制任务;在本文这次入队中,取出的是 DrawFrameTask。它的 run 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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();
}
}

unblockUiThread() 与前面的 mSignal.wait(mLock) 配对:

1
2
3
4
void DrawFrameTask::unblockUiThread() {
AutoMutex _lock(mLock);
mSignal.signal();
}

这里的 signal 使 UI 线程有机会恢复运行,不保证它立即得到 CPU;若无法提前唤醒,就在绘制分支处理后发信号,无论这次调用了 draw() 还是跳过绘制。两处都不是 GPU 完成通知。

RenderThread.DrawFrame

DrawFrameTask::run() 串起了开头 Trace 中的几个阶段。下面结合代码和图,继续看同步与绘制的细节:

1. syncFrameState

syncFrameState 在 RenderThread 上准备这一帧需要的状态,包括处理 layer 更新、同步节点树、准备纹理和推进动画。

UI 线程和 RenderThread 使用分阶段维护的渲染状态。这里不是把完整的 Java View 树复制到另一棵树:Java RenderNode 通过 mNativeRenderNode 持有 native 节点,native 节点内部有 mStagingProperties / mProperties 与 mStagingDisplayListData / mDisplayListData 两组状态。

prepareTree 在 MODE_FULL 下推动 staging 状态进入渲染使用的状态:属性按 dirty 标记更新,Display List 数据可通过指针转交,而不是每帧逐项深拷贝。源码中的 canUnblockUiThread 来自 info.prepareTextures;它描述本次能否提前放行 UI 线程,不是“同步成功”或“GPU 绘制成功”的通用返回值。例如此版本的 prepareSubTree 遇到 ownedBitmapResources 就会关闭这个标志,所以不能从 false 单独断定纹理缓存不足。

syncFrameState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
ATRACE_CALL();
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实现如下:

1
2
3
4
void CanvasContext::makeCurrent() {
// In the meantime this matches the behavior of GLRenderer, so it is not a regression
mHaveNewSurface |= mEglManager.makeCurrent(mEglSurface);
}

mEglManager是一个EglManager对象,其实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
} else if (!eglMakeCurrent(mEglDisplay, surface, surface, mEglContext)) {
LOG_ALWAYS_FATAL("Failed to make current on surface %p, error=%s",
(void*)surface, egl_error_str());
}
mCurrentSurface = surface;
return true;
}

isCurrent(surface) 为真时直接返回;切换 surface 时,eglMakeCurrent 把已有的 mEglContext 与读写 surface 绑定到当前线程。它不重新创建 EGLContext,创建由 eglCreateContext 完成;传入 EGL_NO_CONTEXT 的分支则解除当前绑定。

makeCurrent之后,会调用mContext->prepareTree(info),其实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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.
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 在初始化时,初始化了一个指针

1
long rootNodePtr = nCreateRootRenderNode();

这个 native 根节点由 ThreadedRenderer 持有,Java RenderNode.adopt() 为它建立包装对象,不会再创建一个独立的 native 根节点。

1
mRootNode = RenderNode.adopt(rootNodePtr);

随后 nCreate 创建 RenderProxy,把根节点指针传入。RenderProxy 的构造过程再向 RenderThread 投递创建 CanvasContext 的任务,并等待结果;CanvasContext 也持有同一个根节点。不要把这理解成在 UI 线程直接完成全部渲染上下文初始化。

回到 updateRootDisplayList:它先调用 updateViewTreeDisplayList,再在 renderer 的根节点需要更新或无效时记录根 Display List。其中 canvas.drawRenderNode(view.getDisplayList()) 记录的是对根 View 节点的绘制引用,不是立刻绘制它:

1
2
3
canvas.insertReorderBarrier();
canvas.drawRenderNode(view.getDisplayList());
canvas.insertInorderBarrier();

对应的 Native 记录方法如下:

1
2
3
4
5
6
7
8
9
10
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void RenderNode::prepareTree(TreeInfo& info) {
ATRACE_CALL();
LOG_ALWAYS_FATAL_IF(!info.damageAccumulator, "DamageAccumulator missing");

prepareTreeImpl(info);
}

void RenderNode::prepareTreeImpl(TreeInfo& info) {
info.damageAccumulator->pushTransform(this);

if (info.mode == TreeInfo::MODE_FULL) {
pushStagingPropertiesChanges(info);
}
uint32_t animatorDirtyMask = 0;
if (CC_LIKELY(info.runAnimations)) {
animatorDirtyMask = mAnimatorManager.animate(info);
}
prepareLayer(info, animatorDirtyMask);
if (info.mode == TreeInfo::MODE_FULL) {
pushStagingDisplayListChanges(info);
}
prepareSubTree(info, mDisplayListData);
pushLayerUpdate(info);

info.damageAccumulator->popTransform();
}

这里所涉及到的进一步的具体操作大家可以自行去看代码。

2. draw

Draw

同步之后,只有 canDrawThisFrame 为真才调用 draw();这个版本会查询消费者是否落后,必要时跳过本次绘制并安排后续帧回调。

1
2
3
if (CC_LIKELY(canDrawThisFrame)) {
context->draw();
}

CanvasContext::draw() 位于 frameworks/base/libs/hwui/renderthread/CanvasContext.cpp。下面给出 AOSP 5.0 的完整方法;原稿省略的 beginFrame、viewport 与 dirty 区域处理也在这里。厂商额外的 overdraw 调试代码不属于这个 AOSP 标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void CanvasContext::draw() {
LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
"drawRenderNode called on a context with no canvas or surface!");

profiler().markPlaybackStart();

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 (!mDirtyRegionsEnabled || 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();

if (status & DrawGlInfo::kStatusDrew) {
swapBuffers();
}

profiler().finishFrame();
}

2.1 eglBeginFrame

CanvasContext::draw() 先调用 mEglManager.beginFrame():

1
2
3
4
5
6
7
8
9
10
11
12
void EglManager::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) {
LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
"Tried to beginFrame on EGL_NO_SURFACE!");
makeCurrent(surface);
if (width) {
eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, width);
}
if (height) {
eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height);
}
eglBeginFrame(mEglDisplay, surface);
}

这个方法先确保 context/surface 绑定并查询宽高,再调用 Android 当时的 eglBeginFrame 入口。在此标签的 frameworks/native/opengl/libs/EGL/eglApi.cpp 中,后者主要做 trace 和 display/surface 合法性检查;它不是通用 EGL 标准中的“等待 GPU 开始一帧”操作。

2.2 prepareDirty

1
2
3
4
5
6
7
8
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实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}

2.3 drawRenderNode

1
2
Rect outBounds;
status |= mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);

接下来就是调用OpenGLRenderer的drawRenderNode方法进行绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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();
status = startFrame();

return deferredList.flush(*this, dirty) | status;
}

// Even if there is no drawing command(Ex: invisible),
// it still needs startFrame to clear buffer and start tiling.
return startFrame();
}

这里的 renderNode 是 renderer 的根节点。

这里按默认的 deferred 路径,先整理绘制操作、处理待更新的 layer,再启动窗口目标的绘制并回放批次。下面四行只是上面方法的流程摘录:

1
2
3
4
renderNode->defer(deferStruct, 0);
flushLayers();
status = startFrame();
return deferredList.flush(*this, dirty) | status;

defer / flush 还涉及满足绘制语义约束的合批与排序,不能把所有命令任意重排,也不是给每个 View 都新建一个 FBO。它们向图形驱动发出绘制工作,函数返回不证明 GPU 已执行完。更细的历史分析可参考老罗的《Android 应用程序 UI 硬件加速渲染的 Display List 渲染过程分析》。

2.4 swapBuffers

1
2
3
if (status & DrawGlInfo::kStatusDrew) {
swapBuffers();
}

CanvasContext::swapBuffers() 经 EglManager::swapBuffers() 调用 eglSwapBuffers(mEglDisplay, surface),并处理 surface 失效等错误。提交的缓冲区还要由消费者获取,经过 SurfaceFlinger/HWC 合成与显示;swap 返回不等于像素此刻已上屏。缓冲区交接还受同步 fence 约束,不能把提交时刻当成 GPU 完成时刻。

2.5 FinishFrame

1
profiler().finishFrame();

这里主要结束 profiler 的帧统计,不是等待 GPU 或显示完成的同步点。

总结

鉴于我比较懒,而且总结能力不如老罗,下面沿着他的总结顺序回看流程,并按 AOSP 5.0 补上几个边界:

  1. UI 线程记录 View 的绘制状态,提交 DrawFrameTask 后等待。RenderThread 同步 native RenderNode 的 staging 状态并处理纹理、layer 和动画。
  2. 若 info.prepareTextures 允许提前放行,就在绘制分支之前唤醒 UI 线程;否则在绘制分支之后唤醒。该分支也可能跳过绘制。两条路径都不能当成 GPU 完成或最终显示的通知。
  3. 默认 deferred 路径处理需要更新的离屏 layer,并把窗口绘制结果写入从 Surface/BufferQueue 生产端取得的图形缓冲区,再提交给消费者。对于这里的应用窗口,后续由 SurfaceFlinger/HWC 完成合成与显示。

第 2 步给了 UI 线程与 RenderThread 并行工作的机会:RenderThread 处理当前帧时,UI 线程可能已经开始准备下一帧。但两个线程仍有同步和资源依赖,提前唤醒不保证下一帧一定准时。

在 Android 4.x 的这条 View/HWUI 路径中,Display List 记录和回放的 CPU 工作主要在 UI 线程上,尚未拆到本文的 RenderThread。因此 UI 线程需要先走完当前绘制调用再处理后续工作;GPU 和 SurfaceFlinger 当时也可以异步工作,并不是整个图形系统只有 Main Thread。如下图:

Android 4.x UI 线程上的 Display List 回放

Android 5.0 的下面两张历史图展示了线程之间的重叠:

Main Thread 和 Render Thread

Render Thread 提前唤醒了 Main Thread

第二张图显示 UI 线程的下一次 traversal 与前一帧 RenderThread 工作发生了重叠,这说明提前唤醒可以减少串行等待。但图中没有完整的 GPU fence、SurfaceFlinger 与最终呈现证据,不能据此断言“恰好掉了一帧”或“第二帧完全不受影响”。RenderThread 排队、GPU 积压和 BufferQueue 回压仍可能影响后续帧。

2026 年补充:现在如何对应这条链路

  • Android 16 / AOSP android-16.0.0_r1:DrawFrameTask 仍有投递、状态同步、按条件唤醒 UI 线程的过程,但队列接口、帧信息和 Trace 名称已经变化。此标签记录 DrawFrames <vsyncId>,帧信息还包含 deadline、interval 等,不能把 5.0 的函数签名或固定帧预算当成当前合同。
  • 绘制后端:此标签的 CanvasContext 通过 IRenderPipeline 进入 SkiaOpenGLPipeline 或 SkiaVulkanPipeline。选择取决于配置,不能认为所有设备都走 GLES;Vulkan 路径中名为 swapBuffers 的内部方法也不是 eglSwapBuffers。原文的旧 OpenGLRenderer / DeferredDisplayList 分析保留为历史实现。
  • Android 12 / API 31 起:可在 Perfetto 中结合 FrameTimeline 看 Expected/Actual Timeline、jank 类型及应用帧到 SurfaceFlinger 帧的关联。应用 Actual slice 结束取 GPU 完成与提交时间的较晚者,仍不同于最终上屏;再看对应 SurfaceFlinger/DisplayFrame,才能追踪显示结果。采集缺少这些轨道时,不把单个 DrawFrame CPU 区间当作完整帧耗时或掉帧数。

排查时可以沿着原文的顺序,但把问题分开:UI 线程是在记录 Display List,还是在等同步;RenderThread 是未获得 CPU、处理纹理/绘制,还是等缓冲区;GPU 和显示端是否已经完成各自工作。这样才能判断该优化的是哪一段,而不是把所有长条都归因于 onDraw()。

核对源码与文档

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫

CATALOG
  1. 1. 前言
  2. 2. 从Java层说起
  3. 3. Native层
  4. 4. HWUI-RenderThread
  5. 5. RenderThread.DrawFrame
    1. 5.1. 1. syncFrameState
    2. 5.2. 2. draw
      1. 5.2.1. 2.1 eglBeginFrame
      2. 5.2.2. 2.2 prepareDirty
      3. 5.2.3. 2.3 drawRenderNode
      4. 5.2.4. 2.4 swapBuffers
      5. 5.2.5. 2.5 FinishFrame
  6. 6. 总结
  7. 7. 2026 年补充:现在如何对应这条链路
  8. 8. 核对源码与文档
  • 关于我 && 博客