View绘制流程及原理

前言

Q:View在什么时候添加到屏幕上的?

A: 经过setContentView流程后,将xml文件添加到DecorView

Q:DecorView 是什么时候添加到 Window 上的?

A:在onResume之后

需要看View绘制的重点看performTraversals就行。其他的方法根据需求来查看

handleResumeActivity

@ActivityThread.java

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {
    ...
     // TODO Push resumeArgs into the activity for consideration
        final ActivityClientRecord r = performResumeActivity(token,finalStateRequest, reason);//主要用来onResume
    wm.addView(decor, l);
    ...
      
}

performResumeActivity

@VisibleForTesting
public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,String reason) {
    r.activity.performResume(r.startsNotResumed, reason);
}

performResume

final void performResume(boolean followedByPause, String reason) {
    mInstrumentation.callActivityOnResume(this);
}
callActivityOnResume
public void callActivityOnResume(Activity activity) {
    activity.mResumed = true;
    activity.onResume();//!!!!!!!
    
    if (mActivityMonitors != null) {
        synchronized (mSync) {
            final int N = mActivityMonitors.size();
            for (int i=0; i<N; i++) {
                final ActivityMonitor am = mActivityMonitors.get(i);
                am.match(activity, activity, activity.getIntent());
            }
        }
    }
}

wm.addView(decor, l)

@ WindowManagerImpl.java

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
            mContext.getUserId());
}

mGlobal.addView(!!!)

@WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {
            root = new ViewRootImpl(view.getContext(), display);
            mViews.add(view);//DecorView
            mRoots.add(root);//ViewRootImpl
            mParams.add(wparams);//WindowManager.LayoutParams   
            root.setView(view, wparams, panelParentView, userId);
        }

WindowManagerImpl、WindowManagerGlobal、ViewRootImpl

WindowManagerImpl:确定 View 属于哪个屏幕,哪个父窗口
WindowManagerGlobal:管理整个进程 所有的窗口信息
ViewRootImpl:WindowManagerGlobal 实际操作者,操作自己的窗口

ViewRootImpl.java(!!!)

根据上面的

 root.setView(view, wparams, panelParentView, userId);

来到这个类.

setView

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {
       requestLayout();// 请求遍历
       res = mWindowSession.addToDisplayAsUser// 将窗口添加到WMS上面WindowManagerService
       view.assignParent(this); // getParent  ViewRootImpl
}

requestLayout()

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();//用来检查当前线程是否是主线程(是否是当前线程使用的ViewRootImpl)
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
checkThread
void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}
scheduleTraversals
@UnsupportedAppUsage
void scheduleTraversals() {
     mChoreographer.postCallback(  Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
 }
}mTraversalRunnable
final class TraversalRunnable implements Runnable {
 @Override
public void run() {
  doTraversal();
}
}
doTraversal()
void doTraversal() {
if (mTraversalScheduled) {
 mTraversalScheduled = false;
  performTraversals();//绘制view
}
}

performTraversals(!!!)绘制view

@ViewRootImpl.java

private void performTraversals() {
    //这个mView是通过setView方法传进来的,也就是Activity的根布局DecorView,使用final修饰,以防在遍历过程中被修改
        final View host = mView;

        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals");
            host.debug();
        }
        //mAdded在setView方法修改为true,表明Decorview已经添加到了PhoneWindow
        if (host == null || !mAdded)
            return;
        //是否正在遍历
        mIsInTraversal = true;
        //是否需要马上绘制
        mWillDrawSoon = true;
        //视图大小改变
        boolean windowSizeMayChange = false;
        //新视图
        boolean newSurface = false;
        //视图改变
        boolean surfaceChanged = false;
        WindowManager.LayoutParams lp = mWindowAttributes;

        //Activity窗口的宽度和高度
        int desiredWindowWidth;
        int desiredWindowHeight;
		//DecorView是否可见
        final int viewVisibility = getHostVisibility();
        final boolean viewVisibilityChanged = !mFirst
                && (mViewVisibility != viewVisibility || mNewSurfaceNeeded);

        WindowManager.LayoutParams params = null;
        if (mWindowAttributesChanged) {
            mWindowAttributesChanged = false;
            surfaceChanged = true;
            params = lp;
        }
        CompatibilityInfo compatibilityInfo =
                mDisplay.getDisplayAdjustments().getCompatibilityInfo();
        if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
            params = lp;
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            if (mLastInCompatMode) {
                params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
                mLastInCompatMode = false;
            } else {
                params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
                mLastInCompatMode = true;
            }
        }

        mWindowAttributesChangesFlag = 0;
		//用来保存窗口宽度和高度,来自于全局变量mWinFrame,这个mWinFrame保存了窗口最新尺寸
        Rect frame = mWinFrame;
        //构造方法里mFirst赋值为true,意思是第一次执行遍历吗	
        if (mFirst) {
        	//是否需要重绘
            mFullRedrawNeeded = true;
            //是否需要重新确定Layout
            mLayoutRequested = true;
            //判断要绘制的窗口是否包含状态栏,有就去掉,然后确定要绘制的Decorview的高度和宽度
            if (shouldUseDisplaySize(lp)) {
                // NOTE -- system code, won't try to do compat mode.
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {
                //宽度和高度为整个屏幕的值
                Configuration config = mContext.getResources().getConfiguration();
                desiredWindowWidth = dipToPx(config.screenWidthDp);
                desiredWindowHeight = dipToPx(config.screenHeightDp);
            }

            /**
             * 因为第一次遍历,View树第一次显示到窗口
             * 然后对mAttachinfo进行一些赋值
             * AttachInfo是View类中的静态内部类AttachInfo类的对象
             * 它主要储存一组当View attach到它的父Window的时候视图信息
             */
            mAttachInfo.mUse32BitDrawingCache = true;//使用32位绘图缓存
            mAttachInfo.mHasWindowFocus = false;//视图窗口当前不具有焦点。
            mAttachInfo.mWindowVisibility = viewVisibility;//当前窗口不可见
            mAttachInfo.mRecomputeGlobalAttributes = false;//ViewAncestor应在下次执行遍历时触发全局布局更改
            mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
            // 如果之前未设置布局方向,则设置布局方向(View.LAYOUT_DIRECTION_INHERIT 是默认值)
            if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
                host.setLayoutDirection(mLastConfiguration.getLayoutDirection());
            }
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
            dispatchApplyInsets(host);
            //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);

        } else {
         		//如果不是第一次进来这个方法,它的当前宽度和高度就从之前的mWinFrame获取
            desiredWindowWidth = frame.width();
            desiredWindowHeight = frame.height();
            /**
             * mWidth和mHeight是由WindowManagerService服务计算出的窗口大小,
             * 如果这次测量的窗口大小与这两个值不同,说明WMS单方面改变了窗口的尺寸
             */
            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
                //需要进行完整的重绘以适应新的窗口尺寸
                mFullRedrawNeeded = true;
                //需要对控件树进行重新布局
                mLayoutRequested = true;
                //window窗口大小改变
                windowSizeMayChange = true;
            }
        }
       
		//如果窗口可见性变了,进行判断
        if (viewVisibilityChanged) {
            mAttachInfo.mWindowVisibility = viewVisibility;
            host.dispatchWindowVisibilityChanged(viewVisibility);
            host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
            if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
            	//不可见就结束调整大小事件,释放相关硬件资源
                endDragResizing();
                destroyHardwareResources();
            }
            //如果窗口不可见,就修改标志位
            if (viewVisibility == View.GONE) {
                // After making a window gone, we will count it as being
                // shown for the first time the next time it gets focus.
                mHasHadWindowFocus = false;
            }
        }

        // 如果窗口不可见了,去掉可访问性焦点
        if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
            host.clearAccessibilityFocus();
        }

        /**
         * 执行HandlerActionQueue中HandlerAction数组保存的Runnable
         * 我们平时会通过View.post()或View.postDelayed()方法将一个Runnable对象发送到主线程执行
         * 其实就是通过这个mHandler去执行
         * public void executeActions(Handler handler) {
             synchronized (this) {
                 final HandlerAction[] actions = mActions;
                 for (int i = 0, count = mCount; i < count; i++) {
                     final HandlerAction handlerAction = actions[i];
                     handler.postDelayed(handlerAction.action, handlerAction.delay);
                 }
                 mActions = null;
                 mCount = 0;
             }
           }
         */
        getRunQueue().executeActions(mAttachInfo.mHandler);

        boolean insetsChanged = false;

        boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
        //进行预测量
        if (layoutRequested) {

            final Resources res = mView.getContext().getResources();

            if (mFirst) {
                // 视图窗口当前是否处于触摸模式。
                mAttachInfo.mInTouchMode = !mAddedTouchMode;
                //确保这个Window的触摸模式已经被设置
                ensureTouchModeLocally(mAddedTouchMode);
            } else {
            		/**
                 * 判断一下几个insects的值和上一次相比有没有什么变化,不同的话就改变insetsChanged
                 * mOverscanInsets 记录屏幕中的 overscan 区域               见贴图中相应描述区域
                 * mContentInsets  记录了屏幕中的控件在布局时必须预留的空间   见贴图中相应描述区域
                 * mStableInsets   记录了Stable区域,比mContentInsets区域大  见贴图中相应描述区域
                 * mVisibleInsets  记录了被遮挡的区域,如正在进行输入的TextView等不被遮挡,这样VisibleInsets的变化并不会导致重新布局,
                 *                 所以这里仅仅是将VisibleInsets保存到mAttachInfo中,以便绘制时使用
                 */
                if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
                    insetsChanged = true;
                }
                if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
                    insetsChanged = true;
                }
                if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
                    insetsChanged = true;
                }
                if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
                    if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                            + mAttachInfo.mVisibleInsets);
                }
                if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
                    insetsChanged = true;
                }
                if (mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar) {
                    insetsChanged = true;
                }
                 /**
                 * 如果当前窗口的根布局的width或height被指定为WRAP_CONTENT时,
                 * 比如Dialog,那我们还是给它尽量大的长宽,这里是将屏幕长宽赋值给它
                 */
                if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
                        || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    windowSizeMayChange = true;
 					//判断要绘制的窗口是否包含状态栏,有就去掉,然后确定要绘制的Decorview的高度和宽度
                    if (shouldUseDisplaySize(lp)) {
                        // NOTE -- system code, won't try to do compat mode.
                        Point size = new Point();
                        mDisplay.getRealSize(size);
                        desiredWindowWidth = size.x;
                        desiredWindowHeight = size.y;
                    } else {
                        Configuration config = res.getConfiguration();
                        desiredWindowWidth = dipToPx(config.screenWidthDp);
                        desiredWindowHeight = dipToPx(config.screenHeightDp);
                    }
                }
            }

            // 进行预测量窗口大小,以达到更好的显示大小
            windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
        }
 windowSizeMayChange |= measureHierarchy(); // 预测量
 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); // 布局窗口
 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // 控件树测量
 performLayout(lp, mWidth, mHeight); // 布局
 performDraw(); // 绘制
}

measureHierarchy(!!!)预测量

通过上面的代码可以看到desiredWindowWidth和desiredWindowHeight是经过一系列代码考核得到的值,讲道理整个View树是可以按照这个值去测量的,但是我们不仅只是测量和布局,还要尽可能舒适的一个UI去展示给用户

比如在大屏幕上,Dialog的width修饰为WRAP_CONTENT,按照上面的代码,其实desiredWindowWidth还是给了尽量大的值,就是屏幕宽度;但是Dialog可能就是为了显示几个字,那结果就是整个dialog的布局就被拉伸铺满屏幕;显然这种UI是不美丽的,那就需要通过measureHierarchy方法去优化,尝试下更小的宽度是否合适。就这样来到了measureHierarchy方法。

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        //用于描述宽度的MeasureSpec
        int childWidthMeasureSpec;
        //用于描述高度的MeasureSpec
        int childHeightMeasureSpec;
        //表示测量结果是否可能导致窗口的尺寸发生变化
        boolean windowSizeMayChange = false;

        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
                "Measuring " + host + " in display " + desiredWindowWidth
                + "x" + desiredWindowHeight + "...");
        //表示测量是否能满足控件树充分显示内容的要求
        boolean goodMeasure = false;
        //其实测量协商仅仅发生在width被指定为WRAP_CONTENT情况
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            // 进入到这里就让窗口的大小不以屏幕大小去布局,而是给一个固定的值看看是否合适
            final DisplayMetrics packageMetrics = res.getDisplayMetrics();
            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
            int baseSize = 0;
            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                //取出一个固定值来用,来自于config_prefDialogWidth
                baseSize = (int)mTmpValue.getDimension(packageMetrics);
            }
            if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
                    + ", desiredWindowWidth=" + desiredWindowWidth);

            //假设上面config_prefDialogWidth值是320dp
            if (baseSize != 0 && desiredWindowWidth > baseSize) {
                //使用getRootMeasureSpec()函数组合SPEC_MODE与SPEC_SIZE为一个MeasureSpec
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                //第一次测量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                        + host.getMeasuredWidth() + "," + host.getMeasuredHeight()
                        + ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
                        + " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));

                /**
                 * 控件树的测量结果可以通过mView的getmeasuredWidthAndState()方法获取。
                 * 如果控件树对这个测量结果不满意,则会在返回值中添加MEASURED_STATE_TOO_SMALL位
                 */
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    goodMeasure = true;
                } else {
                    /**
                     * 走到这里说明上面的尺寸不适合View树
                     * 需要对宽度再进行放松,使用上面的预期值与最大值的平均值作为新的宽度
                     */
                    baseSize = (baseSize+desiredWindowWidth)/2;
                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
                            + baseSize);
                    //重新获取MeasureSpec
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                    //第二次测量
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                            + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                    //再次判断View树对新的结果是否满意
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                        goodMeasure = true;
                    }
                }
            }
        }

        /**
         * 如果上面两次尝试测量结果,View树都不满意
         * 那老哥也没办法了,做不了优化了,只能以屏幕宽高去给View树测量了
         * 这是第三次也是最后一次测量了
         */
        if (!goodMeasure) {
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            //如果测量得出的结果与当前窗口值不一样,就需要调整窗口大小了
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
        }

        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals -- after measure");
            host.debug();
        }

        return windowSizeMayChange;
    }

这里会进行至多三次测量:

可以看到这个方法进行了两次优化处理,如果两次优化处理还是不满意,就使用给定的desiredWindowWidth/desiredWindowHeight进行测量;这里也表明了一点,performMeasure方法可能会被调用多次,那onMeasure()方法同样会被回调多次,这样我们在自定义View的时候,就不要在这个回调方法里做过多的new内存操作。

MeasureSpec

注意到方法里会调用到getRootMeasureSpec这么一个方法,说到这个方法就得先谈下MeasureSpec这个类,它是View类的一个静态内部类:

  • MeasureSpec封装了从父级传递给子级的布局要求
  • 每个MeasureSpec代表宽度或高度的要求
  • MeasureSpec由大小和模式组成。 有三种可能

1.UNSPECIFIED :父View没有对子View做任何限制,子View可以是自己想要的尺寸;像我们平时在xml中写View的时候没有设置宽高,这种情况比较少见。它的值是0 << 30 = 0
2.EXACTLY:父View决定了子View确切大小,子View将被限定在给定的边界里;像xml中子view如果是填充父窗体(match_parent)或者确定大小,说明父View已经明确知道子控件想要多大的尺寸了。它的值是1 << 30 = 1073741824
3.AT_MOST:子View可以是它所期望的尺寸,但是不能大于specSize;在布局设置wrap_content,父控件并不知道子控件到底需要多大尺寸(具体值), 需要子控件在measure测量之后再让父控件给他一个尽可能大的尺寸以便让内容全部显示;如果在onMeasure没有指定控件大小,默认会填充父窗体,因为在view的measure源码中, AT_MOST(相当于wrap_content )和EXACTLY (相当于match_parent )两种情况返回的测量宽高都是specSize, 而这个specSize正是父控件剩余的宽高,所以默认measure方法中wrap_content 和match_parent 的效果是一样的,都是填充剩余的空间。它的值是2 << 30 = -2147483648

所以这个类是SPEC_SIZE和SPEC_MODE的组合,结构如下

SPEC_MODE(32,31) - SPEC_SIZE(30,…,1)

也就是高两位表示模式(在父View中是如何定义的),低30位表示大小(父View的建议尺寸)

再来看下performMeasure方法,它有两个参数,一个是包含View的宽度度量规范,一个是包含View的高度度量规范,然后根据这两个宽高的度量规范去测量View所需的大小;那这两个信息怎么来呢,就是通过getRootMeasureSpec获取,这个方法会调用MeasureSpec类的makeMeasureSpec方法,根据给定的大小和模式返回一个度量规范

getRootMeasureSpec

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {

    case ViewGroup.LayoutParams.MATCH_PARENT:
        // Window can't resize. Force root view to be windowSize.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // Window can resize. Set max size for root view.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        // Window wants to be an exact size. Force root view to be that size.
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

既然宽高的度量规范出来了,那就开始测量了,调用performMeasure方法

performMeasure(!!!)控件树测量

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

这里会直接调用View的measure方法

measure

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {    
//判断当前View是否是以可见边界布局的ViewGroup
    boolean optical = isLayoutModeOptical(this);
    //对度量规范进行校正
    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);
    }

    // Suppress sign extension for the low bytes
    long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
    if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

    // Optimize layout by avoiding an extra EXACTLY pass when the view is
    // already measured as the correct size. In API 23 and below, this
    // extra pass is required to make LinearLayout re-distribute weight.
    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标记用于检查控件在onMeasure()方法中是否通过
        //调用setMeasuredDimension()将测量结果存储下来
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
        //解析所有与RTL相关的属性,比如背景,字体,padding等属性设置
        resolveRtlPropertiesIfNeeded();

        int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            //对自己进行测量, 每个View子类都需要重写这个方法以便正确地对自身进行测量
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            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;
        }

        /**
         * 检查View子类的onMeasure()是否调用了setMeasuredDimension()
         * setMeasuredDimension()会将PFLAG_MEASURED_DIMENSION_SET标记重新加入mPrivateFlags中。
         * 之所以做这样的检查,是由于onMeasure()的实现可能由开发者完成,
         * 而在Android看来,开发者是不可信的
         */
        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;

    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
            (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

可以看到其实这个方法并没有做任何的测量工作,真正的测量过程交给了onMeasure方法去做,它的作用在于引发onMeasure()的调用,并对onMeasure()行为的正确性进行检查。另外,在控件系统看来,一旦控件执行了测量操作,那么随后必须进行布局操作,因此在完成测量之后,将PFLAG_LAYOUT_REQUIRED标记加入mPrivateFlags,以便View.layout()方法可以顺利进行

onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

可以看到这个方法实现很简单,只调用了setMeasuredDimension()方法保存测量结果,具体的实现由子类去重写,提供更加合理、高效的实现

setMeasuredDimension

必须通过onMeasure调用此方法来存储测量的宽度和测量的高度。如果不这样做,将在测量时触发异常

 
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);
    }

    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        //保存测量的宽度
        mMeasuredWidth = measuredWidth;
        //保存测量的高度
        mMeasuredHeight = measuredHeight;
        //向mPrivateFlags中添加PFALG_MEASURED_DIMENSION_SET,以此证明onMeasure()保存了测量结果
        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

performLayout(!!!)布局

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
   host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}

然后进入view.java 的layout方法

layout

public void layout(int l, int t, int r, int b) {
onLayout(changed, l, t, r, b);
}

进入 onLayout方法

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

但发现onLayout方法是空的,这说明,此方法具体实现是在具体的view里面实现的

接下来分别看一下TextView和FrameLayout的onLayout方法

TextView.onLayout
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    if (mDeferScroll >= 0) {
        int curs = mDeferScroll;
        mDeferScroll = -1;
        bringPointIntoView(Math.min(curs, mText.length()));
    }
    // Call auto-size after the width and height have been calculated.
    autoSizeText();
}

很简单,直接交给了super即view进行处理,原因很简单:对于单个view(没有子view)来说,只要按照父view传给的ltrb(left、top、right、bottom layout的四个参数)进行处理就好了,并不需要自己本身去计算

FrameLayout.onLayout
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
  void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();

        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();

        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;

                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }

                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

                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;
                }

                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

逻辑很清楚,遍历child,获得child的measureWidth和measureHeight和layoutparam,根据gravity计算childLeft和childTop,另外两个参数直接通过width和height计算获得。最后调用child.layout(View.java里的layout方法)进行处理

注意!!

如果是View:加上自己的 Padding
如果是容器: 加上孩子的 Margin

performDraw(!!!)绘制

private void performDraw() {
boolean canUseAsync = draw(fullRedrawNeeded);
}

draw(ViewRootImpl.java)

private boolean draw(boolean fullRedrawNeeded) {
scrollToRectOrFocus(null, false);//平时在输入时弹出输入框后用来scroll
 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);//硬件加速绘制-效果更好
 drawSoftware//软件绘制
 
  private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
            mView.draw(canvas);
            }
}

draw(View.java)
public void draw(Canvas canvas) {
    一般的绘制步骤:1.绘制背景 2.如果需要,保存canvas来为绘制渐变做准备 3.绘制view 的内容 4.绘制子childrend 5.如果需要,绘制边界渐变和重载图层 6.绘制装饰层(例如进度条)。
    /*
         * 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)
         *      7. If necessary, draw the default focus highlight
         */
      
           // Step 3, draw the content
            onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);
}
dispatchDraw
 protected void dispatchDraw(Canvas canvas) {
 more |= drawChild(canvas, child, drawingTime);
 }

 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);//回到View.java的draw方法
    }
onDraw

@ LinearLayout.java

@Override
protected void onDraw(Canvas canvas) {
    if (mDivider == null) {
        return;
    }

    if (mOrientation == VERTICAL) {
        drawDividersVertical(canvas);
    } else {
        drawDividersHorizontal(canvas);
    }
}

快速浏览

handleResumeActivity

handleResumeActivity @ActivityThread.java
–> performResumeActivity
–> r.activity.performResume
–> mInstrumentation.callActivityOnResume
–> wm.addView(decor, l); (WindowManagerImpl.java)

–> WindowManagerGlobal.addView
–> root = new ViewRootImpl(view.getContext(), display);
–> mViews.add(view); // DecorView
mRoots.add(root); // ViewRootImpl
mParams.add(wparams); // WindowManager.LayoutParams
–> root.setView(view, wparams, panelParentView, userId);

WindowManagerImpl:确定 View 属于哪个屏幕,哪个父窗口
WindowManagerGlobal:管理整个进程 所有的窗口信息
ViewRootImpl:WindowManagerGlobal 实际操作者,操作自己的窗口

ViewRootImpl

ViewRootImpl.setView
–> requestLayout(); // 请求遍历
–> scheduleTraversals
–> mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL,mTraversalRunnable, null);
–> doTraversal
–> performTraversals(); // 绘制View
–> res = mWindowSession.addToDisplayAsUser // 将窗口添加到WMS上面 WindowManagerService
–> 事件处理
–> view.assignParent(this); // getParent ViewRootImpl

performTraversals

performTraversals@ViewRootImpl.java
–> windowSizeMayChange |= measureHierarchy(); // 预测量
–> relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); // 布局窗口
–> performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // 控件树测量
–> performLayout(lp, mWidth, mHeight); // 布局
–> performDraw(); // 绘制

总结

1.首先就是performMeasure的过程,这个方法会调用到View类的measure方法,这里并没有具体的测量实现,而是调用到了onMeasure,需要子类去实现;要知道DecorView其实就是个FrameLayout,是一个ViewGroup,重写onMeasure方法,去循环遍历子View的measure过程;根据自己的MeasureSpec和子View的LayoutParams来决定子View的测量规格,根据获得的测量规格得到子view的宽高,然后一层一层向下遍历,子类也会重写onMeasure方法,测量自己的大小

2.这样所有的View测量结束,第二步就是performLayout了,默认是调用到了View类layout方法;如果是ViewGroup,就调用ViewGroup的layout方法,但是这个方法没有具体实现,还是调用到了View类的layout方法;View类的layout方法回调onLayout方法,要怎么确定位置由子类重写onlayout方法实现;这里DecorView去重写,自己实现,循环调用每个子View的layout方法,将上下左右坐标值传递给子View,确定子View的位置

3.接下来就是最后的绘制过程了,也就是第三步performDraw,继续走到ViewTreeObserver的dispatchOnDraw方法,通知所有注册了OnDrawListener接口的View去调用自己的onDraw()方法,绘制自己;DecorView因为是个ViewGroup,重写了dispatchDraw方法,循环调用每个子View的draw方法;最后就是每个子View重写onDraw方法绘制自身

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值