繪制基礎(chǔ)
安卓繪制框架
應用層
1.通過與WindowManagerService通信倔丈,獲取一塊surface。
2.通過View系統(tǒng)或OpenGL、Skia在這塊surface上繪制脐往。
3.與WindowManagerService通信蕊肥,提交繪制產(chǎn)生的緩存谒获。
服務(wù)層(WindowManagerService)
1.生成蛤肌、釋放surface
2.管理surface的層級
3.獲取surface的數(shù)據(jù),將surface的數(shù)據(jù)進行混合批狱,生成新的一幀裸准。
4.將新的一幀數(shù)據(jù)寫入內(nèi)核,使得顯示器刷新數(shù)據(jù)
關(guān)鍵類:
1.Canvas:畫布赔硫,里面有一個Bitmap炒俱,用于保存繪制過程中的緩存。此類提供了繪制的基本方法爪膊,包括作圓权悟、線等。
2.Paint:畫筆推盛,提供繪制的基本參數(shù)峦阁,比如說顏色,畫筆的粗細等耘成。
3.Surface:可以獲取畫布并提交繪制結(jié)果榔昔。本質(zhì)上是一個代理類。用于Application與WindowManager通信瘪菌。
4.ViewRootImp:實現(xiàn)ViewParent撒会,包含Surface和Choreographer。用于控制Activity的繪制控嗜。
- Choreographer繪制調(diào)度器茧彤。用于控制各個Surface的刷新步調(diào),讓所以Surface基本上同時開始刷新疆栏。一般16ms觸發(fā)一次刷新曾掂。
- SurfaceFlinger:混合各個Surface的數(shù)據(jù),形成新的一幀數(shù)據(jù)壁顶。
View的重繪流程
以最基本的View的invalidate為例
基本繪制流程
流程圖中有小小錯誤珠洗,能找到嗎?
基本流程可以分成兩塊:
一若专、觸發(fā)刷新
子view調(diào)用invalidate许蓖,然后層層遞進,調(diào)到ViewrootImp的ScheduleTraversals调衰,在這個過程中不斷確定重繪的區(qū)域膊爪,更新各個父控件的繪制標志。完成各個層級父控制的繪制標志的刷新之后嚎莉,利用ViewrootImp中的Choreographer控制刷新的時機米酬,觸發(fā)刷新。
二趋箩、刷新過程
-
首先通過Surface的lockCanvas申請一塊新的畫布赃额,獲取一個Canvas加派。
值得注意的是,WindowManagerService會為每一塊surface分配兩塊內(nèi)存跳芳,
一塊作為顯示使用(讀)芍锦,一塊作為繪制使用(寫)。在完成繪制后飞盆,兩塊內(nèi)存的作用互換娄琉。lockCanvas的作用,實際上時是給待繪制的內(nèi)存上鎖吓歇,并且獲取待繪制內(nèi)存的地址车胡,把這塊內(nèi)存作為畫布的緩存。在lockCanvas時照瘾,會將顯示的內(nèi)存的數(shù)據(jù)拷貝到待繪制的內(nèi)存中,同時傳入一個區(qū)域(dirty)丧慈,作為重繪的區(qū)域析命。每次重繪實際只需要繪制需要修改的區(qū)域(dirty區(qū)域),其它區(qū)域保持不變逃默,這樣鹃愤,就大大加快了繪制效率
-
獲取畫布之后,就需要層層調(diào)用View的draw方法完域,在這塊畫布上在做畫软吐。
每一個View都有一塊緩存mDrawCache。View的繪制過程吟税,首先要通過繪制標志凹耙,判斷View的內(nèi)容是否發(fā)生變化,有發(fā)生變化肠仪,則調(diào)用ondraw方法肖抱,重新獲取mDrawCache。最后將mDrawCache的重繪區(qū)域繪制到畫布中异旧。
ViewGroup包含自己本身的繪制和子View的繪制意述。自己本身的繪制和View一樣,子View的繪制需要層層調(diào)用子View的繪制方法吮蛹。
完成繪制后荤崇,調(diào)用unlockCanvasAndPost接口。將繪制內(nèi)容提交給WindowManagerService潮针。
接口unlockCanvasAndPost的實質(zhì)是將繪制的內(nèi)存解鎖术荤,并且通知WindowManagerService將兩塊內(nèi)存的作用互換,這樣然低,SurfaceFlinger就會將最新繪制后得到的內(nèi)存進行混合顯示喜每。
硬件加速
為什么使用硬件加速
CPU和GPU架構(gòu)
CPU : Central Processing Unit 务唐, 中央處理器,是計算機設(shè)備核心器件带兜,用于執(zhí)行程序代碼枫笛。
GPU : Graphic Processing Unit , 圖形處理器刚照,主要用于處理圖形運算枪眉,通常所說“顯卡”的核心部件就是GPU接癌。
下面是CPU和GPU的結(jié)構(gòu)對比圖。其中:
黃色的Control為控制器,用于協(xié)調(diào)控制整個CPU的運行雨涛,包括取出指令、控制其他模塊的運行等拾积;
-
綠色的ALU(Arithmetic Logic Unit)是算術(shù)邏輯單元抹蚀,用于進行數(shù)學、邏輯運算郭变;
橙色的Cache和DRAM分別為緩存和RAM颜价,用于存儲信息。
從結(jié)構(gòu)圖可以看出诉濒,CPU的控制器較為復雜周伦,而ALU數(shù)量較少。因此CPU擅長各種復雜的邏輯運算未荒,但不擅長數(shù)學尤其是浮點運算专挪。
- 和CPU不同的是,GPU就是為實現(xiàn)大量數(shù)學運算設(shè)計的片排。從結(jié)構(gòu)圖中可以看到寨腔,GPU的控制器比較簡單,但包含了大量ALU率寡。GPU中的ALU使用了并行設(shè)計脆侮,且具有較多浮點運算單元。
硬件加速的主要原理勇劣,就是通過底層軟件代碼靖避,將CPU不擅長的圖形計算轉(zhuǎn)換成GPU專用指令,由GPU完成比默。
硬件加速的開啟
Android 系統(tǒng)的 UI 從 繪制 到 顯示在屏幕上 是分兩個步驟的
第一步:在Android 應用程序這一側(cè)進行的幻捏。(將 UI 構(gòu)建到一個圖形緩沖區(qū) Buffer 中,交給SurfaceFlinger )
第二步:在SurfaceFlinger進程這一側(cè)進行的命咐。(獲取Buffer 并合成以及顯示到屏幕中篡九。)
其中,第二步在 SurfaceFlinger 的操作一直是以硬件加速方式完成的醋奠,所以我們說的硬件加速一般指的是在 應用程序 圖形通過GPU加速渲染 到 Buffer 的過程榛臼。
在Android中伊佃,可以四給不同層次上開啟硬件加速:
- 應用:
<application android:hardwareAccelerated="true"> - Activity
<activity android:hardwareAccelerated="true"> - Window
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); - View
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
在這四個層次中,應用和Activity是可以選擇的沛善,Window只能打開航揉,View只能關(guān)閉。
硬件加速的流程分析
模式模型
構(gòu)建階段
所謂構(gòu)建就是遞歸遍歷所有視圖金刁,將需要的操作緩存下來帅涂,之后再交給單獨的Render線程利用OpenGL渲染。在Android硬件加速框架中尤蛮,View視圖被抽象成RenderNode節(jié)點媳友。
View中的繪制都會被抽象成一個個DrawOp(DisplayListOp),比如View中drawLine产捞,構(gòu)建中就會被抽象成一個DrawLintOp醇锚,drawBitmap操作會被抽象成DrawBitmapOp,每個子View的繪制被抽象成DrawRenderNodeOp坯临,每個DrawOp有對應的OpenGL繪制命令搂抒,同時內(nèi)部也握著繪圖所需要的數(shù)據(jù)。
如此以來尿扯,每個View不僅僅握有自己DrawOp List,同時還拿著子View的繪制入口焰雕,如此遞歸衷笋,便能夠統(tǒng)計到所有的繪制Op,很多分析都稱為Display List矩屁,源碼中也是這么來命名類的辟宗,不過這里其實更像是一個樹,而不僅僅是List吝秕,示意如下:
構(gòu)建完成后泊脐,就可以將這個繪圖Op樹交給Render線程進行繪制,這里是同軟件繪制很不同的地方烁峭,軟件繪制時容客,View一般都在主線程中完成繪制,而硬件加速约郁,除非特殊要求缩挑,一般都是在單獨線程中完成繪制,如此以來就分擔了主線程很多壓力鬓梅,提高了UI線程的響應速度供置。
構(gòu)建過程源碼分析
從流程圖可以看出,HardwareRenderer是整個硬件加速繪制的入口绽快。整個繪制的過程通過不斷dispatchGetDisplayList或者dispathDraw遍歷View芥丧,刷新displayList紧阔。
從名字能看出,ThreadedRenderer應該跟一個Render線程息息相關(guān)续担,不過ThreadedRenderer是在UI線程中創(chuàng)建的擅耽,那么與UI線程也必定相關(guān),其主要作用:
1赤拒、在UI線程中完成DrawOp集構(gòu)建
2秫筏、負責跟渲染線程通信
可見ThreadRenderer的作用是很重要的,簡單看一下實現(xiàn):
ThreadedRenderer(Context context, boolean translucent) {
...
<!--新建native node-->
long rootNodePtr = nCreateRootRenderNode();
mRootNode = RenderNode.adopt(rootNodePtr);
mRootNode.setClipToBounds(false);
<!--新建NativeProxy-->
mNativeProxy = nCreateProxy(translucent, rootNodePtr);
ProcessInitializer.sInstance.init(context, mNativeProxy);
loadSystemProperties();
}
- RootNode用來標識整個DrawOp樹的根節(jié)點挎挖,有個這個根節(jié)點就可以訪問所有的繪制Op.
- RenderProxy對象这敬,這個對象就是用來跟渲染線程進行通信的句柄
再看一下RenderProxy的源碼
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory)
: mRenderThread(RenderThread::getInstance())
, mContext(nullptr) {
SETUP_TASK(createContext);
args->translucent = translucent;
args->rootRenderNode = rootRenderNode;
args->thread = &mRenderThread;
args->contextFactory = contextFactory;
mContext = (CanvasContext*) postAndWait(task);
mDrawFrameTask.setContext(&mRenderThread, mContext);
}
- RenderThread是一個單例線程,也就是說蕉朵,每個進程最多只有一個硬件渲染線程崔涂,這樣就不會存在多線程并發(fā)訪問沖突問題,到這里其實環(huán)境硬件渲染環(huán)境已經(jīng)搭建好好了始衅。
ThreadRender的繪制入口
@Override
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
attachInfo.mIgnoreDirtyState = true;
final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
choreographer.mFrameInfo.markDrawStart();
<!--關(guān)鍵點1:構(gòu)建View的DrawOp樹-->
updateRootDisplayList(view, callbacks);
<!--關(guān)鍵點2:通知RenderThread線程繪制-->
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
...
}
- updateRootDisplayList冷蚂,構(gòu)建RootDisplayList,其實就是構(gòu)建View的DrawOp樹汛闸,updateRootDisplayList會進而調(diào)用根View的updateDisplayListIfDirty蝙茶,讓其遞歸子View的updateDisplayListIfDirty,從而完成DrawOp樹的創(chuàng)建
- 通知RenderThread線程繪制
構(gòu)建樹的流程
private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
<!--更新-->
updateViewTreeDisplayList(view);
if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
<!--獲取DisplayListCanvas-->
DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
try {
<!--利用canvas緩存Op-->
final int saveCount = canvas.save();
canvas.translate(mInsetLeft, mInsetTop);
callbacks.onHardwarePreDraw(canvas);
canvas.insertReorderBarrier();
canvas.drawRenderNode(view.updateDisplayListIfDirty());
canvas.insertInorderBarrier();
callbacks.onHardwarePostDraw(canvas);
canvas.restoreToCount(saveCount);
mRootNodeNeedsUpdate = false;
} finally {
<!--將所有Op填充到RootRenderNode-->
mRootNode.end(canvas);
}
}
}
- 利用View的RenderNode獲取一個DisplayListCanvas
- 利用DisplayListCanvas構(gòu)建并緩存所有的DrawOp
- 將DisplayListCanvas緩存的DrawOp填充到RenderNode
- 將根View的緩存DrawOp設(shè)置到RootRenderNode中诸老,完成構(gòu)建
DisplayList的更新過程
// Don't need to recreate the display list, just need to tell our
// children to restore/recreate theirs
if (renderNode.isValid()
&& !mRecreateDisplayList) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();
return renderNode; // no work needed
}
// If we got here, we're recreating it. Mark it as such to ensure that
// we copy in child display lists into ours in drawChild()
mRecreateDisplayList = true;
int width = mRight - mLeft;
int height = mBottom - mTop;
int layerType = getLayerType();
final DisplayListCanvas canvas = renderNode.start(width, height);
try {
if (layerType == LAYER_TYPE_SOFTWARE) {
buildDrawingCache(true);
Bitmap cache = getDrawingCache(true);
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
}
} else {
computeScroll();
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
if (debugDraw()) {
debugDrawFocus(canvas);
}
} else {
draw(canvas);
}
}
} finally {
renderNode.end(canvas);
setDisplayListProperties(renderNode);
}
- 如果本View不需要更新RendorNode隆夯,則調(diào)用dispathcGetDisplayList,讓其子view更新RendorNode别伏。最終會導致步驟2蹄衷。
- 如果本View需要更新RendorNode,則調(diào)用draw(Canvas)方法厘肮。
繼續(xù)跟蹤draw(Canvas)方法
if (!dirtyOpaque) {
drawBackground(canvas);
}
// 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);
drawAutofilledHighlight(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);
我們知道每個view的DrawRenderNodeOp緩存主要包括displayListOps和其子View的DrawRenderNodeOp
- drawBackground愧口、onDraw 、onDrawForeground类茂、drawDefaultFocusHighlight是更新自身相關(guān)的displayListOps
-
dispatchDraw向下觸發(fā)便利耍属,更新子View相關(guān)的DrawRenderNodeOp。
dispatchDraw最終會走到draw(Canvas canvas, ViewGroup parent, long drawingTime) ,看一下里面的代碼
....
if (drawingWithRenderNode) {
// Delay getting the display list until animation-driven alpha values are
// set up and possibly passed on to the view
renderNode = updateDisplayListIfDirty();
if (!renderNode.isValid()) {
// Uncommon, but possible. If a view is removed from the hierarchy during the call
// to getDisplayList(), the display list will be marked invalid and we should not
// try to use it again.
renderNode = null;
drawingWithRenderNode = false;
}
}
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
}
}
....
- 子view通過updateDisplayListIfDirty刷新自己的RendNode巩检。如果子View本身恬涧,以及子View的子控件沒有變化,RenderNode不會發(fā)生實質(zhì)的變化碴巾。
- 最后通過DisplayListCanvas的drawRenderNode將繪制緩存保存到父View 中
DisplayListCanvas類分析溯捆,以drawLine為例
void DisplayListCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
points = refBuffer<float>(points, count);
addDrawOp(new (alloc()) DrawLinesOp(points, count, refPaint(&paint)));
}
RenderThread渲染過程
DrawOp樹構(gòu)建完畢后,UI線程利用RenderProxy向RenderThread線程發(fā)送一個DrawFrameTask任務(wù)請求,RenderThread被喚醒提揍,開始渲染啤月。
- 首先進行DrawOp的合并
- 接著繪制特殊的Layer
- 最后繪制其余所有的DrawOpList
- 調(diào)用swapBuffers將前面已經(jīng)繪制好的圖形緩沖區(qū)提交給Surface Flinger合成和顯示。
開啟硬件加速的優(yōu)缺點
優(yōu)點
- 硬件加速使用雙線程工作劳跃,主線程只負責構(gòu)建繪制樹谎仲,渲染線程負責渲染。主線程基本上不會因為繪制超時而卡頓刨仑。
- GPU分擔了CPU的渲染任務(wù)郑诺,CPU有多余的時間做其他重要的事情,這將使得手機整體表現(xiàn)將更加流暢杉武。
- GPU擅長做渲染工作辙诞,硬件加速允許應用執(zhí)行繁重的渲染任務(wù)。如果沒有GPU或者不采用硬件加速轻抱,播放視頻將非撤赏浚卡頓
缺點 - 硬件加速屬于雙緩沖機制,使用顯存進行頁面渲染(使用較少的物理內(nèi)存)祈搜,導致更頻繁的顯存操作较店,可能引起以下現(xiàn)象:
花屏、閃屏 - 硬件加速的準備工作較長容燕,可能在應用剛啟動時梁呈,存在掉幀現(xiàn)象。