Android 图像渲染有两种方式一是 CPU 渲染, 另外一种是 GPU 渲染android
CPU 渲染称之为软件绘制, Android CPU 渲染引擎框架为 Skia, 它是一款在底端设备上呈现高质量的 2D 跨平台图形框架, Google 的 Chrome、Flutter 内部都有使用这个图形渲染框架canvas
GPU 渲染称之为硬件绘制(即开启硬件加速)缓存
市面上最经常使用于图形渲染的引擎莫过于 OpenGL 了, Android 系统架构中的外部连接库中有 OpenGL ES 的依赖, 而且提供了应用层的 API, 用于作高性能的 2D/3D 图形渲染, Android 中对 OpenGL 的支持以下bash
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 |
Android 7.0 以后除了添加 OpenGL ES3.2 的支持, 同时添加了 Vulkan 图像引擎, Vulkan 是用于高性能 3D 图形的低开销、跨平台 API, 它与 OpenGL 不一样, 它被添加到 Android 运行时库中, 目前支持面稍窄架构
Android 的图形渲染是一个生产者消费者模型, Android 图形渲染的组件结构图以下app
生产者为 Media Player 视频解码器, OpenGL ES 等产生的图像缓存数据, 他们经过 BufferData 的形式传递到缓冲队列中框架
图像数据的消耗者主要是 SurfaceFlinger, 该系统服务会消耗 Surface 提供的数据缓冲流, 而且使用窗口管理器中提供的数据, 把他们合并输出绘制到屏幕上ide
控制窗口的 Android 系统服务,它是视图容器。窗口老是由 Surface 提供支持。该服务会监督生命周期、输入和聚焦事件、屏幕方向、转换、动画、位置、变形、Z-Order 以及窗口的其余许多方面。窗口管理器会将全部窗口元数据发送到 SurfaceFlinger,以便 SurfaceFlinger 可使用该数据在显示部分合成 Surface。性能
画笔: Skia 和 OpenGL, 咱们经过 Canvas API 进行绘制最终都会调用到外部连接库的 Skia 和 OpenGL动画
画纸: Surface, Android 中全部的元素都在 Surface 这张画纸上进行绘制
画板: Graphic Buffer, 它用于图像数据的缓冲, 将数据发送给 SurfaceFlinger 进行绘制
显示: 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 的绘制, 可能有两种实现方式
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 它是如何初始化而且赋值的, 也就是说硬件绘制是如何开启的?
这须要从 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 是如何进行硬件绘制的了
硬件渲染开启以后, 在 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 主要进行了两个操做
好的, 能够看到这里调用了 View.updateViewTreeDisplayList() 对 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 称之为渲染数据更为合适, 它主要作了以下操做
好的, DecorView 的 updateDisplayListIfDirty 操做完成以后, 当前 Window 的 Surface 中全部的渲染数据就更新了, 以后再调用 ThreadedRenderer.nSyncAndDrawFrame 就能够将数据发送到 SurfaceFlinger 提供的 Graphic Buffer 中等待其展现到屏幕上了
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;
}
}
复制代码
好的, 能够看到软件绘制主要有三部
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 的绘制流程的分发, 这里再也不赘述了
硬件绘制
软件绘制
所以硬件绘制较之软件绘制会更加流畅
硬件绘制消耗的内存要高于软件绘制, 但在当下大内存手机时代, 用空间去换时间仍是很是值得的
至此, 对 Andorid 图形渲染的操做, 整体上有了一些轮廓, 上述代码都在应用框架层, 对于 SurfaceFlinger 与当前进程的通讯以及渲染原理能够参考老罗的文章, 笔者从中受益良多
参考文献: https://source.android.com/devices/graphics https://blog.csdn.net/qian520ao/article/details/81144167