页面渲染流程 简单整理

57 阅读5分钟

个人水平有限,可能存在纰漏或有描述问题。

涉及的点:

1.setContentview 中做了什么

2.super.onCreate 中干了什么

3.在什么时候创建了 decorview ,;decorview ,window,windowmanager 的关系。

4.在 xml 解析 view 时,通过 Factory2创建 view 有什么差异。

5.在resume 中 什么时候出发了 view 的绘制。

5.requestlayout 以及 invalidate的区别。

6.编舞者的作用,以及为啥要保证垂直同步信号和渲染节点的一致。

7.渲染的的2级缓存和3级缓存。

8.在绘制时,如何将 java 层的数据 传递到 c++层,并通过 surfaceflinger进行合成 ,并存放在BackBuffer再到展示到页面的过程。

执行流程

onCreate:

页面的渲染首先在 oncreate 方法中,依次通过 super.oncreate()方法完成 factory2的赋值操作,factory2 是后面 view,viewgroup,自定义 view 创建的核心类。

在 setcontentView()方法中设置了对应的 xml,在该方法内部会判断 Decorview 是否为空,如果为空,就会通过 phoneWindow 进行创建。同时实现 decorview 和 phonewindow相互持有。

(拓展:同时在 Androidx中 在该方法中会调用 initializeViewTreeOwners()方法,在该方法中会将LifecycleOwner、ViewModelStoreOwner等,同 decorview 进行绑定。因此在后面自定义 view 时,可以通过同 decorview 获取对应值,实现生命周期绑定,viewmodel 写入到 ViewModelStore等操作。)

在setContentView()方法后 会执行到 AppCompatDelegateImpl - setContentView方法:在该方法中会通过 LayoutInflater.from(mContext)方式,进行view树的创建操作。其中创建的过程都会通过mFactory2.onCreateView()方法进行创建;在创建时分为 view 、viewGroup、自定义 view 3种情况。其中 view 的创建会根据 view 名进行 new 出对应的 view。而 viewGroup 和自定义 view 会通过反射的形式进行创建。(自定义 view 和 ViewGroup 创建调用相同的方法,就是传递参数不同;自定义 view 没有携带前缀参数,在反射时直接通过 name 进行反射。viewGroup 是前缀+name 进行发射生成对象)。接下来就会将创建的 view 和 viewGroup 进行添加生成了对应的 view 树。

onResume:

在handleResumeActivity 执行 view 添加到 window,并执行绘制的流程.

在该方法中首先调用 windowmanager.addView()方法,在方法中最终会通过 WindowManagerGlobal.addview 方法进行添加;在该方法内部,会判断ViewRootImp 是否为空,如果为空就创建,并将当前线程作为UI 线程,并作为后期判断是否在 ui 线程的依据;接着将 view(DecorView),root,params 添加到对应的集合中;后面就会调用viewRootImpl.setView () 方法,在该方法内部会触发requestLayout方法,接着在requestLayout方法中 执行scheduleTraversals()方法,在该方法内部会通过 handler 设置异步栅栏。然后通过编舞者 设置回调;然后再回调结果中调用doTraversal方法。在该方法中,移除异步栅栏,然后执行performTraversals方法,该方法就会执行后续的performMeasure、performLayout、performDraw 方法进行测量排版和绘制功能。

编舞者:

编舞者(Choreographer):编舞者的主要作用就是调节作用,调节垂直同步信号和布局渲染的执行时间,保证两者节奏一致。避免出现丢帧出现。撕裂是因为没有设置2级缓存和3级缓存导致。

Choreographer 的作用

在 Android 中,Choreographer 是一个专门负责协调垂直同步信号(VSync)与 UI 渲染流程的调度器,它的主要功能是:

  1. 监听 VSync 信号
    • VSync 是屏幕刷新信号(通常 16.6ms 一次,60Hz)。
    • Choreographer 会在收到 VSync 信号时启动一次 UI 渲染调度。
  1. 调度三大绘制阶段
    • 输入处理(Input)
    • 动画计算(Animation)
    • 视图绘制(Traversal → measure/layout/draw)
    • 这些阶段会被安排在同一帧的合适时机执行,保证 UI 渲染和屏幕刷新同步。
  1. 避免丢帧
    • 如果 UI 渲染完成时间超过 VSync 间隔,就会丢帧,产生卡顿。
    • Choreographer 会尽量保证这些任务在 VSync 信号到来前完成,减少卡顿感。

接下来就是将布局图形绘制并展示页面:

1、surface 的创建:在performTraversals - relayoutWindow - 在windowmanagerservice 中创建 对应的 surface; java 中 surface:surface.mNativeObject = c++ surface.(其实就是 java 中的 surface.mNativeObject 持有 c++层 surface 的指针)

C++ surface :可以通过BufferQueueProducer 从 BufferQueue 中获取到 GraphicBuffer图形缓存区。

Surfaceflinger ->layer: 可以通过 BufferQueueConsumer从BufferQueue 获取图形缓存区(GraphicBuffer);

2.在绘制时 。surface.lock(dirty)时; 会先进行 surface 锁定。然后java 层的 surface.mNativeObject(c++层 surface)会通过BufferQueueProducer 获取到图形缓存区 GraphicBuffer。然后创建 BitmapInfo(包含区域的宽高,等信息) 。接着创建 SkBitmap 根据BimapInfo的数据信息 。view 图形最终绘制到 SkBimap 上。

3、canvas.draw 就是通过 nativeCanvas(SkCanvas) 将图形绘制到 SkBitmap 上。

4.Surface.unlockCanvasAndPost 方法,触发 surface.unlock 解锁。然后借助 BufferQueueProducer.queueBuffer将图形缓存区(GraphicBuffer)放入到 BufferQueue队列中。并通知到 Surfaceflinger有新的一帧数据,在下次垂直同步信号来时surfaceFlinger - layer 会借助BufferQueueConsumer从BufferQueue 中获取到缓存区(GraphicBuff)进行图形合成。然后存放到 Back Buffer 。在后续垂直同步信号到来时,将 frameBuffer 和 BackBuffer 进行替换,然后就展示到了页面。

两级缓存交互流程

[Frame Buffer] → 屏幕显示

[Back Buffer ] → GPU 绘制新帧

↓ 交换(Swap Buffers)

[Frame Buffer] ← 新内容显示

[Back Buffer ] ← 开始绘制下一帧

BufferQueue,BufferQueueProducer,BufferQueueConsumer,GraphicBuff

BufferQueue:

本质是一个生产者-消费者队列,用来在 生产者(Producer)消费者(Consumer) 之间传递图形缓冲(GraphicBuffer)。

BufferQueueProducer:

生产者接口,供 应用层 Surface 使用。

负责:

  1. 向 BufferQueue 申请可写的缓冲区(dequeueBuffer)。
  2. 将绘制好的缓冲提交回队列(queueBuffer)。
BufferQueueConsumer

消费者接口,供 SurfaceFlinger(或其他消费方)使用。

负责:

    1. 从队列中取出已经绘制好的缓冲(acquireBuffer)。
    2. 用完后将缓冲释放回队列(releaseBuffer)。
GraphicBuffer:图形缓存区

一种跨进程共享的显存缓冲(底层基于 Gralloc)。

本质上是 GPU/硬件可直接访问的图像存储区域。

图片画的比较垃圾见谅:

oncreate:

image.png

onresume:

image.png