ViewRootImpl的performDraw过程

ViewRootImpl充当的是View和window之间的纽带。在startActivity之后,经过与ActivityManagerService的IPC交互,会在ActivityThread的handleResumeActivity方法中执行到getWindow().addView,就是将根布局 Decor添加到window中以显示。getWindow会以WindowManagerGloble来执行addView方法,其中就会创建ViewRootImpl实例并调用其setView方法传入Decor布局,在setView中会执行到performTranvesals方法,这个方法是重点:
<pre>
private void performTraversals() {
......
performMeasure();
......
performLayout();
......
performDraw();
......
}
</pre>
会依次执行Measure测量、Layout布局和Draw绘制。
接着来分析performDraw的执行路径:
ViewRootImpl.performDraw->ViewRootImpl.draw->ThreadedRenderer.draw->ThreadedRenderer.updateRootDisplayList->ThreadedRenderer.updateViewTreeDIsplayList->View.updateDisplayListIfDirty->PhoneWindow$DecorView.draw->View.draw->ViewGroup.dispatchDraw->ViewGroup.drawChild->View.draw
第一个跳跃的类是ThreadedRenderer,看名字就可以猜出类是用于绘制线程的,在ViewRootImpl.setView方法中会调用enableHardwareAcceleration(attars)方法:
<pre>
public void setView(View view, WindowManager.LayoutParams attars, View panelParnetView) {
......
if (mSUrfaceHolder == null) {
enableHardwareAcceleration(attrs);
}
......
}

private void enableHardwareAcceleration(WindowManager.Layoutparams attars) {
......
mAttachInfo.mHardwareRenderer = HardwareRenderer.create(mContext, translucent);
......
}
</pre>
最后调用create方法创建HardwareRenderer实例:
<pre>
static HardwareRenderer create(Context context, boolean translucent) {
HardwareRenderer renderer = null;
if (DisplayListCanvas.isAvailable()) {
renderer = new ThreadedRenderer(context, translucent);
}
return renderer;
}
</pre>
这里创建的是ThreadedRenderer(继承自HardwareRenderer),然后调用ThreadedRenderer的draw方法,再调其updateRootDisplayList方法,再调其updateViewTreeDisplayList方法,最后就调用其参数view(也就是DecorView)的updateDisplayListIfDirty方法,在这个方法里会调用View的draw(canvas)绘制方法,由于DecorView方法重写了draw方法,所以先执行DecorView的draw方法:
<pre>
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenubackground.draw(canvas);
}
}
</pre>
方法开头就直接调用了父类(View)的draw(canvas)方法(这里需要说明,View有两个draw重载方法,所以下面提到draw方法都会带上不同的参数来区分):
<pre>
/**
*使用所给的Canvas手动刷新view(和他所有的childrend)。
*在刷新之前,view必须做了完整的测量布局。创建view的时候
*如果需要自定义绘制就重写onDraw方法,否则直接调用super即可。
public void draw(Canvas canvas) {
......
//步骤1,绘制背景
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
//一般情况下会跳过步骤2和5
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
//步骤3,绘制内容
if (!dirtyOpaque) onDraw(canvas);
//步骤4,绘制children
dispatchDraw(canvas);
//浮层是内容的一部分并绘制在前景之下
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
//步骤6,绘制装饰(前景,进度条)
onDrawForeground(canvas);
//自定义操作
reutrn;
}

boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;

float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;

//步骤2,保存canvas图层
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
    paddingLeft += getLeftPaddingOffset();
}

int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
    right += getRightpaddingOffset();
    bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mSrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
//如果顶部和底部的渐变层重叠导致很奇怪的显示,就去剪切
if (verticalEdges && (top + length > bottom - length)) {
    length = (bottom - top) / 2;
 }
 //如果有需要的话,横向渐变也剪切
 if (horizontalEdges && (left + length > right - length)) {
    length = (right -left) / 2;
 }
if (verticalEdges) {
    topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
    drawTop = topFadeStrength * fadeHeight > 1.0f;
    bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
    drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
if (horizontalEdges) {
    leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
    drawLeft = leftFadeStrength * fadeHeight > 1.0f;
    rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
    drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolideColor();
if (solidColor == 0) {
    final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
    if (drawTop) {
        canvas.saveLayer(left, top, right, top+length, null, flags);
    }
    if (drawBottom) {
        canvas.saveLayer(left, bottom-length, right, bottom, null, flags);
    }
    if (drawLeft) {
        canvas.saveLayer(left, top, left+length, bottom, null, flags);
    }
    if (drawRight) {
        canvas.saveLayer(right-length, top, right, bottom, null, flags);
    }
} else {
    scrollabilityCache.setFadeColor(solidColor);
}
//步骤3,绘制内容
if (!dirtyOpaque) onDraw(canvas);
//步骤4, 绘制children
dispatchDraw(canvas);
//步骤5, 绘制渐变效果和重载图层
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
if (drawTop) {
    matrix.setScale(1, fadeHeight * topFadeStrength);
    matrix.postTranslate(left, top);
    fade.setLocalMatrix(matrix);
    p.setShader(fade);
    canvas.drawRect(left, top, right, top+length, p);
}
if (drawBottom) {
    matrix.setScale(1, fadeHeight * bottomFadeStrength);
    matrix.postRotate(180);
    matrix.postTranslate(left, bottom);
    fade.setLocalMatrix(matrix);
    p.setShader(fade);
    canvas.drawRect(left, bottom-length, right, bottom, p);
}
if (drawLeft) {
    matrix.setScale(1, fadeHeight * rightFadeStrength);
    matrix.postRotate(90);
    matrix.postTranslate(right, top);
    fade.setLocalMatrix(matrix);
    p.setShader(fade);
    canvas.drawRect(right-length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
//覆盖层是内容的一部分并且绘制在前景的下面
if (mOverlay != null && !mOverlay.isEmpty()) {
    mOverlay.getOverlayView().dispatchDraw(canvas);
}
//步骤6, 绘制装饰(前景,进度条)
onDrawForeground(canvas);

}
</pre>
android源码已经对draw方法进行了说明:一般的绘制步骤:1.绘制背景 2.如果需要,保存canvas来为绘制渐变做准备 3.绘制view 的内容 4.绘制子childrend 5.如果需要,绘制边界渐变和重载图层 6.绘制装饰层(例如进度条)。
其中第3步和第4步分别调用了onDraw和dispatchDraw,onDraw也就是我们自定义View时重写的绘制接口;dispatchDraw在View中是一个空实现的方法,需要View的子类去实现;
<pre>
protected void dispatchDraw(Canvas canvas) {
......
more |= drawChild(canvas, child, drawingTime);
......
}
</pre>
这里调用了ViewGroup的drawChild方法:
<pre>
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
</pre>

本来这里以后就已经不是DecorView的代码了,而是他的child View的方法,但是在调试时系统也把子view的draw(canvas, this, drawingTime)方法纳入第一调用层级,所以这里一起说一下:
<pre>
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
......
if (drawingWithRenderNode) {
renderNode = updateDisplayListIfDirty();
if (!renderNode.isValid()) {
renderNode = null;
drawingWIthRenderNode = false;
}
}
......
}
</pre>
在这里调用了View的updateDisplayListIfDirty方法,这样就开始了新的一轮轮回:view.updateDisplayListIfDirty-->ViewGroup.dispatchDraw-->ViewGroup.drawChild-->新子View.draw(canvas, parent, drawingTime)----------->>>>
如此轮回,直到到达最后一个子View,在最后一个View的轮回中,view.updateDisplayListIfDirty方法里有一段代码要注意:
<pre>
public RenderNode updateDisplayListIfDirty() {
......
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
} else {
draw(canvas);
}
......
}
</pre>
这里有个if判断,如果成立则调用View.dispatchDraw方法,否则调用View.draw(canvas)方法,这个就是终结轮回的节点。到达最后一个子View时判断就不成立,直接调用draw(canvas),而在draw方法中(看上面代码段),就会依次调用onDraw和dispatchDraw,此时View的dispatchDraw为空实现,代码不轮回,直接运行并返回,从而终结draw轮回。

总结一下:整个流程由DecorView开始,到最终子View结束。DecorView的draw(canvas)中依次执行onDraw和dispatchDraw,此时dispatchDraw有children可以分发,开始轮回................到最后一个子View时,其updateDisplayListIfDirty方法经过if判断不执行dispatchDraw而是执行draw(canvas),在他的draw里dispatchDraw没有children可用,所以继续运行并返回,结束轮回。

以一张调试图来展示一下整个流程:

C21598DB-BBFD-4F60-BE7C-2C9C173D74E6.png

==============================

分析一下View.updateDisplayListIfDirty方法中对执行dispatchDraw还是draw方法的判定条件


6383EFDF-A106-43B3-A4B4-41F7D033623F.png

判定条件是:mPrivateFlags & PFLAG_SKIP_DRAW == PFLAG_SKIP_DRAW;
翻译过来就是mPrivateFlags中包含PFLAG_SKIP_DRAW就执行dispatchDraw,不包含就执行draw(mPrivateFlags是View中当前起作用的所有Flags标识)。那PFLAG_SKIP_DRAW是在哪里设置的呢?
View中有个setFlags方法,这里就是设置PFLAG_SKIP_DRAW的地方。
<pre>
void setFlags(int flags, int mask) {
......
if ((changed & DRAW_MASK) != 0) {
......
//表示将PFLAG_SKIP_DRAW放入mPrivateFlags中
mPrivateFlags |= PFLAG_SKIP_DRAW;
......
requestlayout();
invalidate(true);
}
......
}
</pre>
简单的解释就是:如果是View则不设置PFLAG_SKIP_DRAW,如果是ViewGroup就设置。这也符合上面的if分析。那setFlags是什么时候调用的呢?
在ViewGroup初始化的时候,会调用构造函数:
<pre>
public ViewGroup(Context context, AttributeSet attrs............) {
super(context, attrs.....);
initViewGroup();
initFromAttributes(context, attrs.....);
}
</pre>
在ViewGroup的构造函数里会调用initViewGroup方法:
<pre>
private void initViewGroup() {
if (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
......
}
</pre>
这里就调用到了setFlags并传递参数(WILL_NOT_DRAW, DRAW_MASK),这也正好符合setFlags中if的判断条件。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容