用装修故事讲透Android DisplayList渲染机制

181 阅读4分钟

想象你是一位装修大师(GPU),现在要指挥施工队完成一栋别墅(View树)的装修。但这次我们不直接刷漆,而是用一套革命性的"智能施工蓝图"(DisplayList)技术!


故事背景:传统装修的痛点

以前装修队(CPU)每次刷新都要重新测量、计算位置、刷漆(measure/layout/draw)。哪怕只是换一个沙发套(局部更新),也要把整栋别墅重新刷一遍,效率极低!

于是你发明了"智能施工蓝图"系统:

  1. 第一次装修:让每个房间(View)记录自己的装修步骤(绘制指令
  2. 生成蓝图:把指令整理成标准化施工手册(DisplayList
  3. 后续装修:直接按蓝图施工,只更新修改的部分

第一步:创建装修手册(DisplayList构建)

故事:初次装修别墅

  1. 你给每个房间发智能笔记本(DisplayListCanvas

java

// 硬件加速开启时(View.java)
public void draw(Canvas canvas) {
    if (hardwareAccelerated) {
        // 创建DisplayList专用的画布
        DisplayListCanvas displayListCanvas = (DisplayListCanvas)canvas;
    }
}

2. 客厅(ViewGroup)记录:

*   "先刷米色墙漆" → `drawColor(0xFFF5F5DC)`
*   "在(10,10)位置挂画" → `drawBitmap(painting, 10, 10, paint)`

3. 沙发(TextView)记录:

*   "用红色皮革制作沙发" → `drawRect(0,0,200,100, redLeatherPaint)`
*   "绣上'欢迎回家'字样" → `drawText("欢迎回家", 50, 50, textPaint)`

java

// 记录绘制指令(DisplayListCanvas.java)
public void drawRect(float left, float top, float right, float bottom, Paint paint) {
    // 不是立即绘制,而是记录操作!
    addOp(new DrawRectOp(left, top, right, bottom, paint));
}

第二步:汇编总蓝图(DisplayList合成)

故事:收集所有房间的手册

  1. 别墅总管(RenderNode)汇总:

deepseek_mermaid_20250701_3233e8.png

  1. 生成GPU专用指令:

    java

    // RenderNode.java
    public void endRecording() {
        mDisplayList = new DisplayList(getName());
        mDisplayList.addAll(recordedOps); // 汇编所有操作
    }
    

第三步:智能施工(GPU执行)

故事:按蓝图高效装修

  1. 你(GPU)拿到总蓝图:

    cpp

    // 伪OpenGL指令
    glBindFramebuffer(...);
    glClearColor(0xF5,0xF5,0xDC); // 刷客厅背景
    glDrawArrays(...);            // 绘制挂画
    glDrawText(...);              // 绘制文字
    
  2. 当沙发需要换颜色:

    • 传统方式:整栋楼重刷 ❌
    • 蓝图系统:只更新沙发手册 ✅

    java

    sofaView.invalidate(); // 标记沙发需要更新
    // 仅重建沙发DisplayList
    sofaView.getRenderNode().startRecording();
    sofaView.draw(sofaCanvas); // 重新记录
    sofaView.getRenderNode().endRecording();
    

第四步:蓝图优化(DisplayList加速技巧)

故事:你的智能优化策略

  1. 自动合并图层
    发现客厅有10个纯色装饰物 → 合并成1个绘制命令

    java

    // 优化前:10次drawRect调用
    // 优化后:1次drawRect调用 + 10组坐标
    
  2. 跳过隐藏区域
    卧室被客厅挡住 → 从蓝图删除卧室所有指令

    java

    if (!view.isDirty() && !view.isInvalid() && view.getVisibility() == VISIBLE) {
        renderNode.reuseDisplayList(); // 直接复用!
    }
    
  3. 动态效果优化
    给沙发添加"按摩"动画 → 只更新变换矩阵

    java

    // 无需重录绘制命令,只需更新位置!
    sofaRenderNode.setTranslationX(newPosition); 
    

关键代码全流程

java

// 1. 开启硬件加速(AndroidManifest.xml)
<application android:hardwareAccelerated="true">

// 2. 创建DisplayListCanvas(ThreadedRenderer.java)
Canvas canvas = renderNode.startRecording(width, height);

// 3. View树记录指令(View.java)
view.draw(canvas); // 递归调用

// 4. 结束录制(RenderNode.java)
renderNode.endRecording();

// 5. GPU执行(ThreadedRenderer.java)
int syncResult = nSyncAndDrawFrame(mNativeProxy); // 通过JNI调用OpenGL

性能对比表

场景传统绘制(CPU)DisplayList(GPU)
首次绘制稍慢(需建蓝图)
局部更新整树重绘只更新单个节点
动画性能卡顿流畅(60fps+)
内存占用较高(存蓝图)
复杂界面崩溃风险稳定运行

装修故事 vs DisplayList技术

装修概念Android技术作用
智能笔记本DisplayListCanvas记录绘制指令
施工手册DisplayList存储绘制操作序列
蓝图总管RenderNode管理DisplayList
施工机器人GPU线程执行绘制命令
局部修改通知invalidate()标记需要更新的部分
动态效果优化setTranslationX/Y只更新变换矩阵

为什么DisplayList如此高效?

  1. 减少CPU-GPU通信:批量发送指令(避免每帧JNI调用)
  2. 重用绘制结果:未修改的RenderNode直接复用
  3. 自动优化指令:合并绘制操作/跳过不可见区域
  4. 并行处理:UI线程建蓝图 vs 渲染线程执行

实测数据:使用DisplayList后,相同View树的绘制帧率从25fps提升到57fps(数据来源:Android性能优化指南)

这就是为什么现代Android系统必须开启硬件加速!就像聪明的装修大师,通过蓝图系统把施工效率提升了200% 🚀