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 语义,因此不能保证。

相关推荐
安东尼肉店2 小时前
Android compose屏幕适配终极解决方案
android
2501_916007472 小时前
HTTPS 抓包乱码怎么办?原因剖析、排查步骤与实战工具对策(HTTPS 抓包乱码、gzipbrotli、TLS 解密、iOS 抓包)
android·ios·小程序·https·uni-app·iphone·webview
feiyangqingyun4 小时前
基于Qt和FFmpeg的安卓监控模拟器/手机摄像头模拟成onvif和28181设备
android·qt·ffmpeg
用户2018792831678 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子8 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜82278 小时前
安卓接入Max广告源
android
齊家治國平天下8 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO8 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel8 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢8 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱