一. 图形渲染方式
Android 图像渲染有两种方式一是 CPU 渲染, 另一种是 GPU 渲染
一) CPU 渲染
CPU 渲染称之为软件绘制, Android CPU 渲染引擎框架为 Skia, 它是一款在底端设备上呈现高质量的 2D 跨平台图形框架, Google 的 Chrome、Flutter 内部都有使用这个图形渲染框架
二) GPU 渲染
GPU 渲染称之为硬件绘制(即开启硬件加速)
1. OpenGL
市面上最常用于图形渲染的引擎莫过于 OpenGL 了, Android 系统架构中的外部链接库中有 OpenGL ES 的依赖, 并且提供了应用层的 API, 用于做高性能的 2D/3D 图形渲染, Android 中对 OpenGL 的支持如下
OpenGL 版本支持
Android 版本 | OpenGL ES 支持 |
---|---|
Android 1.0 | OpenGL ES 1.0、1.1 |
Android 2.2 | OpenGL ES 2.0 |
Android 4.3 | OpenGL ES 3.0 |
Android 5.0 | OpenGL ES 3.1 |
Android 7.0 | OpenGL ES 3.2 |
OpenGL API 支持

2. Vulkan
Android 7.0 之后除了添加 OpenGL ES3.2 的支持, 同时添加了 Vulkan 图像引擎, Vulkan 是用于高性能 3D 图形的低开销、跨平台 API, 它与 OpenGL 不同, 它被添加到 Android 运行时库中, 目前支持面稍窄
二. 图性渲染组件
Android 的图形渲染是一个生产者消费者模型, Android 图形渲染的组件结构图如下

图像生产者
生产者为 Media Player 视频解码器, OpenGL ES 等产生的图像缓存数据, 他们通过 BufferData 的形式传递到缓冲队列中
图像消耗者
图像数据的消耗者主要是 SurfaceFlinger, 该系统服务会消耗 Surface 提供的数据缓冲流, 并且使用窗口管理器中提供的数据, 把他们合并输出绘制到屏幕上
窗口管理器
控制窗口的 Android 系统服务,它是视图容器。窗口总是由 Surface 提供支持。该服务会监督生命周期、输入和聚焦事件、屏幕方向、转换、动画、位置、变形、Z-Order 以及窗口的其他许多方面。窗口管理器会将所有窗口元数据发送到 SurfaceFlinger,以便 SurfaceFlinger 可以使用该数据在显示部分合成 Surface。
各个组件之间的映射关系
-
画笔: Skia 和 OpenGL, 我们通过 Canvas API 进行绘制最终都会调用到外部链接库的 Skia 和 OpenGL
- Skia: 2D 图像绘制, 关闭硬件加速时使用该引擎
- OpenGL: 2D/3D 图像绘制, 开启硬件加速时使用该引擎
-
画纸: Surface, Android 中所有的元素都在 Surface 这张画纸上进行绘制
- Window 是 View 的容器, 每个 Window 会关联一个 Surface
- WindowManager 用于管理所有的 Window
- 它将 Window 的 Surface 信息传递给 Graphic Buffer
- 将 Window 其他数据信息发送给 SurfaceFlinger
-
画板: Graphic Buffer, 它用于图像数据的缓冲, 将数据发送给 SurfaceFlinger 进行绘制
- 4.1 之前使用双缓冲机制, 4.1 之后使用三缓冲机制
-
显示: SurfaceFlinger, 它将 WindowManager 提供的所有的 Surface, 通过硬件合成输出到屏幕上
三. 图像渲染流程
熟悉 View 绘制的三大流程可知, View 的绘制发起在 ViewRootImpl 的 performDraw 中, 我们直接从这里分析
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
private void performTraversals(){
if (!cancelDraw && !newSurface) {
......
// 调用了 performDraw
performDraw();
} else {
......
}
}
private void performDraw() {
try {
// 执行绘制
boolean canUseAsync = draw(fullRedrawNeeded);
......
} finally {
......
}
}
private boolean draw(boolean fullRedrawNeeded) {
......
final Rect dirty = mDirty;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
// 若开启了硬件加速, 则使用 OpenGL 的 ThreadedRenderer 进行绘制
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
......
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
} else {
// 若我们没有开启硬件加速, 则调用 drawSoftware
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
.......
}
}
好的可以看到, View 的绘制, 可能有两种实现方式
- 调用 ThreadedRenderer.draw() 进行 GPU 绘制
- 调用 ViewRootImpl.drawSoftware() 进行 CPU 绘制
GPU 绘制又称之为硬件加速绘制, 在 Android 4.0 之后是系统默认开启的, 我们先分析硬件绘制原理
一) 硬件绘制
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
private boolean draw(boolean fullRedrawNeeded) {
......
final Rect dirty = mDirty;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
......
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
}
}
.......
}
}
从 ViewRootImpl.draw 的代码中, 我们知道硬件绘制只有在 mAttachInfo 的 mThreadedRenderer 有效的情况下才会执行
因此在想了解硬件绘制流程之前, 需要搞清楚 mThreadedRenderer 它是如何初始化并且赋值的, 也就是说硬件绘制是如何开启的?
1. 硬件绘制的开启
这需要从 ViewRootImpl 的创建说起, ViewRootImpl 是用于管理 View 树与其依附 Window 的一个媒介, 当我们调用 WindowManager.addView 时便会创建一个 ViewRootImpl 来管理即将添加到 Window 中的 View
public final class WindowManagerGlobal {
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
synchronized (mLock) {
// 创建了 ViewRootImpl 的实例
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// 添加到缓存中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
// 将要添加的视图添加到 ViewRootImpl 中
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
......
}
}
}
}
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
final View.AttachInfo mAttachInfo;
public ViewRootImpl(Context context, Display display) {
......
// 构建了一个 View.AttachInfo 用于描述这个 View 树与其 Window 的依附关系
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
......
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
if (mSurfaceHolder == null) {
// 根据 attrs 判读是否开启硬件加速
enableHardwareAcceleration(attrs);
}
}
private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
mAttachInfo.mHardwareAccelerated = false;
mAttachInfo.mHardwareAccelerationRequested = false;
......
final boolean hardwareAccelerated =
(attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
if (hardwareAccelerated) {
......
if (fakeHwAccelerated) {
......
} else if (!ThreadedRenderer.sRendererDisabled
|| (ThreadedRenderer.sSystemRendererDisabled && forceHwAccelerated)) {
......
// 创建硬件加速的渲染器
mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
attrs.getTitle().toString());
mAttachInfo.mThreadedRenderer.setWideGamut(wideGamut);
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mHardwareAccelerated =
mAttachInfo.mHardwareAccelerationRequested = true;
}
}
}
}
}
好的, 这个硬件加速渲染器是通过 WindowManager.LayoutParams 来决定的, 他会给 mAttachInfo 的 mThreadedRenderer 属性创建一个渲染器线程的描述
好的, 有了 mThreadedRenderer 这个对象, 接下来就可以探索 mThreadedRenderer.draw 是如何进行硬件绘制的了
2. 硬件绘制流程
硬件渲染开启之后, 在 ViewRootImpl.draw 中就会执行 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback); 进行 View 的绘制
public final class ThreadedRenderer {
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks,
FrameDrawingCallback frameDrawingCallback) {
......
// 1. 构建根视图的渲染数据
updateRootDisplayList(view, callbacks);
......
// 2. 通知 RenderThread 线程绘制
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
......
}
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
// 1.1 构建 View 树的渲染数据
updateViewTreeDisplayList(view);
// 1.2 视图需要更新 || 当前的根渲染器中的数据已经无效了
if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
// 构建当前 Window 对应的 Surface 的画笔
DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
try {
......
// 让 Surface 画笔的数据指向根视图 DecorView 中的数据
canvas.drawRenderNode(view.updateDisplayListIfDirty());
......
// 表示当前 Window 的视图数据更新完毕, 不需要更新了
mRootNodeNeedsUpdate = false;
} finally {
// 将 Canvas 画笔中的数据, 保存到渲染器中
mRootNode.end(canvas);
}
}
}
private void updateViewTreeDisplayList(View view) {
// 在根 View 的 Flag 中添加一个 Drawn 指令
view.mPrivateFlags |= View.PFLAG_DRAWN;
// 若根视图被设置了 INVALIDATE, 则说明需要重新构建显示列表
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
== View.PFLAG_INVALIDATED;
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
// 如果 View 的显示区域已经无效了, 则更新 View 的显示列表
view.updateDisplayListIfDirty();
......
}
}
好的, 可以看到 ThreadedRenderer 主要进行了两个操作
- 调用 updateRootDisplayList 构建根视图的渲染数据
- 调用 View.updateViewTreeDisplayList(), 更新 View 树的渲染器数据
- 将 View 树的渲染器数据保存到当前 Window 的画布 Surface 渲染器中
- 将 Surface 渲染器中的数据发送到 RenderThread 进行真正的渲染
好的, 可以看到这里调用了 View.updateViewTreeDisplayList() 对 View 树的渲染数据的进行构建, 接下来我们就看看他是如何操作的
View 的渲染数据的构建
public class View {
final RenderNode mRenderNode;
public View(Context context) {
......
// 可见在硬件绘制中, 每一个 View 对应着一个渲染器中的结点
mRenderNode = RenderNode.create(getClass().getName(), this);
......
}
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
.....
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.isValid() || (mRecreateDisplayList)) {
// 1. 当前 View 渲染器中数据依旧是有效的 && 没有要求重绘
if (renderNode.isValid() && !mRecreateDisplayList) {
// 1.2 将更新 Render 的操作分发给子 View, 该方法会在 ViewGroup 中重写
dispatchGetDisplayList();
// 1.3 直接跳过使用 Canvas 绘制的操作
return renderNode;
}
// 2. 走到这里说明当前 View 的渲染数据已经失效了, 需要重新构建渲染数据
mRecreateDisplayList = true;
int width = mRight - mLeft;
int height = mBottom - mTop;
int layerType = getLayerType();
// 2.1 通过渲染器获取一个 Canvas 画笔, 画笔的可作用的区域为 width, height
final DisplayListCanvas canvas = renderNode.start(width, height);
try {
if (layerType == LAYER_TYPE_SOFTWARE) {
......
} else {
......
// 2.2 当前 View 的 Draw 可跳过, 直接分发去构建子 View 渲染数据
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
} else {
// 2.3 绘制自身
draw(canvas);
}
}
} finally {
// 2.4 表示当前 View 的渲染数据已经保存在 Canvas 中了
// 将它的数据传递到渲染器
renderNode.end(canvas);
}
} else {
......
}
return renderNode;
}
}
好的, View.updateDisplayListIfDirty 方法从名字上来理解是 若 View 展示列表无效了则更新它, 事实上它做的也是如此, 只不过这个 DisplayList 称之为渲染数据更为合适, 它主要做了如下操作
- 当前 View 渲染数据依旧是有效的 并且没有要求重绘
- 将更新渲染数据的操作分发给子 View
- 遍历结束之后直接返回 当前现有的渲染器结点对象
- 当前 View 渲染数据无效了
- 通过渲染器构建可渲染区域的画笔 Canvas
- 调用 view.draw 进行渲染数据的构建
- 这个方法留在软件渲染的时候再分析
- 当前 View 的渲染数据重新构建好了, 则将它保存在渲染器 renderNode 中
好的, DecorView 的 updateDisplayListIfDirty 操作完成之后, 当前 Window 的 Surface 中所有的渲染数据就更新了, 之后再调用 ThreadedRenderer.nSyncAndDrawFrame 就可以将数据发送到 SurfaceFlinger 提供的 Graphic Buffer 中等待其展示到屏幕上了
3.硬件绘制流程图

- 当调用 View.draw 时首先获取当前 Window 的画布 Surface
- 获取当前画布 Surface 的渲染器 RenderNode, 通过他来构建一个画笔 Canvas
- View.updateDisplayListIfDirty 完成后, 画笔 Canvas 便会将数据输出到其对应的渲染器中
- 最终 Window 的 Surface 画布中便会保存 DecorView 的渲染数据
- GPU 从 SurfaceFlinger 托管的 BufferQueue 中获取一个 Graphic Buffer
- Surface 将渲染器中的数据发送到 GPU, GPU 将渲染数据栅格化到 Graphic Buffer 中
- GPU 将 Graphic Buffer 重新发送给 SurfaceFlinger 的 BufferQueue 中
- SurfaceFlinger 将栅格化的数据发送给屏幕呈现出来
二) 软件渲染
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
private boolean draw(boolean fullRedrawNeeded) {
......
final Rect dirty = mDirty;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
......
} else {
// 若我们没有开启硬件加速, 则调用 drawSoftware
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
.......
}
}
可以看到软件渲染调用了 drawSoftware 方法, 接下来我们继续探究软件渲染是如何执行的
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
final Canvas canvas;
try {
......
// 1. 通过 Surface 的 lock 操作, 获取一个画笔, 这个画笔是 Skia 的上层封装
canvas = mSurface.lockCanvas(dirty);
......
}
......
try {
......
try {
......
// 2. 调用了 DecorView 的 draw 方法
mView.draw(canvas);
......
} finally {
.......
}
} finally {
// 3. 解锁画笔, 将数据发送给 SurfaceFlinger
surface.unlockCanvasAndPost(canvas);
......
}
return true;
}
}
好的, 可以看到软件绘制主要有三部
- 首先是通过 Surface 的 lockCanvas 获取一个画笔 Canvas, 它是 Android 2D 图像库 Skia 的一个上层封装
- 然后调用了 View 的 draw 方法
- 最后调用了 unlockCanvasAndPost 解锁画笔, 将数据同步给 SurfaceFinger 缓冲区, 进行渲染
public class View {
public void draw(Canvas canvas) {
......
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) {
onDraw(canvas);
}
// Step 4, draw the children
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
return;
}
......
}
}
好的, 很简单这就是 View 的绘制流程的分发, 这里不再赘述了
软件绘制流程图

- View 想要绘制的时候, 首先要获取 Window 对应的 Surface 画纸
- 通过 Surface.lock 操作获取一个 Canvas 画笔, 这个 Canvas 是 Skia 的上层封装
- 使用 Skia 画笔在 Surface 画纸上进行绘制
- SurfaceFlinger 会托管一个 BufferQueue, 我们从 BufferQueue 中获取到 Graphic Buffer 画板
- 将我们使用 Skia 画笔在 Surface 画纸上绘制的内容栅格化到 Graphic Buffer 画板上
- 将填充了数据画板发送给 SurfaceFlinger 进行绘制
- SurfaceFlinger 将画板内容渲染到手机屏幕上
四. 硬件绘制与软件绘制差异
从渲染机制
- 硬件绘制使用的是 OpenGL/ Vulkan, 支持 3D 高性能图形绘制
- 软件绘制使用的是 Skia, 仅支持 2D 图形绘制
渲染效率上
-
硬件绘制
- 在 Android 5.0 之后引入了 RendererThread, 它将 OpenGL 图形栅格化的操作全部投递到了这个线程
- 硬件绘制会跳过渲染数据无变更的 View, 直接分发给子视图
-
软件绘制
- 在将数据投入 SurfaceFlinger 之前, 所有的操作均在主线程执行
- 不会跳过无变化的 View
因此硬件绘制较之软件绘制会更加流畅
从内测消耗上
硬件绘制消耗的内存要高于软件绘制, 但在当下大内存手机时代, 用空间去换时间还是非常值得的
从兼容性上
- 硬件绘制的 OpenGL 在各个 Android 版本的支持上会有一些不同, 常有因为兼容性出现的系统 bug
- 软件绘制的 Skia 库从 Android 1.0 便屹立不倒, 因此它的兼容性要好于硬件绘制
五. 总结
至此, 对 Andorid 图形渲染的操作, 总体上有了一些轮廓, 上述代码都在应用框架层, 对于 SurfaceFlinger 与当前进程的通信以及渲染原理可以参考老罗的文章, 笔者从中受益良多
参考文献:
https://source.android.com/devices/graphics
https://blog.csdn.net/qian520ao/article/details/81144167
网友评论