view的绘制机制(二)

简介

上篇文章描述了渲染android布局的源码分析view的绘制机制(一),分析了在Activity中onCreate()方法中执行setContentView()的内部实现机制,以及LayoutInflate对象对于布局进行pull,从而添加到父布局。做了这么多的铺垫,我们也需要对于view的绘制正式进入分析。

目录
  1. ViewRootImpl流程分析
  2. 分析onMeasure()
ViewRootImpl流程分析

通过上篇的分析我们知道Activity的最上层的View是DecorView,我们所做的就是在它子布局中添加组件,从而引发了我们的一个思考,DecorView如何进行页面渲染呢?

当Activity初始化 Window和将布局添加到PhoneWindow的内部类DecorView类之后,ActivityThread类会调用handleResumeActivity方法将顶层视图DecorView添加到PhoneWindow窗口,来看看handlerResumeActivity方法的实现

final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume) {

            ...

            if (r.window == null && !a.mFinished && willBeVisible) {
                //获得当前Activity的PhoneWindow对象
                r.window = r.activity.getWindow();
                //获得当前phoneWindow内部类DecorView对象
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                //得当当前Activity的WindowManagerImpl对象
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    //标记根布局DecorView已经添加到窗口
                    a.mWindowAdded = true;
                    //将根布局DecorView添加到当前Activity的窗口上面
                    wm.addView(decor, l);

            ...

以上流程就是将PhoneWindow中的内部类DecorView通过ViewManager添加到Activity的窗口中,我们主要看 wm.addView(decor, l)方法,查看WindowManager的实现类WindowManagerImpl的源码:

    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        //其对象是WindowManagerGlobal对象
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

以上代码很简单,直接内部调用了WindowManagerGlobar的addView的方法:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {

        ...

        ViewRootImpl root;
        View panelParentView = null;

        ...
        //实例化ViewRootImpl对象
         root = new ViewRootImpl(view.getContext(), display);

        ...

        // do this last because it fires off messages to start doing things
        try {
            //将传进来的参数DecorView设置到root中
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
          ...
        }
    }

首先我们对ViewRootImpl对象进行实例化,进而调用其对象中的setView方法,将DecorView传入,我们继续去查看ViewRootImpl中的源码:

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
            //将顶层视图DecorView赋值给全局的mView
                mView = view;
            ...
            //标记已添加DecorView
             mAdded = true;
            ...
            //请求布局
            requestLayout();
            ...  
        }
 }

该方法实现有点长,直接看以上几行代码:,首先将DecorView复制给全局的mView,接着通过requestLayout()请求布局:

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
           //执行线程
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
        }
    }
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
     void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);

            try {
                performTraversals();
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
    }

以上代码主要是
1.在scheduleTraversals中会调用Choregrapher.postCallback,将它post出去,而postSyncBarrier方法禁止了后续的消息处理,一旦post出去了同步的Barrier之后,所有的非异步调用的消息就会被停止分发。
2.mHandler.getLooper().removeSyncBarrier(mTraversalBarrier),再调用performTraversals函数。删除掉一个同步的Barrier。源码中看,就是删除了一个以Message.arg1为mTraversalBarrier的Message。也就是说,会首先从MessageQueue中把mTraversalBarrier的这个Message删除,然后调用performTraversals()。

private void performTraversals() {  
..
//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.widthhe和lp.height表示DecorView根布局宽和高
 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

  // Ask host how big it wants to be
  //执行测量操作
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//执行布局操作
 performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//执行绘制操作
performDraw();

}


private int getRootMeasureSpec(int windowSize, int rootDimension) {  
    int measureSpec;  
    switch (rootDimension) {  
    case ViewGroup.LayoutParams.MATCH_PARENT:  
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);  
        break;  
    case ViewGroup.LayoutParams.WRAP_CONTENT:  
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);  
        break;  
    default:  
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);  
        break;  
    }  
    return measureSpec;  
}  

该方法主要流程就体现了View绘制渲染的三个主要步骤,分别是测量,布局,绘制三个阶段。

分析onMeasure()

onMeasure()方法测量视图的大小的,View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。
先来看看onMeasure()方法(View.java):

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
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);
    }

在onMeasure()方法中调用setMeasuredDimension()方法,对于view进行测量操作。先来看看getDefaultSize()方法处理了什么:

   public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

通过上面代码我们可以了解到:通过MeasureSpec获取到两个整型值,一个是测量模式,另外一个是测量尺寸。查看MeasureSpec的源码:

public static class MeasureSpec {
 private static final int MODE_SHIFT = 30;
 private static final int MODE_MASK = 0x3 << MODE_SHIFT;

 /**
 * Measure specification mode: The parent has not imposed any constraint
 * on the child. It can be whatever size it wants.
 */
 public static final int UNSPECIFIED = 0 << MODE_SHIFT;

 /**
 * Measure specification mode: The parent has determined an exact size
 * for the child. The child is going to be given those bounds regardless
 * of how big it wants to be.
 */
 public static final int EXACTLY = 1 << MODE_SHIFT;

 /**
 * Measure specification mode: The child can be as large as it wants up
 * to the specified size.
 */
 public static final int AT_MOST = 2 << MODE_SHIFT;

 /**
 * Creates a measure specification based on the supplied size and mode.
 *
 * The mode must always be one of the following:
 * <ul>
 * <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
 * <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
 * <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
 * </ul>
 *
 * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
 * implementation was such that the order of arguments did not matter
 * and overflow in either value could impact the resulting MeasureSpec.
 * {@link android.widget.RelativeLayout} was affected by this bug.
 * Apps targeting API levels greater than 17 will get the fixed, more strict
 * behavior.</p>
 *
 * @param size the size of the measure specification
 * @param mode the mode of the measure specification
 * @return the measure specification based on size and mode
 */
 public static int makeMeasureSpec(int size, int mode) {
 if (sUseBrokenMakeMeasureSpec) {
 return size + mode;
 } else {
 return (size & ~MODE_MASK) | (mode & MODE_MASK);
 }
 }

 /**
 * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
 * will automatically get a size of 0. Older apps expect this.
 *
 * @hide internal use only for compatibility with system widgets and older apps
 */
 public static int makeSafeMeasureSpec(int size, int mode) {
 if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
 return 0;
 }
 return makeMeasureSpec(size, mode);
 }

 /**
 * Extracts the mode from the supplied measure specification.
 *
 * @param measureSpec the measure specification to extract the mode from
 * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
 * {@link android.view.View.MeasureSpec#AT_MOST} or
 * {@link android.view.View.MeasureSpec#EXACTLY}
 */
 public static int getMode(int measureSpec) {
 return (measureSpec & MODE_MASK);
 }

 /**
 * Extracts the size from the supplied measure specification.
 *
 * @param measureSpec the measure specification to extract the size from
 * @return the size in pixels defined in the supplied measure specification
 */
 public static int getSize(int measureSpec) {
 return (measureSpec & ~MODE_MASK);
 }
...
}

MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而specSize是指在某种测量模式下的规格大小。
1. EXACTLY
表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,也可以按照自己的意愿设置成任意的大小。
2. AT_MOST
表示子视图最多只能是specSize中指定的大小,应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,也可以按照自己的意愿设置成任意的大小。
3. UNSPECIFIED
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。
总结:
1.一般view我们不需要重写onMeasure()方法,我们可以直接跟着view的测试流程走就可以了,当然子view继承view的时候,可以重写onMeasure()方法自己来定制。
例如:

 @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        setMeasuredDimension(300,300);  
    }  

这样的话就把View默认的测量流程覆盖掉了,不管在布局文件中定义MyView这个视图的大小是多少,最终在界面上显示的大小都将会是300*300。
2.如果我们需要处理wrap-content的话我们可以进行onMeasure()的重写,因为通过以上源码我们知道,specSize规格AT_MOST和EXACTLY都是根据父布局一样的大小,所以我们必须通过重新onMeasure()来对specSize()规格AT_MOST这个值进行设定指定的值。

需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。

以上就是关于onMeasure()的分析。接下来我们继续分析onLayout()和onDraw()view的绘制机制(三)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值