详解View 的内容如何显示在屏幕上

177 阅读3分钟

一、使用方式:开发者如何触发显示更新

开发者只需要做简单的操作,系统会自动处理复杂的显示流程:

1. 修改 View 属性(最常见方式)

// 在 UI 线程中修改 View 属性
textView.setText("新内容");       // 修改文本
imageView.setImageBitmap(bitmap); // 更换图片
button.setBackgroundColor(Color.RED); // 改变背景色

// 系统自动触发重绘流程,无需手动调用绘制方法

2. 自定义 View 时重写绘制方法

public class MyCustomView extends View {
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 在这里绘制自定义内容
        canvas.drawCircle(100, 100, 50, paint);
    }
    
    // 当需要更新时
    public void update() {
        // 标记需要重绘
        invalidate(); 
    }
}

3. 创建动画效果

// 使用属性动画
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "translationX", 0f, 200f);
anim.setDuration(1000);
anim.start();

// 系统自动处理每一帧的更新和重绘

二、核心原理:显示流程的四个阶段

想象 View 显示就像制作一部动画电影:

deepseek_mermaid_20250614_0c5b82.png

1. 测量与布局阶段(Measure & Layout)

  • 测量:计算每个 View 需要多大空间

    // 伪代码
    void measure(int widthSpec, int heightSpec) {
        // 根据父容器的约束计算自身尺寸
        setMeasuredDimension(calculatedWidth, calculatedHeight);
    }
    
  • 布局:确定每个 View 的位置

    java

    复制

    下载

    // 伪代码
    void layout(int left, int top, int right, int bottom) {
        // 确定自己在父容器中的位置
        setFrame(left, top, right, bottom);
    }
    

2. 绘制阶段(Draw)

  • 生成绘制指令列表(DisplayList)

  • 不会立即执行绘制,只是记录操作:

    // 伪代码
    void draw(Canvas canvas) {
        drawBackground(canvas);  // 记录背景绘制指令
        onDraw(canvas);          // 记录自定义内容指令
        drawChildren(canvas);    // 记录子View绘制指令
    }
    

3. 渲染阶段(Render)

  • 主线程:将绘制指令同步到 RenderThread
  • RenderThread:将指令转换为 GPU 命令(OpenGL/Vulkan)
  • GPU 执行实际渲染,输出到图形缓冲区

4. 显示阶段(Display)

  • SurfaceFlinger 收集所有图层的缓冲区
  • 在 VSYNC 信号到来时合成最终图像
  • 发送到屏幕物理显示

三、核心机制:VSYNC 和 Choreographer

VSYNC(垂直同步)信号

  • 显示器每 16.6ms(60Hz 屏幕)发出一次心跳信号
  • 协调所有绘制操作,避免画面撕裂
  • 三重缓冲机制防止卡顿:

deepseek_mermaid_20250614_a3d058.png

Choreographer(编舞者)

  • 协调 UI 更新与 VSYNC 信号

  • 四种回调类型:

    回调类型执行内容执行顺序
    INPUT触摸事件处理1
    ANIMATION属性动画更新2
    TRAVERSAL视图测量/布局/绘制3
    COMMIT渲染结果提交4

四、源码级执行流程(简化版)

1. 触发更新(例如调用 invalidate())

// View.java
public void invalidate() {
    // 最终调用到 ViewRootImpl
    parent.invalidateChild(this, dirtyRect);
}

// ViewRootImpl.java
void invalidateChild(View child, Rect dirty) {
    scheduleTraversals(); // 关键:安排遍历
}

2. 安排绘制任务

// ViewRootImpl.java
void scheduleTraversals() {
    mChoreographer.postCallback(
        Choreographer.CALLBACK_TRAVERSAL, 
        mTraversalRunnable, // 包含performTraversals()
        null
    );
}

3. 等待并响应 VSYNC

// Choreographer 底层实现 (C++)
void Choreographer::dispatchVsync(...) {
    // 回调到Java层
    env->CallVoidMethod(mReceiver, gVsyncMethod, ...);
}

// Java层处理
void frameCallback(long frameTimeNanos) {
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
}

4. 执行视图遍历三部曲

// ViewRootImpl.java
void performTraversals() {
    // 1. 测量
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
    // 2. 布局
    performLayout();
    
    // 3. 绘制
    performDraw();
}

5. 绘制实现细节

private void performDraw() {
    // 使用硬件加速渲染器
    mAttachInfo.mThreadedRenderer.draw(mView, ...);
}

// ThreadedRenderer.java
void draw(View view, ...) {
    // 1. 更新显示列表
    updateViewTreeDisplayList(view);
    
    // 2. 同步到渲染线程
    nSyncAndDrawFrame(mNativeProxy, ...);
}

6. 渲染线程工作流程

// RenderProxy.cpp (native层)
void RenderProxy::syncAndDrawFrame() {
    // 创建OpenGL/Vulkan命令
    CanvasContext* context = getContext();
    context->draw();
}

// CanvasContext.cpp
void draw() {
    // 1. 准备帧
    makeCurrent();
    
    // 2. 执行所有GL绘制命令
    renderFrame();
    
    // 3. 交换缓冲区
    swapBuffers();
}

7. 显示到屏幕

// SurfaceFlinger.cpp
void handleMessageRefresh() {
    // 1. 收集所有图层
    Vector<Layer*> layers(mVisibleLayers);
    
    // 2. 合成图层
    for (auto layer : layers) {
        layer->latchBuffer(); // 获取缓冲区
        layer->setPerFrameData(); // 设置每帧数据
    }
    
    // 3. 最终合成
    doComposition();
    
    // 4. 发送到显示器
    postFramebuffer();
}

七、总结:完整的显示流程全景图

deepseek_mermaid_20250614_9b6875.png 这个流程就像精心设计的工厂流水线:

  1. 设计部门(UI线程):设计产品图纸(测量布局)
  2. 工程部门(UI线程):编写生产指令(生成显示列表)
  3. 制造部门(RenderThread):操作机器生产(GPU渲染)
  4. 质检部门(SurfaceFlinger):组装最终产品(图层合成)
  5. 物流部门(显示系统):按时发货(VSYNC同步显示)

每一帧都经过这样的流程,60Hz 屏幕意味着每秒要完成 60 次这样的完整流程(约 16.6ms/帧)。任何环节超时都会导致掉帧(jank),这就是为什么性能优化如此重要