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