ActivityThread.handleResumeActivity
1 、View绘制的三大过程
//View绘制的三大过程开始位置
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
// 将DecorView添加到Window上,紧接着进入绘制三大过程,实际上是调用WindowManagerImpl的addView方法,然后调用WindowManagerGlobal的addView方法。
// 出发绘制的单打过程的条件是:当DecorView被添加到Window中时。
wm.addView(decor, l);
}
WindowManagerImpl.addView
// 参数view是顶层视图(DecorView)
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerGlobal
// 参数view是顶层视图(DecorView)
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
View panelParentView = null;
root = new ViewRootImpl(view.getContext(), display);
// view就是顶层视图(DecorView)
view.setLayoutParams(wparams);
// 将View和和ViewRooot已将View的 params收集到容器中管理
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
}
}
小结
我省略了一些不重要的代码,触发View的绘制过程的条件是ActivityTherad.handleResumeActivity方法开始将DecorView添加到Window上时,紧接着在WindowManagerGlobal的addView方法中创建ViewRootImpl 对象并调用ViewRootImpl的setView方法,将顶层视图(DecorView)做参数传入,进入绘制三大过程,实际上是调用WindowManagerImpl的addView方法,然后调用WindowManagerGlobal的addView方法。触发View绘制三大过程的条件是:当DecorView被添加到Window中时,上面最后一步提到调用ViewRootimpl的setView并传入DecorView,那么我们看一下ViewRootImpl.setView方法做了什么?
ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
requestLayout();
}
我们看到setView方法里面调用了requestLayout方法,代码如下:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
在requestLayout方法中又调用 checkThread(),看看就知道检查当前线程,看一下代码:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
你现在应该知道为什么不能再工作线程中更新UI了吗?当然你要是Activity的onResume之前是可以在工作线程更新UI的,我们看到在requestLayout方法中还调用checkThread结束还调用scheduleTraversals方法,点击去看看代码:
void scheduleTraversals() {
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
在scheduleTraversals方法中其他的不要看,你就mTraversalRunnable代码。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
在TraversalRunnable 中又调用doTraversal方法。
void doTraversal() {
performTraversals();
}
可以看到在doTraversal方法中调用performTraversals方法。
private void performTraversals() {
final View host = mView;
// 因为顶层View,这里需要根据窗口的宽高以及View自身的LayoutParams计算MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// performMeasure 测量
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
//再次测量的标志
boolean measureAgain = false;
//有权重,就会被测量两次,为了性能,想线性布局中使用Weight
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
//有权重,就会被测量两次,为了性能,想线性布局中使用Weight
// performMeasure
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// performLayout
performLayout(lp, mWidth, mHeight);
// performDraw
performDraw();
}
可以看到在performTraversals方法中又分别调用:performMeasure(测量)、performLayout(布局)和performDraw(绘制),执行了View的绘制三大过程,那么接下来我们分别介绍这些过程。
2、 测量(performMeasure)
首先看看performTraversals方法在执行performMeasure之前做的测量准备,代码如下;
// 因为顶层View,这里需要根据窗口的宽高以及View自身的LayoutParams计算MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// performMeasure 测量
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
//再次测量的标志
boolean measureAgain = false;
//有权重,就会被测量两次,为了性能,想线性布局中使用Weight
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
//有权重,就会被测量两次,为了性能,想线性布局中使用Weight
// performMeasure
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
首先我知道测量时需要MeasureSpec,所以在performTraversals方法中执行performMeasure方法之前,即在测量之前需要准备MeasureSpec,具体代码:
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
我们看看那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;
}
可以看到在创建DecorView的MeasureSpec时,需要根据自己的LayoutParams和Parent的测量模式(model)最终决定DecorView的MeasureSpec。
2.1 、MeasureSpec的介绍
模式(Mode) + 尺寸(Size)->MeasureSpec 32位int值
00000000 00000000 00000000 00000000
SpecMode(前2位) + SpecSize(后30)
mode + size --> MeasureSpec
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
父容器不对View做任何限制,系统内部使用
public static final int UNSPECIFIED = 0 << MODE_SHIFT; 00000000 00000000 00000000 00000000
父容器检测出View的大小,Vew的大小就是SpecSize LayoutPamras match_parent 固定大小
public static final int EXACTLY = 1 << MODE_SHIFT; 01000000 00000000 00000000 00000000
父容器指定一个可用大小,View的大小不能超过这个值,LayoutPamras wrap_content
public static final int AT_MOST = 2 << MODE_SHIFT; 10000000 00000000 00000000 00000000
由上述可知MeasureSpe封装了测量model和size,而前2位表示SpecMode,后30位表示(SpecSize),MeasureSpe中还定义了三种MeasureSpe,分别是:
- UNSPECIFIED 父容器不对View做任何限制,系统内部使用,当然你在ScrollView中也是可以看到的。
- EXACTLY 父容器检测出View的大小,Vew的大小就是SpecSize LayoutPamras.match_parent 固定大小
- AT_MOST 父容器指定一个可用大小,View的大小不能超过这个值,LayoutPamras.wrap_conten
在MeasureSpe中定义了从MeasureSpe获取model和size的方法,我们就不看了。
2.2、performMeasure方法
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
try {
//调用DecorView的measure方法进行调度测量,那么DecorView是继承了FrameLayout
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
可以看到performMeasure方法中直接调用了View.measure,而这个measure方法是View的测量调度方法:
2.2.1 默认的情况
//View的measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
/**
* 没有缓存直接调用onMeasure进行测量操作
* onMeasure -> setMeasuredDimension -> setMeasuredDimensionRaw(保存测量的结果)
*/
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
// 测量自己,这应该将mPrivateFlags设置为PFLAG_MEASURED_DIMENSION_SET标志回传
// 不复写使用默认大小,因为目前是针对的DecorView,所以我们要看DecorView的OnMeasure的实现,即Framelayout
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;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
/**
* 在setMeasuredDimensionRaw中设置 将mPrivateFlags为了PFLAG_MEASURED_DIMENSION_SET标志
*/
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;
}
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//不复写使用默认大小
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
为了简洁我把一些优化的算法给删掉,在measure方法中又调用onMeasure测量自己,然后调用setMeasuredDimension和setMeasuredDimensionRaw 方法保存测量结果,并将mPrivateFlags设置为PFLAG_MEASURED_DIMENSION_SET标志回传,如果你不复写View.onMeasure方法,就使用默认大小,如果你自定义View,不复写onMeasure方法,最后你会发现:wrap_content 和 match_parent的效果是一样的,可以进去看看onMeasure源码,因为目前是针对的DecorView,所以我们要看DecorView的OnMeasure的实现,即Framelayout,当你需要自定义Viewroup时你需要根据child的布局属性来测量自己的宽和高,而当你自定义View时,你仅仅只需要测量自己就可以了,最后总结:
ViewGroup : measure --> onMeasure(测量子控件的宽高measureChild)--> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的宽高)
View measure --> onMeasure(测量自己) --> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的宽高)
最后我们来看看FrameLayout的onMeasure是怎么实现的:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
// GONE 是不进行测量的
if (mMeasureAllChildren || child.getVisibility() != GONE) {
/**
* 调用measureChildWithMargins测量Child
*/
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
/**
* 根据FrameLayout的布局特点,为了计算自己的大小,它只需要计算最大的宽度和高度
*/
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
//设置测量结果,进行回传
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
// 测量child measureChildWithMargins方法
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到在FrameLayout.onMeasure方法中是通过循环遍历所有的child,来决定自己的最终宽度和高,那么DecorView是继承FrameLayout的,所以这也就是DecorView的测量规范,由measureChildWithMargins方法得知Child的ModeSpec是由parentWidthMeasureSpec 和 Child的LayoutParam决定,然后在顶用child.measure方法将计算出来的ModeSpec传给Child,最终会回调到child的onMeasure方法中。
3、布局 performLayout
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
//View和ViewGroup 也是会调用的onLayout,layout方法是摆放自己,而onLayout是根据自己的要求摆放child的
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
顶层视图(DecorView)host调用layout方法摆放自己
public void layout(int l, int t, int r, int b) {
// onLayout
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
}
}
在layouyt方法中有会调用onLayout,对child进行摆放,不管是View还是ViewGroup都会回调onLayout方法,但是onLayout方法是对child进行摆放的,在View中是空实现,你自定义实现了也没有实际的意义,所以layout的过程是比较简单的,接下来我们看看performDraw方法,
ViewGroup.layout(来确定自己的位置,4个点的位置) -->onLayout(进行子View的布局)
View.layout(来确定自己的位置,4个点的位置)。
4、绘制performDraw
private void performDraw() {
boolean canUseAsync = draw(fullRedrawNeeded);
}
performDraw方法很简单,直接调用了draw方法:
private boolean draw(boolean fullRedrawNeeded) {
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
draw方法代码很多,我就挑重点讲,在draw方法中调用了drawSoftware方法:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
mView.draw(canvas);
}
drawSoftware方法代码非常多,主要就是初始化Canvas相关的操作,只挑重点讲,在drawSoftware方法中经过Canvas的初始化,直接调用了View的mView.draw(canvas);并将canvas作为参数传进去:
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
if (!dirtyOpaque) {
drawBackground(canvas);
}
//ViewGroup不会回调onDraw这个方法,如果你设置背景onDraw是会被回调的,具体请看ViewGroup的构造方法initViewGroup
if (!dirtyOpaque) onDraw(canvas);
//在ViewGroup实现了这个方法,在View中是空实,意思就是:如果是ViewGroup那么它会分发绘制的给child,而绘制有child自己完成。
dispatchDraw(canvas);
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
在draw方法中,你可能会看到privateFlags 这个标志,实际上这个表示是用来表示是否需要绘制,即回调onDraw方法,在ViewGroup的构造函数中将此标志设置为 WILL_NOT_DRAW,所以为什么ViewGroup默认不会回调onDraw方法的原因,当然如果你设置背景onDraw是会被回调的。
最后关于自定义View相关的:
-
ViewGroup
1、绘制背景 drawBackground(canvas);2、绘制自己onDraw(canvas),实际上ViewGroup 并不需要去关心这个方法;
3、绘制子View dispatchDraw(canvas) ViewGroup 会在dispatchDraw方法中循环遍历所有的child并调用ViewGroup .drawChild方法紧接着调用child.draw方法让child自己绘制,dispatchDraw方法是在ViewGroup中才会有的,所以View并不存在此方法;
4、绘制前景,滚动条等装饰onDrawForeground(canvas)
-
View
1、绘制背景 drawBackground(canvas)
2、绘制自己onDraw(canvas)
3、绘制前景,滚动条等装饰onDrawForeground(canvas)
网友评论