前言
Draw 過(guò)程系列文章
上篇分析了硬件加速相關(guān)知識(shí):
Android 自定義View之Draw過(guò)程(中)
本篇將從代碼的角度深入分析硬件加速繪制與軟件繪制。
通過(guò)本篇文章,你將了解到:
1、軟件繪制流程
2、硬件加速繪制流程
2、LayerType 對(duì)繪制的影響
3、Canvas 從哪里來(lái)到哪里去
4、繪制流程全家福
軟件繪制流程
上篇說(shuō)過(guò)在ViewRootImpl->draw(xx)里軟件繪制與硬件加速繪制分道揚(yáng)鑣:

上圖是Window 區(qū)分硬件加速繪制與軟件繪制的入口。
由易到難,先來(lái)看看軟件繪制流程。
drawSoftware(xx)
#ViewRootImpl.java
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
//持有的畫布
final Canvas canvas;
...
try {
...
//申請(qǐng)畫布對(duì)象,該畫布初始大小為dirty的尺寸
canvas = mSurface.lockCanvas(dirty);
//設(shè)置密度
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
...
} catch (IllegalArgumentException e) {
...
return false;
} finally {
...
}
try {
//畫布是否需要移動(dòng)
canvas.translate(-xoff, -yoff);
//mView 即是添加到該Window的RootView
//對(duì)于Activity、Dialog開(kāi)啟的Window,mView就是我們熟知的DecorView
//rootView draw()方法
mView.draw(canvas);
} finally {
try {
//提交繪制的內(nèi)容到Surface
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
...
}
}
return true;
}
以上方法功能重點(diǎn)如下:
1、從Surface 申請(qǐng)Canvas對(duì)象,該Canvas為CompatibleCanvas 類型
2、拿到Canvas后,調(diào)用View.draw(Canvas)開(kāi)始繪制RootView
3、整個(gè)ViewTree 繪制完成后將內(nèi)容提交到Surface
注:RootView 只是個(gè)代稱,并不是某個(gè)View的名字。
一些常見(jiàn)的RootView 請(qǐng)移步:Android 輸入事件一擼到底之源頭活水(1)
關(guān)于View.draw(xx)方法在:Android 自定義View之Draw過(guò)程(上) 已做過(guò)詳細(xì)分析,結(jié)合上述代碼,用如下圖表示:

可以看得出來(lái),軟件繪制有如下特點(diǎn):
- 從RootView 遞歸調(diào)用子布局的draw(xx)方法,直到每個(gè)符合條件的View都進(jìn)行了繪制
- 繪制過(guò)程中,所有的View持有相同的Canvas對(duì)象
引入問(wèn)題1:既然所有的View都持有相同的Canvas,那么每個(gè)View繪制的起點(diǎn)、終點(diǎn)是如何確定的呢?
該問(wèn)題稍后分析。
硬件加速繪制流程
概要
軟件繪制是將Canvas的一系列操作寫入到Bitmap里,而對(duì)于硬件加速繪制來(lái)說(shuō),每個(gè)View 都有一個(gè)RenderNode,當(dāng)需要繪制的時(shí)候,從RenderNode里獲取一個(gè)RecordingCanvas,與軟件繪制一樣,也是調(diào)用Canvas一系列的API,只不過(guò)調(diào)用的這些API記錄為一系列的操作行為存放在DisplayList。當(dāng)一個(gè)View錄制結(jié)束,再將DisplayList交給RenderNode。此時(shí),繪制的步驟已經(jīng)記錄在RenderNode里,到此,針對(duì)單個(gè)View的硬件繪制完成,這個(gè)過(guò)程也稱作為DisplayList的構(gòu)建過(guò)程。
調(diào)用過(guò)程分析
來(lái)看看硬件加速的入口:
#ThreadedRenderer.java
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
...
//(1)--->錄制操作
//更新根View的DisplayList
//從此處開(kāi)始將繪制操作記錄到DisplayList里
//最終記錄在rootRenderNode里
updateRootDisplayList(view, callbacks);
//(2)--->渲染
//渲染繪制的內(nèi)容
//以proxy為橋梁,而proxy又與rootRenderNode關(guān)聯(lián)
//因此最終將上一步記錄的繪制操作交給單獨(dú)的線程渲染
int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
...
}
重點(diǎn)關(guān)注錄制操作過(guò)程,接著來(lái)分析它:
#ThreadedRenderer.java
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
//遍歷ViewTree,構(gòu)建DisplayList
updateViewTreeDisplayList(view);
//當(dāng)ViewTree DisplayList構(gòu)建完畢后
//一開(kāi)始mRootNode 是沒(méi)有DisplayList
if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
//申請(qǐng)Canvas
RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
try {
...
//view.updateDisplayListIfDirty() 返回的是RootView 關(guān)聯(lián)的renderNode
//現(xiàn)在將RootView renderNode掛到canvas下,這樣子就串聯(lián)起所有的renderNode了
canvas.drawRenderNode(view.updateDisplayListIfDirty());
...
mRootNodeNeedsUpdate = false;
} finally {
//最后將DisplayList 掛到renderNode下
mRootNode.endRecording();
}
}
}
private void updateViewTreeDisplayList(View view) {
//標(biāo)記該View已繪制過(guò)
view.mPrivateFlags |= View.PFLAG_DRAWN;
//mRecreateDisplayList --> 表示該View 是否需要重建DisplayList,也就是重新錄制,更直白地說(shuō)是否需要走Draw 過(guò)程
//若是打上了 PFLAG_INVALIDATED 標(biāo)記,也就是該View需要刷新,則需要重建
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
== View.PFLAG_INVALIDATED;
//清空原來(lái)的值
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
//如果有需要,更新View的DisplayList
view.updateDisplayListIfDirty();
//View 已經(jīng)重建完畢,無(wú)需再重建
view.mRecreateDisplayList = false;
}
以上調(diào)用了到了View里的方法:updateDisplayListIfDirty()。
顧名思義,如果有需要更新View的DisplayList。
#View.java
public RenderNode updateDisplayListIfDirty() {
//每個(gè)View構(gòu)造的時(shí)候都會(huì)創(chuàng)建一個(gè)RenderNode:mRenderNode,稱之為渲染節(jié)點(diǎn)
final RenderNode renderNode = mRenderNode;
//是否支持硬件加速,通過(guò)判斷View.AttachInfo.mThreadedRenderer
if (!canHaveDisplayList()) {
return renderNode;
}
//取出該View的標(biāo)記
//1、繪制緩存失效 2、渲染節(jié)點(diǎn)還沒(méi)有DisplayList 3、渲染節(jié)點(diǎn)有DisplayList,但是需要更新
//三者滿足其中一個(gè)條件,則進(jìn)入條件代碼塊
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.hasDisplayList()
|| (mRecreateDisplayList)) {
//如果有DisplayList且該DisplayList無(wú)需更新,則說(shuō)明該View不需要重新走Draw過(guò)程
if (renderNode.hasDisplayList()
&& !mRecreateDisplayList) {
//標(biāo)記該View已經(jīng)繪制完成且緩存是有效的
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
//繼續(xù)查看子布局是否需要構(gòu)建DisplayList
dispatchGetDisplayList(); //---------(1)
return renderNode; // no work needed
}
//上述條件不滿足,則說(shuō)明該View需要構(gòu)建DisplayList
mRecreateDisplayList = true;
//layout 過(guò)程確定的View的坐標(biāo)此時(shí)用到了
int width = mRight - mLeft;
int height = mBottom - mTop;
//獲取當(dāng)前設(shè)置的layerType
int layerType = getLayerType();
//從renderNode里獲取Canvas對(duì)象,Canvas的尺寸初始化為View的尺寸
//該Canvas是RecordingCanvas類型,簡(jiǎn)單理解為用來(lái)錄制的Canvas
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
try {
//layerType 有三種取值
//如果是軟件繪制緩存
if (layerType == LAYER_TYPE_SOFTWARE) {
//---------(2)
//則構(gòu)建緩存
buildDrawingCache(true);
//實(shí)際上就是將繪制操作寫入Bitmap里
Bitmap cache = getDrawingCache(true);
if (cache != null) {
//將該Bitmap繪制到Canvas里
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
}
} else {
//如果沒(méi)有設(shè)置軟件繪制緩存
//一般配合Scroller 滑動(dòng)使用
computeScroll();
//mScrollX、mScrollY 為滾動(dòng)的距離
//當(dāng)mScrollX 為正值時(shí),canvas向左移動(dòng),繪制的內(nèi)容往左移動(dòng),這也就是為什么明明scroll為正值,為啥View內(nèi)容往左移的根本原因
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
//---------(3)
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
//該View不需要繪制自身內(nèi)容(包括內(nèi)容、前景、背景等)
//直接發(fā)起繪制子布局的請(qǐng)求
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
if (debugDraw()) {
debugDrawFocus(canvas);
}
} else {
//需要繪制自身
draw(canvas);
}
}
} finally {
//最后結(jié)束canvas錄制,并將錄制產(chǎn)生的結(jié)果:DisplayList交給renderNode
//---------(4)
renderNode.endRecording();
setDisplayListProperties(renderNode);
}
} else {
//三個(gè)條件不滿足,認(rèn)為已經(jīng)繪制完成
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
//返回renderNode 到上一層
//---------(5)
return renderNode;
}
注釋里列出了5個(gè)比較重要的點(diǎn),來(lái)一一解析:
(1)
dispatchGetDisplayList()
該方法在View里沒(méi)有實(shí)現(xiàn),在ViewGroup實(shí)現(xiàn)如下:
#ViewGroup.java
protected void dispatchGetDisplayList() {
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
//遍歷子布局
final View child = children[i];
if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
//重建子布局DisplayList
recreateChildDisplayList(child);
}
}
...
}
private void recreateChildDisplayList(View child) {
//判斷是否需要重建
child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
child.mPrivateFlags &= ~PFLAG_INVALIDATED;
//調(diào)用子布局重建方法
child.updateDisplayListIfDirty();
child.mRecreateDisplayList = false;
}
可以看出,dispatchGetDisplayList 作用:
遍歷子布局,并調(diào)用它們的重建方法
這樣子,從RootView開(kāi)始遞歸調(diào)用updateDisplayListIfDirty(),如果子布局需要重建DisplayList,則重新錄制繪制操作,否則繼續(xù)查找子布局是否需要重建DisplayList。
(2)
buildDrawingCache(xx) 用來(lái)繪制離屏緩存,后續(xù)再細(xì)說(shuō)。
(3)
跳過(guò)繪制這段可參考:Android ViewGroup onDraw為什么沒(méi)調(diào)用
(4)
硬件加速繪制有開(kāi)始、錄制、結(jié)束的標(biāo)記:
1、renderNode生成用來(lái)繪制的Canvas--> beginRecording,此為開(kāi)始。
2、調(diào)用Canvas.drawXX()--> 錄制具體的東西,此為錄制過(guò)程
3、renderNode結(jié)束繪制--> endRecording(),從Canvas里拿到錄制的結(jié)果:DisplayList,并將該結(jié)果賦值給renderNode,此為錄制結(jié)束
(5)
從第4點(diǎn)可以看出,錄制的結(jié)果已經(jīng)存放到RenderNode里,需要將RenderNode返回,該RenderNode將會(huì)被掛到父布局的Canvas里,也就是說(shuō)父布局Canvas已經(jīng)持有了子布局錄制好的DisplayList。
簡(jiǎn)單一些,用圖表示單個(gè)View的硬件加速繪制流程:

ViewTree 硬件加速過(guò)程:

很明顯,硬件加速繪制過(guò)程就是構(gòu)建DisplayList過(guò)程,從RootView遞歸子布局構(gòu)建DisplayList,當(dāng)整個(gè)DisplayList構(gòu)建完畢,就可以進(jìn)行渲染了,渲染線程交給GPU處理,這樣子大大解放了CPU工作。
LayerType 對(duì)繪制的影響
以上分別闡述了軟件繪制與硬件加速繪制的流程,分析的起點(diǎn)是該Window是否支持硬件加速而走不同的分支。
從RootView開(kāi)始到遍歷所有的子孫View,要么都是軟件繪制,要么都是硬件加速繪制,如果在硬件加速繪制的中途禁用了某個(gè)View的硬件加速會(huì)如何表現(xiàn)呢?我們之前提到過(guò)通過(guò)設(shè)置View->LayerType來(lái)禁用硬件加速,接下來(lái)分析LayerType對(duì)繪制流程的影響。
從 Android 自定義View之Draw過(guò)程(上)
分析可知:不管軟件繪制或者硬件加速繪制,都會(huì)走一套公共的流程:
draw(xx)->dispatchDraw(xx)->draw(x1,x2,x3)->draw(xx)...

這也是遞歸調(diào)用的過(guò)程。
對(duì)于單個(gè)View,軟件繪制與硬件加速分歧點(diǎn)在哪呢?
答案是:draw(x1,x2,x3)方法
View的軟硬繪制分歧點(diǎn)
#View.java
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
//canvas是否支持硬件加速
//默認(rèn)canvas是不支持硬件加速的
//RecordingCanvas支持硬件加速
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
//是否使用RenderNode繪制,也就是該View是否支持硬件加速
//該View支持硬件加速的條件是:canvas支持硬件加速+該Window支持硬件加速
boolean drawingWithRenderNode = mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& hardwareAcceleratedCanvas;
//動(dòng)畫相關(guān)
...
if (hardwareAcceleratedCanvas) {
//canvas支持硬件加速,需要檢測(cè)是否需要重建DisplayList
mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
mPrivateFlags &= ~PFLAG_INVALIDATED;
}
RenderNode renderNode = null;
Bitmap cache = null;
//獲取LayerType,View 默認(rèn)類型是None
int layerType = getLayerType();
//------>(1)
if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
//1、設(shè)置了離屏軟件繪制緩存 2、View不支持硬件加速繪制
//兩者滿足其一
if (layerType != LAYER_TYPE_NONE) {
//可能設(shè)置了軟件緩存或者硬件緩存
//此時(shí)硬件緩存當(dāng)做軟件緩存來(lái)使用
layerType = LAYER_TYPE_SOFTWARE;
//繪制到軟件緩存
//------>(2)
buildDrawingCache(true);
}
//取出軟件緩存
cache = getDrawingCache(true);
}
if (drawingWithRenderNode) { //----->(3)
//該View支持硬件加速
//則嘗試構(gòu)建DisplayList,并返回renderNode
renderNode = updateDisplayListIfDirty();
if (!renderNode.hasDisplayList()) {
//一般很少走這
renderNode = null;
drawingWithRenderNode = false;
}
}
int sx = 0;
int sy = 0;
if (!drawingWithRenderNode) {
computeScroll();
//不使用硬件加速時(shí)將內(nèi)容偏移記錄
sx = mScrollX;
sy = mScrollY;
}
//注意這兩個(gè)標(biāo)記,下面會(huì)用到
//1、存在軟件緩存 2、不支持硬件加速 兩者同時(shí)成立,則說(shuō)明:使用軟件緩存繪制
final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
//1、不存在軟件緩存 2、不支持硬件加速,兩者同時(shí)成立,則說(shuō)明:使用軟件繪制
final boolean offsetForScroll = cache == null && !drawingWithRenderNode;
if (offsetForScroll) {
//------>(4)
//如果是軟件繪制,需要根據(jù)View的偏移與內(nèi)容偏移移動(dòng)canvas
//此時(shí)包括內(nèi)容滾動(dòng)偏移量
canvas.translate(mLeft - sx, mTop - sy);
} else {
if (!drawingWithRenderNode) {
//------>(5)
//如果不支持硬件加速,則說(shuō)明可能是軟件緩存繪制
//此時(shí)也需要位移canvas,只不過(guò)不需要考慮內(nèi)容滾動(dòng)偏移量
canvas.translate(mLeft, mTop);
}
...
}
...
if (!drawingWithRenderNode) {
//不支持硬件加速
if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
//裁減canvas,限制canvas展示區(qū)域,這就是子布局展示為什么不能超過(guò)父布局區(qū)域的原因
if (offsetForScroll) {
//是軟件繪制,則裁減掉滾動(dòng)的距離
//------>(6)
canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
} else {
//否則無(wú)需考慮滾動(dòng)距離
if (!scalingRequired || cache == null) {
canvas.clipRect(0, 0, getWidth(), getHeight());
} else {
canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
}
}
}
...
}
if (!drawingWithDrawingCache) {
//不使用軟件緩存繪制
if (drawingWithRenderNode) {
//支持硬件加速
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
//將該View的renderNode掛到父布局的Canvas下,此處建立了連接
((RecordingCanvas) canvas).drawRenderNode(renderNode);
} else {
//軟件繪制,發(fā)起了繪制請(qǐng)求:dispatchDraw(canvas) & draw(canvas);
//------>(7)
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
}
} else if (cache != null) {
//軟件繪制緩存存在
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
...
//沒(méi)有設(shè)置緩存類型,則將軟件繪制緩存寫入到canvas的bitmap里
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
} else {
...
canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
}
}
...
//該View 構(gòu)建完畢
mRecreateDisplayList = false;
return more;
}
該方法里面的判斷比較亂,提取了比較重要的7個(gè)點(diǎn):
(1)
只要設(shè)置了離屏軟件緩存或者不支持硬件加速,那么就需要使用軟件緩存繪制。
(3)
只要支持硬件加速,則使用硬件加速繪制。結(jié)合(1),是不是覺(jué)得有點(diǎn)矛盾呢?想想滿足(1)條件的情況之一:設(shè)置了離屏軟件緩存,也支持硬件加速,按照(1)的邏輯,那么此時(shí)啟用了軟件緩存繪制。那么(3)繼續(xù)用硬件加速繪制不是多此一舉嗎?
回顧一下updateDisplayListIfDirty()里的片段:
if (layerType == LAYER_TYPE_SOFTWARE) {
...
//軟件緩存繪制
buildDrawingCache(true);
} else {
//硬件繪制
...
}
這里邊再次進(jìn)行了判斷。
(4)(5)
Canvas位移
對(duì)于軟件繪制,將Canvas進(jìn)行位移,位移距離考慮了View本身偏移以及View內(nèi)容偏移。
對(duì)于軟件緩存繪制,將Canvas進(jìn)行位移,僅僅考慮了View本身偏移。
對(duì)于硬件加速繪制,沒(méi)看到對(duì)Canvas進(jìn)行位移。
實(shí)際上針對(duì)軟件緩存繪制與硬件加速繪制,Canvas位移既包括View本身偏移也包含了View內(nèi)容偏移。只是不在上述的代碼里。
對(duì)于軟件緩存繪制:
在buildDrawingCacheImpl(xx) -> canvas.translate(-mScrollX, -mScrollY);進(jìn)行了內(nèi)容偏移。
而對(duì)于硬件加速繪制:
在layout(xx)->mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom) 進(jìn)行了View本身的偏移。
在updateDisplayListIfDirty(xx)->canvas.translate(-mScrollX, -mScrollY);進(jìn)行了內(nèi)容偏移。
因此,不論軟件繪制/軟件緩存繪制/硬件加速繪制,三者都對(duì)Canvas進(jìn)行了位移,位移包括:View本身的偏移以及內(nèi)容的偏移。
以上也解釋了問(wèn)題1。
(6)
Canvas裁減
對(duì)于軟件繪制,Canvas裁減包括了View內(nèi)容偏移。
對(duì)于軟件緩存繪制,Canvas 繪制到Bitmap里。
對(duì)于硬件加速繪制,在setDisplayListProperties(xx)->renderNode.setClipToBounds(xx) 進(jìn)行裁減。
(7)
如果是軟件繪制,那么直接調(diào)用dispatchDraw(xx)/draw(xx)發(fā)起繪制。
draw(x1,x2,x3)方法作用:決定View是使用何種繪制方式:
1、硬件加速繪制
2、軟件繪制
3、軟件緩存繪制
軟件緩存繪制
來(lái)看看如何構(gòu)建軟件緩存:
#View.java
public void buildDrawingCache(boolean autoScale) {
//如果緩存標(biāo)記為失效或者緩存為空
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
mDrawingCache == null : mUnscaledDrawingCache == null)) {
try {
//構(gòu)建緩存
buildDrawingCacheImpl(autoScale);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}
private void buildDrawingCacheImpl(boolean autoScale) {
int width = mRight - mLeft;
int height = mBottom - mTop;
...
boolean clear = true;
Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;
//bitmap 不存在或者bitmap與View尺寸不一致,則創(chuàng)建
...
Canvas canvas;
if (attachInfo != null) {
canvas = attachInfo.mCanvas;
if (canvas == null) {
//第一次,AttachInfo里并沒(méi)有Canvas
canvas = new Canvas();
}
//關(guān)聯(lián)bitmap
canvas.setBitmap(bitmap);
attachInfo.mCanvas = null;
} else {
//很少走這
canvas = new Canvas(bitmap);
}
computeScroll();
final int restoreCount = canvas.save();
//根據(jù)內(nèi)容滾動(dòng)平移
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN;
if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
mLayerType != LAYER_TYPE_NONE) {
//打上標(biāo)記,說(shuō)明軟件繪制緩存已生效
mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
}
//同樣的,調(diào)用公共方法
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
} else {
draw(canvas);
}
canvas.restoreToCount(restoreCount);
canvas.setBitmap(null);
if (attachInfo != null) {
//記錄下來(lái),下次創(chuàng)建直接使用
attachInfo.mCanvas = canvas;
}
}
如此一來(lái),軟件緩存就構(gòu)建完成了,其結(jié)果存儲(chǔ)在Bitmap里,可以通過(guò)如下方法獲?。?/p>
#View.java
public Bitmap getDrawingCache(boolean autoScale) {
//禁止使用軟件緩存
//默認(rèn)不禁止
if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
return null;
}
if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
//是否開(kāi)啟了軟件緩存繪制,默認(rèn)不開(kāi)啟
//構(gòu)建緩存
buildDrawingCache(autoScale);
}
//將緩存返回
return autoScale ? mDrawingCache : mUnscaledDrawingCache;
}
該方法可用來(lái)獲取View的頁(yè)面。
做一個(gè)小結(jié):
一開(kāi)始,硬件加速繪制流程和軟件繪制流程各走各的互不影響。
1、使用軟件繪制時(shí)候,設(shè)置了離屏緩存類型:軟件緩存,則軟件繪制失效,僅僅使用軟件緩存繪制。設(shè)置了硬件緩存類型也當(dāng)做軟件緩存繪制。
2、使用硬件加速繪制的時(shí)候,設(shè)置了離屏緩存類型:軟件緩存,則硬件加速繪制失效,僅僅使用軟件緩存繪制。這也就是為什么設(shè)置軟件緩存可以禁用硬件加速的原因。
3、軟件緩存繪制的結(jié)果保存在bitmap里,該Bitmap最終會(huì)繪制到父布局的Canvas里。
不管使用哪種繪制類型,都會(huì)走共同的調(diào)用方法:draw(xx)/dispatchDraw(xx)。
因此,繪制類型對(duì)于我們重寫onDraw(xx)是透明的。
Canvas 從哪里來(lái)到哪里去
軟件繪制
從ViewRootImpl->drawSoftware(xx)開(kāi)始,通過(guò):
canvas = mSurface.lockCanvas(dirty);
生成了Canvas。該Canvas通過(guò)View.draw(xx)方法傳遞給所有的子布局,因此此種情形下,整個(gè)ViewTree共享同一個(gè)Canvas對(duì)象。Canvas類型為:CompatibleCanvas。
硬件加速繪制
從View->updateDisplayListIfDirty(xx)開(kāi)始,通過(guò):
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
生成了Canvas??梢钥闯?,對(duì)于每個(gè)支持硬件加速的View都重新生成了Canvas。Canvas類型為:RecordingCanvas。
軟件緩存繪制
從View->buildDrawingCacheImpl(xx)開(kāi)始,通過(guò):
canvas = new Canvas();
生成了Canvas,并將該Canvas記錄在AttachInfo里,下次再次構(gòu)建該View軟件緩存時(shí)拿出來(lái)使用。可以看出,對(duì)于每個(gè)使用了軟件緩存的View都生成了新的Canvas,當(dāng)然如果AttachInfo有,就可以重復(fù)使用。
脫離View的Canvas
以上三者有個(gè)共同的特點(diǎn):所生成的Canvas最終都與Surface建立了聯(lián)系,因此通過(guò)這些Canvas繪制的內(nèi)容最終能夠展示在屏幕上。
那是否可以直接構(gòu)造脫離View的Canvas呢?答案是可以的。
private void buildCanvas(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas();
canvas.setBitmap(bitmap);
//繪制
canvas.drawXX(xx);
...
}
如上所示,創(chuàng)建一個(gè)Canvas與Bitmap,并將兩者關(guān)聯(lián)起來(lái)。最后調(diào)用Canvas繪制API,繪制的結(jié)果將保存在Bitmap里。這個(gè)過(guò)程實(shí)際上也是軟件緩存繪制使用的方法。
當(dāng)然拿到了Bitmap后,我們想讓其展示就比較簡(jiǎn)單了,只要讓其關(guān)聯(lián)到View上就可以展示到屏幕上了。關(guān)聯(lián)到View上實(shí)際上就是使用View關(guān)聯(lián)的Canvas將生成的Bitmap繪制其上,
繪制流程全家福
用圖表示繪制流程:
單純的軟件繪制與硬件加速繪制:

設(shè)置了軟件緩存時(shí)的繪制:

至此,Draw過(guò)程系列文章結(jié)束。
本文源碼基于Android 10。