前言
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方法绘制自身