这段时间学习了下View的绘制流程,本着好记性不如烂笔头的原则,尝试将这些内容记录下来,用于巩固和总结。
这次学习的源码是基于Android SDK25来学习的,相比于之前版本的源码有了些许改变。
对于部分代码的功能和作用没有专门在正文中写出,而是以注释的形式写在了代码中。
流程可大致分为两个部分,首先是在Activity的onCreate阶段设置contentView,另一个阶段则是在Activity.onResume阶段后的绘制过程。
大致流程图如下:
刚开始学习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实例,这些实例之间是相互继承的关系
AppCompatDelegate.png最后我们在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放了进去。
在Activity的OnResume()方法运行后,ActivityThread.java会调用handleResumeActivity()方法,并在其中调用ViewManager的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;
- //设置一个同步分割符,用于t该消息之后的同学
- 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()中做了非常多的处理,这里我们只关注于绘制相关的三大流程,简化代码如下:
- 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是一个十分特殊的类,它用于确定view的测量规格,代表了一个32位的int值,由SpecMode(高2位)+SpecSize(低30位)两部分组成。 SpecMode有以下三种:
它的创建方法为:
- 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()
用于DecorView确定MeasureSpec,主要由窗口尺寸和自身的LayoutParams共同决定。具体规则如下:
LayoutParams宽/高参数 | 生成的MeasureSpec |
---|---|
LayoutParams.match_parent和具体数值 | EXCATLY |
LayoutParams.warp_content | AT_MOST |
用于普通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 |
测量流程的入口为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所占空间和父布局已经使用的空间。
布局流程的入口为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);
- }
- }
- }
结束了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开发艺术探索》
来源: https://juejin.im/entry/5a0d56ef6fb9a0452a3be091