android View详解—View的刷新流程源码解析

每一帧的绘制是生产者消费者模式

Android 整个「每一帧渲染到屏幕」的链路,从 App 端的 ViewRootImpl 到最终显示在面板(Display),本质上就是 典型的生产者-消费者模型。核心组件与数据流如下:

  1. 生产者(Producer)

    • App 主线程的 ChoreographerViewRootImplSurfaceFlingerBufferQueue

    • 每 16.7 ms(60 Hz)收到 VSync 信号后,主线程/RenderThread 把 UI 树绘制到一块 GraphicBuffer (OpenGL / Vulkan 渲染结果)。

    • 绘制完成把这块 buffer queueSurfaceFlinger 的 BufferQueue → 生产者完成一次"生产"。

  2. 缓冲区(Queue)

    BufferQueue 是 Binder 跨进程对象,内部维护 最多 2~3 个 slot (双缓冲/三缓冲)。

    • 当 App 把 buffer 放入队列后,会立即拿到下一块空闲 buffer 继续下一帧的生产。

  3. 消费者(Consumer)

    SurfaceFlinger 作为消费者进程,在收到 HW-VSync软件 VSync 后,从 BufferQueue dequeue 最新一帧 buffer。

    • 把该帧做合成(Composition,可能叠加多图层),然后提交给 Display HAL(HWComposer),最终驱动面板刷新。

  4. 同步与背压

    • 如果 App 生产过快(丢帧),BufferQueue slot 满,dequeueBuffer阻塞 生产者,形成背压。

    • 如果生产过慢,消费者拿不到新 buffer,就继续显示上一帧 → 用户感觉卡顿。

App 每帧把渲染结果"生产"进 BufferQueue,SurfaceFlinger 作为"消费者"取帧送显;这套机制就是 生产者-消费者 + 双/三缓冲 + VSync 同步 的经典实现。

vsync信号来临到生成一帧流程

Choreographer.FrameDisplayEventReceiver.onVsync

Vsync信号来临会调用到android的Choreographer.FrameDisplayEventReceiver.onVsync方法

在该方法中发送异步消息到主线程

复制代码
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
        VsyncEventData vsyncEventData) {

//发送异步消息
Message msg = Message.obtain(mHandler, this);
//true这里是异步消息
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);

处理异步消息

复制代码
//主线程调用异步消息,开始绘制流程
@Override
public void run() {
    mHavePendingVsync = false;
    doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}

各种事件开始处理

复制代码
void doFrame(long frameTimeNanos, int frame,
        DisplayEventReceiver.VsyncEventData vsyncEventData) {
//输入事件 触摸、按键、轨迹球、鼠标事件
doCallbacks(Choreographer.CALLBACK_INPUT, frameData, frameIntervalNanos);

mFrameInfo.markAnimationsStart();
//普通动画 ValueAnimator、ObjectAnimator、View.animate() 等属性动画的下一帧计算。
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameData, frameIntervalNanos);
//insets 动画 与系统窗口(IME、系统栏)同步的边衬动画,比如键盘弹出/收回动画。
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameData,
        frameIntervalNanos);

mFrameInfo.markPerformTraversalsStart();
//遍历 / 刷新布局 ViewRootImpl.scheduleTraversals() 安排的 performTraversals()------测量、布局、绘制;invalidate() 最终走到这里。
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameData, frameIntervalNanos);
//帧提交 / 收尾 在 draw() 完成、SurfaceFlinger 消费前做最后的 Buffer 提交 与 帧统计;也用于 FrameMetrics 打点。
doCallbacks(Choreographer.CALLBACK_COMMIT, frameData, frameIntervalNanos);

Choreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL

处理绘制相关流程

复制代码
void doCallbacks(int callbackType, FrameData frameData, long frameIntervalNanos) {

try {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
    for (CallbackRecord c = callbacks; c != null; c = c.next) {
       //分发事件,这里调用的是ViewRootImpl.mTraversalRunnable.run
        c.run(frameData);
    }

开始刷新

ViewRootImpl.mTraversalRunnable.run

复制代码
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        //开始度量,布局,绘制
        doTraversal();
    }
}

void doTraversal() {
//如果没有请求刷新,Vsync信号来将不会刷新
    if (mTraversalScheduled) {
        //修改标志位
        mTraversalScheduled = false;
        //移除消息屏障,这个执行后才会执行普通消息
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        //开始测量,布局,绘制
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

ViewRootImpl.performTraversals

// 开始测量,布局,绘制
performTraversals();

复制代码
private void performTraversals() {
//度量
performMeasure()
//布局
performLayout(lp, mWidth, mHeight);
//绘制
performDraw()
}

ViewRootImpl.performMeasure

复制代码
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        //顶层View开始下发度量
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

ViewRootImpl.performLayout

复制代码
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {

final View host = mView;
if (host == null) {
    return;
}

try {
    //顶层View开始度量
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

ViewRootImpl.performDraw

复制代码
private boolean performDraw() {
try {
    boolean canUseAsync = draw(fullRedrawNeeded, usingAsyncReport && mSyncBuffer);
    if (usingAsyncReport && !canUseAsync) {
        mAttachInfo.mThreadedRenderer.setFrameCallback(null);
        usingAsyncReport = false;
    }
} 
}

private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
drawSoftware

}

private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
    // 如果脏区域非空、正在做动画,或无障碍焦点变化,则真正进入绘制
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {

        // ---- 硬件加速路径(默认开启) ----
        if (isHardwareEnabled()) {
            // 把 View 树封装成 DisplayList,通过 Binder 交给 RenderThread
            // 由 GPU 异步完成真正的绘制与上屏
            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);

        // ---- 软件绘制路径(硬件加速关闭或 Surface 不支持) ----
        } else {
            // 直接在 UI 线程用 Skia/Canvas 把 View 画到 Surface
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                              scalingRequired, dirty, surfaceInsets)) {
                // 软件绘制失败,直接返回 false
                return false;
            }
        }
    }

    // 本次绘制流程结束,返回是否使用异步帧报告
    return useAsyncReport;}


private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
mView.draw(canvas);

}

生产数据交给消费者消费

android处理完后会交给消费者消费

请求下一次Vsync信号来需要刷新流程

View. assignParent

复制代码
void assignParent(ViewParent parent) {
    if (mParent == null) {
        mParent = parent;
    } else if (parent == null) {
        mParent = null;
    } else {
        throw new RuntimeException("view " + this + " being added, but"
                + " it already has a parent");
    }
}

最顶层View

最顶层View的mParent是通过ViewRootImpl的setView方法添加的时候注入的

复制代码
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) 
view.assignParent(this);
}

View树下发Parent

是通过ViewGroup设置下发的

复制代码
是通过ViewGroup设置下发的
private void addViewInner(View child, int index, LayoutParams params,
        boolean preventRequestLayout)

if (preventRequestLayout) {
    child.assignParent(this);
} else {
    child.mParent = this;
}

View. Invalidate

内容变化但是大小不变化会触发该方法,该方法在硬件加速打开的时候不会出现崩溃,不会进行线程检查checkThread()

在调节满足的情况下ViewGroup的invalidateChil逐级上报,最后上报到顶层ViewGroup中

最顶层是

复制代码
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
if (p != null && ai != null && l < r && t < b) {
    final Rect damage = ai.mTmpInvalRect;
    damage.set(l, t, r, b);
    p.invalidateChild(this, damage);
}
public final void invalidateChild(View child, final Rect dirty) {
    final AttachInfo attachInfo = mAttachInfo;
    //开启硬件加速后执行该位置
    if (attachInfo != null && attachInfo.mHardwareAccelerated) {
        // HW accelerated fast path
        onDescendantInvalidated(child, child);
        return;
    }

调用流程是

View.invalidate->ViewGroup.invalidateChild->ViewGroup.invalidateChild->......->最顶层ViewGroup.invalidateChild->ViewRootImpl.invalidateChild

View. requestLayout

控件大小变化后会执行该方法,方法在硬件加速开启的情况下也会进行线程检查

会调用ViewGroup的requestLayout

复制代码
public void requestLayout() {

if (mParent != null && !mParent.isLayoutRequested()) {
    mParent.requestLayout();
}

调用流程是View.requestLayout->ViewGroup.requestLayout->ViewGroup.requestLayout->......->最顶层ViewGroup.requestLayout->ViewRootImpl.requestLayout

ViewRootImpl . requestLayout

复制代码
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
	//线程检查
        checkThread();
        mLayoutRequested = true;
	//开始消息屏障,入队
        scheduleTraversals();
    }
}

ViewRootImpl. scheduleTraversals 消息屏障入队

复制代码
void scheduleTraversals() {
    //设置标志位,每一次Vsync期间只会插入一次,没有移除消息屏障不会再次插入
    if (!mTraversalScheduled) {
        //修改标志位
        mTraversalScheduled = true;
        //开启消息屏障,记录屏障token
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //将消息放入Choreographer中
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

Choreographer.postCallback入队刷新消息

入队消息,等待vsync信号来临取出

复制代码
private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
synchronized (mLock) {
    //消息进入队列中
    mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
    
}

硬件加速开启后View刷新

在开启硬件加速的情况下,View的大小未改变,之改变内存,是不会进行线程检查的

不会检查线程,只有大小改变了触发了requestLayout会崩溃

复制代码
View.invalidateChild
public final void invalidateChild(View child, final Rect dirty) {
    final AttachInfo attachInfo = mAttachInfo;
    //开启硬件加速后执行该位置
    if (attachInfo != null && attachInfo.mHardwareAccelerated) {
        // HW accelerated fast path
        onDescendantInvalidated(child, child);
        return;
    }
View.onDescendantInvalidated

@Override
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
    // TODO: Re-enable after camera is fixed or consider targetSdk checking this
    // checkThread();
    if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
        mIsAnimating = true;
    }
//开启硬件加速后不会检查线程
    invalidate();
}

View.post获取宽高原理

入口

View.post()

复制代码
public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        // 已经 attach,直接丢给主线程 Handler
        return attachInfo.mHandler.post(action);
    }
    // 还没 attach,先缓存
    getRunQueue().post(action);
    return true;
}

View.postDelayed

复制代码
/**
 * 将 Runnable 延迟主线程执行的安全入口。
 *
 * 1. 如果 View 已经挂载到窗口(attachInfo 非空),
 *    直接通过 ViewRootImpl 的主线程 Handler 分发,
 *    保证 run 代码运行在 **主线程** 且按时触发。
 *
 * 2. 如果 View 尚未挂载(例如刚 new 出来、还没 addView),
 *    先把它放进 **View 的 RunQueue** 缓存起来;
 *    等到 dispatchAttachedToWindow() 时再整体迁移到主线程 Handler。
 *    这样即使提前 post 也不会丢失任务,更不会在非法线程执行。
 *
 * @param action      待执行代码块
 * @param delayMillis 延迟时间(毫秒)
 * @return true 表示成功入队(与 Handler 返回值一致)
 */
public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;   // 取挂载信息(ViewRootImpl 提供)
    if (attachInfo != null) {
        // 已挂载:直接交给主线程 Handler
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }

    // 未挂载:先放进 RunQueue,等 attach 后一次性迁移到主线程
    getRunQueue().postDelayed(action, delayMillis);
    return true;
}

attach 前:RunQueue 缓存

View.getRunQueue

复制代码
/**
 * 返回当前 View 的"延迟任务队列"单例。
 * 第一次调用时懒创建 HandlerActionQueue,用来缓存尚未 attach 时通过
 * post/postDelayed 提交的 Runnable;等 View 被挂载到窗口后再整体迁移到主线程 Handler。
 */
private HandlerActionQueue getRunQueue() {
    if (mRunQueue == null) {          // 懒加载,避免无谓对象
        mRunQueue = new HandlerActionQueue();
    }
    return mRunQueue;
}

HandlerActionQueue.post

复制代码
/**
 * 将 Runnable 立即放入队列(延迟 0 ms)。
 * 内部直接调用 postDelayed,简化入口。
 */
public void post(Runnable action) {
    postDelayed(action, 0);
}

/**
 * 把 Runnable 及其延迟时间包装成 HandlerAction 并加入数组。
 * 数组满时自动扩容,保证线程安全。
 */
public void postDelayed(Runnable action, long delayMillis) {
    // 1. 封装任务
    final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

    // 2. 线程安全地加入数组
    synchronized (this) {
        if (mActions == null) {
            mActions = new HandlerAction[4];   // 初始容量 4
        }
        // GrowingArrayUtils.append:若数组已满则自动 2 倍扩容
        mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
        mCount++;                              // 元素个数 +1
    }
}

attach 后:layout 完成 → 搬到主线程

当 ViewRootImpl 第一次 performTraversals() 完成后,会调用:

ViewRootImpl.performTraversals

复制代码
private void performTraversals() {
    ...
    if (mFirst) {
        host.dispatchAttachedToWindow(mAttachInfo, 0);
    }
    ...
}

在 dispatchAttachedToWindow() 链路上,View 把缓存的 Runnable 全部搬到主线程:

View.dispatchAttachedToWindow

复制代码
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mAttachInfo = info;
    ...
    // 把之前缓存的 RunQueue 搬到主线程 Handler
    if (mRunQueue != null) {
        mRunQueue.executeActions(info.mHandler);
        mRunQueue = null;
    }
    ...
}

HandlerActionQueue.executeActions

复制代码
public void executeActions(Handler handler) {
    synchronized (this) {
        final HandlerAction[] actions = mActions;
        for (int i = 0, count = mCount; i < count; i++) {
            final HandlerAction handlerAction = actions[i];
            handler.postDelayed(handlerAction.action, handlerAction.delay);
        }
        mActions = null;
        mCount = 0;
    }
}

最终执行:拿到宽高

由于搬运发生在 performTraversals() 之后,此时 View 已经 measure/layout 完毕,Runnable 在主线程执行时自然可以安全地读取宽高。

一句话总结

View.post() 先缓存,等 attach + layout 完成后一次性搬到主线程,所以一定能拿到宽高;而 Handler.post() 没有 attach & layout 语义,因此不能保证。

相关推荐
zhangphil3 小时前
Android adb shell命令分析应用内存占用
android·adb
漠缠4 小时前
Android AI客户端开发(语音与大模型部署)面试题大全
android·人工智能
Lei活在当下4 小时前
一个基础问题:关于SDK初始化时机的选择
android
雨白7 小时前
Android 触摸反馈与事件分发原理解析
android
relis9 小时前
解密大语言模型推理:Prompt Processing 的内存管理与计算优化
android·语言模型·prompt
CYRUS STUDIO12 小时前
FART 自动化脱壳框架优化实战:Bug 修复与代码改进记录
android·自动化·逆向·fart
2501_9159090612 小时前
uni-app iOS 上架常见问题与解决方案,实战经验全解析
android·ios·小程序·https·uni-app·iphone·webview
如此风景12 小时前
Compose 多平台UI开发的基本原理
android
CYRUS_STUDIO13 小时前
静态分析根本不够!IDA Pro 动态调试 Android 应用的完整实战
android·逆向