上篇文章描述了渲染android布局的源码分析view的绘制机制(一),分析了在Activity中onCreate()方法中执行setContentView()的内部实现机制,以及LayoutInflate对象对于布局进行pull,从而添加到父布局。做了这么多的铺垫,我们也需要对于view的绘制正式进入分析。
通过上篇的分析我们知道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()方法测量视图的大小的,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:
*
* - {@link android.view.View.MeasureSpec#UNSPECIFIED}
* - {@link android.view.View.MeasureSpec#EXACTLY}
* - {@link android.view.View.MeasureSpec#AT_MOST}
*
*
* Note: 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.
*
* @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的绘制机制(三)