Android-View绘制流程浅析
引
这段时间学习了下View的绘制流程,本着好记性不如烂笔头的原则,尝试将这些内容记录下来,用于巩固和总结。
这次学习的源码是基于Android SDK25来学习的,相比于之前版本的源码有了些许改变。
对于部分代码的功能和作用没有专门在正文中写出,而是以注释的形式写在了代码中。
基本流程
流程可大致分为两个部分,首先是在Activity的onCreate阶段设置contentView,另一个阶段则是在Activity.onResume阶段后的绘制过程。
大致流程图如下:
绘制流程.png
流程解析
setContentView
刚开始学习Android时,默认生成的hello world中的OnCreate的方法里调用的就是setContentView(R.layout.activity_main);
而目前默认的MainActivity继承于AppCompatActivity
在API22之后Google遗弃了ActionBarActivity,推荐我们也可以说是强制我们使用AppCompatActivity。
在AppCompatActivity.java中可以看到如下的代码:
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
在这3个重载方法setContentView中都会调用getDelegate()的setContentView()方法。而getDelegate()则会根据不同的api版本返回不同的Delegate实例,这些实例之间是相互继承的关系
最后我们在AppCompatDelegateImplV9中找到了setContentView的实现方法
@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mOriginalWindowCallback.onContentChanged();
}
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
@Override
public void setContentView(View v, ViewGroup.LayoutParams lp) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v, lp);
mOriginalWindowCallback.onContentChanged();
}
上面的代码中ensureSubDecor()方法用于生成DecorView和subDecor。与之前版本中Activity生成的DecorView不同的地方在于添加了一层布局FitWindowsLinearLayout,并将R.id.content放入了其中。这里涉及到的流程比较复杂,如果想详细了解这部分代码推荐看这篇文章
到这里终于看见了比较熟悉的布局加载的代码,在这里将我们的在onCreate中的设置的contentView放了进去。
updateViewLayout
在Activity的OnResume()方法运行后,ActivityThread.java会调用handleResumeActivity()方法,并在其中调用View的updateViewLayout(),而ViewManager是一个接口,ViewGroup实现了此接口:
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (!checkLayoutParams(params)) {
throw new IllegalArgumentException("Invalid LayoutParams supplied to " + this);
}
if (view.mParent != this) {
throw new IllegalArgumentException("Given view not a child of " + this);
}
view.setLayoutParams(params);
}
在其中进行了一些检查后调用了view.setLayoutParams的方法。
public void setLayoutParams(ViewGroup.LayoutParams params) {
//判断params是否为空
if (params == null) {
throw new NullPointerException("Layout parameters cannot be null");
}
mLayoutParams = params;
//根据的布局方向解析布局参数
resolveLayoutParams();
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).onSetLayoutParams(this, params);
}
requestLayout();
}
在此方法中进行了参数的赋值和方法的回调,而requestLayout()则循环调用了 mParent.requestLayout()方法,最终调用到的是ViewRootImpl.java的requestLayout()方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//判断调用线程是否为主线程
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
其中scheduleTraversals()方法则是用于通知开始绘制的重要方法:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//设置一个同步分割符,用于分割该消息之后的消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//通过mChoreographer异步调用mTraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
其中mChoreographer是一个消息处理器,用于控制同步处理输入(Input)、动画(Animation)、绘制(Draw)三个UI操作,具体分析推荐这篇文章。
其中的mTraversalRunnable的在它的run方法中调用了doTraversal()方法:
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
至此我们终于看到了performTraversals()方法。
performTraversals()
performTraversals()中做了非常多的处理,这里我们只关注于绘制相关的三大流程,简化代码如下:
private void performTraversals() {
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//Measure流程入口
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
//perform流程入口
performLayout(lp, mWidth, mHeight);
......
//perform流程入口
performDraw();
}
MeasureSpec
MeasureSpec是一个十分特殊的类,它用于确定view的测量规格,代表了一个32位的int值,由SpecMode(高2位)+SpecSize(低30位)两部分组成。
SpecMode有以下三种:
- UNSPECIFIED:父容器不作限制,一般用于系统内部。
- EXACTLY:精确模式,大小为SpecSize,对应LayoutParams中的match_parent和具体数值。
- AT_MOST:最大模式,大小不能大于SpecSize,对应于LayoutParams中的warp_content。
它的创建方法为:
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
//当API<=17时,使用旧的创建方式
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
而此方法主要由两种方式调用:getRootMeasureSpec()和getChildMeasureSpec()
getRootMeasureSpec()
用于DecorView确定MeasureSpec,主要由窗口尺寸和自身的LayoutParams共同决定。具体规则如下:
LayoutParams宽/高参数 | 生成的MeasureSpec |
---|---|
LayoutParams.match_parent和具体数值 | EXACTLY |
LayoutParams.warp_content | AT_MOST |
getChildMeasureSpec()
用于普通view确定MeasureSpec,主要由父容器的MeasureSpec和自身的LayoutParams共同决定。具体规则如下:
ChildLayoutParams\ParentSpecMode | EXACTLY | AT_MOST | UNSPEECIFIED |
---|---|---|---|
dp/px | EXACTLY childSize | EXACTLY childSize | EXACTLY childSzie |
match_parent | EXACTLY parentsize | AT_MOST parentSize | UNSPECIFIED 0 |
warp_content | AT_MOST parentsize | AT_MOST parentSize | UNSPECIFIED 0 |
measure
测量流程的入口为performMeasure():
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
可以在代码中看到,该方法调用了mView.measure(childWidthMeasureSpec, childHeightMeasureSpec),将传入的MeasureSpec传入的到mView的measure方法中。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//判断是否是Optical bound
boolean optical = isLayoutModeOptical(this);
//如果和父布局在Optical bound属性上不同则需要对MeasureSpec进行相应的处理
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// 通过传入的MeasureSpec生成相应的key
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
//mMeasureCache用于存储在特定的MeasureSpec(即上一行中的key)下的测量结果
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
//判断是否需要强制进行Layout
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// 判断是否需要测量布局,优化测量流程。
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// 清除PFLAG_MEASURED_DIMENSION_SET标志
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
//如果为forceLayout或者API<19时(mMeasureCache中无相应的key),走正常测量流程onMeasure()
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
//标志layout前不必再测量
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
//将缓存值读出并通过setMeasuredDimensionRaw存入相应的成员变量
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// 通过这个标志位判断重写onMeasure的时候是否调用了setMeasuredDimension()
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
//存储本次测量的MeasureSpec,用于下次测量比较
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
//通过上面的key存储对应的测量结果
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
在上述代码中可以看到,当需要测量且不从缓存中取出相应结果时,需要调用onMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
onMeasure方法中默认实现只是调用了setMeasuredDimension()方法,传入的参数是通过getDefaultSize方法获取相应的specSize,规则如下:若SpecMode为UNSPECIFIED则取SuggestedMinimum,否则取传入的specSize。其中SuggesttedMinmun为设置的minWidth/minHeight与background的大小的最大值。
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
可见,在setMeasuredDimension中仅对Optical bound进行了相应的处理后便调用了setMeasuredDimensionRaw方法。
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
在此处便是将相应的测量结果存入成员变量,并设置相应的标志位。
上述过程是单个view的绘制流程,在viewGroup中会有一些对于子view的处理方法:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
measureChildren的方法依次遍历所有子view并对Visibility不为Gone的view调用measureChild()方法
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
//根据父view的MeasureSpec和自身的LayoutParams生成相应的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
上述代码可见,生成了相应的Measure之后传入到了view的Measure方法中,而生成规则便是上文中普通view的MeasureSpec生成规则。
除此之外还有一个measureChildWithMargins()方法,与上面的measureChild方法类似,区别在于getChildMeasureSpec时的第二个参数加入Margin所占空间和父布局已经使用的空间。
layout
布局流程的入口为performLayout()方法:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
//调用mView的layout方法,传入四个参数分别代表该view离父布局的左、上、右、下的距离
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
//请求布局的个数
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
//获取仍然需要布局的view集合
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
//设置标志位,标志正在进行处理第二次布局请求
mHandlingLayoutInLayoutRequest = true;
// 需要进行布局的view的个数
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
//依次调用requestLayout()
view.requestLayout();
}
//对view树进行重新测量
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
//进行第二次布局
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//清除标志位,标志第二次布局请求结束
mHandlingLayoutInLayoutRequest = false;
// 获取还需要进行布局的view集合
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// 加入到下一个frame处理,防止无限循环
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
在performLayout()的过程中调用了DecorView的layout()方法,但是DecorView中未复写此方法,在viewGroup中直接调用了view.layout()方法:
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
//通过标志位判断是否需要测量,该标志位会在measure()方法中被清除
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//判断该view的父布局是否为Optical bound并进行相应的处理
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//调用onlayout进行布局
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
//进行layoutChange监听回调
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
在layout()方法中,经过一些判断后,通过调用onLayout方法进行布局,而在View.java中此方法的实现为空,而ViewGroup中此方法为抽象方法,查看一下FrameLayout的实现方法:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
在FrameLayout的onLayout方法调用了layoutChildren()方法:
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
//获取子view数量
final int count = getChildCount();
//获取FrameLayout的边界
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
//遍历所有的子view
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//对Visibility不为Gone的view进行操作
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//在measure阶段会对所有的子view进行测量,这里获取测量后的大小
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
//获取子view的gravity属性,未设置则设置为默认Gravity.TOP | Gravity.START
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
//获取Layout的方向RTL或则LTR
final int layoutDirection = getLayoutDirection();
//根据gravity(Start/End)和layoutDirection(RTL/LTR)确定水平方向排布方式的绝对值,例如RLT的START为Right,与LTR布局相反
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
//确定竖直方向排布方式
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
//根据不同的方向对子view的位置做不同的排布
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
//将处理后的位置信息传入子view的layout方法进行布局
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
Draw
结束了Layout流程后,就到了三部曲的最后一步绘制流程。入口为performDraw():
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
}
//将mFullRedrawNeeded存入局部变量并置为false,该值用于表示是否需要完全重新绘制
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
//mIsDrawing用于标识正在绘制
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
//调用draw方法并传入fullRedrawNeeded确定是否需要完全重新绘制
draw(fullRedrawNeeded);
} finally {
//正在绘制标志结束
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
// 省略部分关于渲染器的代码
......
}
在performDraw中调用了draw方法:
private void draw(boolean fullRedrawNeeded) {
//判断当前是存在有效的surface
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
if (DEBUG_FPS) {
trackFPS();
}
//加入绘制监听
if (!sFirstDrawComplete) {
synchronized (sFirstDrawHandlers) {
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++) {
mHandler.post(sFirstDrawHandlers.get(i));
}
}
}
//将界面滑动至所需位置
scrollToRectOrFocus(null, false);
//界面如果进行滑动,分发滑动监听事件
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
//根据滑动是否完成做出相应的处理
boolean animating = mScroller != null && mScroller.computeScrollOffset();
final int curScrollY;
if (animating) {
curScrollY = mScroller.getCurrY();
} else {
curScrollY = mScrollY;
}
//RootView滑动监听回调
if (mCurScrollY != curScrollY) {
mCurScrollY = curScrollY;
fullRedrawNeeded = true;
if (mView instanceof RootViewSurfaceTaker) {
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
}
}
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
int resizeAlpha = 0;
//获取需要绘制的区域
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating && mScroller != null) {
mScroller.abortAnimation();
}
return;
}
//当需要完全重新绘制时,将dirty的大小设置为整个屏幕大小
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(mTag, "Draw " + mView + "/"
+ mWindowAttributes.getTitle()
+ ": dirty={" + dirty.left + "," + dirty.top
+ "," + dirty.right + "," + dirty.bottom + "} surface="
+ surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
appScale + ", width=" + mWidth + ", height=" + mHeight);
}
//通知已注册的监听器,绘制过程即将开始
mAttachInfo.mTreeObserver.dispatchOnDraw();
int xOffset = -mCanvasOffsetX;
int yOffset = -mCanvasOffsetY + curScrollY;
final WindowManager.LayoutParams params = mWindowAttributes;
final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
if (surfaceInsets != null) {
xOffset -= surfaceInsets.left;
yOffset -= surfaceInsets.top;
// Offset dirty rect for surface insets.
dirty.offset(surfaceInsets.left, surfaceInsets.right);
}
//无障碍模式相关处理
boolean accessibilityFocusDirty = false;
final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
if (drawable != null) {
final Rect bounds = mAttachInfo.mTmpInvalRect;
final boolean hasFocus = getAccessibilityFocusedRect(bounds);
if (!hasFocus) {
bounds.setEmpty();
}
if (!bounds.equals(drawable.getBounds())) {
accessibilityFocusDirty = true;
}
}
mAttachInfo.mDrawingTime =
mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
//省略部分硬件加速相关处理的代码
......
//调用drawSoftware进行绘制
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
ViewRootImpl的draw()方法主要对view的滚动和硬件加速进行处理,而主要的绘制流程是drawSoftware()方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
//根据draw()中生成的dirty创建一个被锁定绘制区域的canvas
canvas = mSurface.lockCanvas(dirty);
// The dirty rectangle can be modified by Surface.lockCanvas()
//noinspection ConstantConditions
if (left != dirty.left || top != dirty.top || right != dirty.right
|| bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
}
//设置画布密度
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not lock surface", e);
// Don't assume this is due to out of memory, it could be
// something else, and if it is something else then we could
// kill stuff (or ourself) for no reason.
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
}
try {
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
+ canvas.getWidth() + ", h=" + canvas.getHeight());
//canvas.drawARGB(255, 255, 0, 0);
}
// 如果canvas图层有alpha则清除颜色
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
// 重置dirty
dirty.setEmpty();
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
if (DEBUG_DRAW) {
Context cxt = mView.getContext();
Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
", metrics=" + cxt.getResources().getDisplayMetrics() +
", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
}
try {
//设置canvas的偏离值
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
//调用DecorView的draw方法
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
} finally {
try {
//surface更新视图并释放canvas
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not unlock surface", e);
mLayoutRequested = true; // ask wm for a new surface next time.
//noinspection ReturnInsideFinallyBlock
return false;
}
if (LOCAL_LOGV) {
Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
}
}
return true;
}
由代码可以看出drawSoftware()主要是生成了一个画布,并通过调用mView.draw()进行view绘制,并更新到surface上。
DecorView的draw方法比较简单,在super.draw()后增加了mMenuBackground的draw。因此主要看View的Draw方法:
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
//通过标志位判断当前view是否是透明的
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* 这段注释中表明了绘制的步骤
* 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)
*/
int saveCount;
//当view不是透明的时候绘制背景
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) {
// 不透明时绘制view的内容,即调用onDraw
if (!dirtyOpaque) onDraw(canvas);
// 绘制子view,通过传递canvas,将绘制事件传递给子view
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// 绘制装饰物,前景滑动块等
onDrawForeground(canvas);
// we're done...
return;
}
//省略不常见情况的部分代码,流程与上述代码类似
......
}
按照上述的绘制步骤,第一步为绘制背景,即drawBackground():
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
//根据布局过程中获取的位置信息确定background的边界
setBackgroundBounds();
// Attempt to use a display list if requested.
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mHardwareRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
//获取偏移量,根据偏移量在不同位置绘制背景
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
第三步为绘制view的内容,即onDraw()方法,此方法为空实现,因为内容应由使用者决定其具体实现。
第四步绘制子view,即dispatchDraw()方法,在View.java中此方法为空实现,dispatchDraw()一般是针对存在子view的布局即ViewGroup,因此查看ViewGroup.dispatchDraw():
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
//省略部分动画相关的代码
.....
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
//对可见的View调用了drawChild方法
more |= drawChild(canvas, child, drawingTime);
}
}
//省略部分代码
}
在上述代码中调用了drawChild()方法,而此方法是调用View.draw(Canvas canvas, ViewGroup parent, long drawingTime)方法
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
//省略部分代码
......
//对画布进行裁剪
if (!drawingWithRenderNode) {
// apply clips directly, since RenderNode won't do it for this draw
if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
if (offsetForScroll) {
canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
} else {
if (!scalingRequired || cache == null) {
canvas.clipRect(0, 0, getWidth(), getHeight());
} else {
canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
}
}
}
if (mClipBounds != null) {
// clip bounds ignore scroll
canvas.clipRect(mClipBounds);
}
}
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
// Fast path for layouts with no backgrounds
//如果存在标志位PFLAG_SKIP_DRAW,则跳过本体的绘制,直接绘制其子view
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
//调用子View的draw方法,并将调整好的canvas传进去
draw(canvas);
}
}
} else if (cache != null) {
、、如果存在cache则直接利用cache
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
// no layer paint, use temporary paint to draw bitmap
Paint cachePaint = parent.mCachePaint;
if (cachePaint == null) {
cachePaint = new Paint();
cachePaint.setDither(false);
parent.mCachePaint = cachePaint;
}
cachePaint.setAlpha((int) (alpha * 255));
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
} else {
// use layer paint to draw the bitmap, merging the two alphas, but also restore
int layerPaintAlpha = mLayerPaint.getAlpha();
if (alpha < 1) {
mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
}
canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
if (alpha < 1) {
mLayerPaint.setAlpha(layerPaintAlpha);
}
}
}
//省略部分代码
......
}
此方法与上文中的draw方法不同,主要思想是会先检测缓存,如果没有缓存则调再调用上文中的draw(Canvas canvas)方法进行绘制或者直接通知子view进行绘制。
第六步,绘制装饰物:
public void onDrawForeground(Canvas canvas) {
//绘制滑动指示器
onDrawScrollIndicators(canvas);
//绘制滑动条
onDrawScrollBars(canvas);
//初始化foreground,并先顶起绘制范围
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
if (mForegroundInfo.mBoundsChanged) {
mForegroundInfo.mBoundsChanged = false;
final Rect selfBounds = mForegroundInfo.mSelfBounds;
final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
if (mForegroundInfo.mInsidePadding) {
selfBounds.set(0, 0, getWidth(), getHeight());
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
}
final int ld = getLayoutDirection();
Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
foreground.setBounds(overlayBounds);
}
//对foreground进行绘制
foreground.draw(canvas);
}
}
小结
之前学习之后,原以为能过较为轻松完成这篇总结笔记,开始写之后才发现之前学习时还是有一些地方没有完全弄懂,边记边学断断续续用了近一周的时间才完成这篇总结。
在写的过程中通过不断的查阅资料又发现了不少比较好的博文,并在文中进行了标注。
最后由于本人能力有限,如有谬误,请斧正,谢谢。
参考资料
Android走进Framework之AppCompatActivity.setContentView
Android Choreographer 源码分析
View绘制流程及源码解析(一)——performTraversals()源码分析
《Android开发艺术探索》