提问1:view是在什么时候添加到屏幕上的? setContentView()? ,并不是 之前分析setContentView()的流程可以得知在这个过程中只是创建出了DecorView,同时通过反射的方式创建出xml文件中相关组件的对象,此时组件中的view还并为与窗口关联。
提问2:下面在哪一处,可以打印出mTextView的高度?1?2?3? 实际上只有2能拿到。因为1,3出执行的时候还没有生成对应的高度,而在2处,是个handler message 虽然没有延时,但是当message被执行时,还会花费些时间,就已经生成生成对应的高度(玄学)。
提问3:getHeight() 和 getMeasureHeight()的区别? getMeasuredHeight()获取测量完的高度,只要在onMeasure方法执行完,就可以它获取到宽高。 getHeight()必须在onLayout方法执行完后,才能获得宽高。
提问4:.xml中的view是什么时候添加到 window上的? ActiviyThread 的 handleResumeActivity()
ActivityThread.java
.....
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
// TODO Push resumeArgs into the activity for consideration
// 注意这里
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
if (r == null) {
// We didn't actually resume the activity, so skipping any follow-up actions.
return;
}
if (mActivitiesToBeDestroyed.containsKey(token)) {
// Although the activity is resumed, it is going to be destroyed. So the following
// UI operations are unnecessary and also prevents exception because its token may
// be gone that window manager cannot recognize it. All necessary cleanup actions
// performed below will be done while handling destruction.
return;
}
final Activity a = r.activity;
.....
注意我加上注释的地方 ,进入这个函数,在这个函数里面会执行activity的perforResume,
ActivityThread.java
public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
String reason) {
.......
r.activity.performResume(r.startsNotResumed, reason); // 注意这里
r.state = null;
r.persistentState = null;
r.setState(ON_RESUME);
......
}
进入到activity的perforResume看看(如下),最终会通过 mInstrumentation.callActivityOnResume来使得activty执行 onResume()方法
Activity.java
final void performResume(boolean followedByPause, String reason) {
.....
mCalled = false;
// mResumed is set by the instrumentation
mInstrumentation.callActivityOnResume(this); // 注意这里
EventLogTags.writeWmOnResumeCalled(mIdent, getComponentName().getClassName(), reason);
if (!mCalled) {
throw new SuperNotCalledException(
"Activity " + mComponent.toShortString() +
" did not call through to super.onResume()");
}
......
总的来说 ,执行onResume()过程是这样的
--> performResumeActivity
--> r.activity.performResume
--> mInstrumentation.callActivityOnResume
那DecorView是什么时候添加到window上的?其实是在ActivityThread.java的performResume之后添加上去的。如下代码,在注释1出 会拿到在setContentView()创建出来的PhoneWindow,同时得到他的DecorView(也是在setContentView过程中创建来的) 在注释2处 得到 WindowManager,在注释3处拿到相关属性,最后在注释4处,通过WindowManager把DecorView 和 PhoneWindow的相关属性作为参数,进行添加。
ActivityThread.java
.....
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
......
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow(); //1 注意这里
View decor = r.window.getDecorView(); //注意这里
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager(); // 2 注意这里
WindowManager.LayoutParams l = r.window.getAttributes(); // 3 注意这里
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l); // 4 注意这里
......
}
顺着刚才的线索继续分析,在注释3处,ViewManager wm = a.getWindowManager();这个a是activity,进入到activity的getWindowManager(),在getWindowManager()直接返回了mWindowManager,那Activity.java中的mWindowManager是怎么来的呢?mWindowManager的初始值是在activity的attach()方法中设置的。在注释1处回去创建出WindowManager(实际上是WindowManagerImpl),并将其赋给mWindowManager。
Activity.java
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
.....
mWindow.setWindowManager( // 注释 1
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
mWindow.setPreferMinimalPostProcessing(
(info.flags & ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING) != 0);
......
回到之前的 wm.addView(decor, l); 实际上是调用WindowManagerImpl的addView()方法,addView中又调用了 mGlobal.addView(),新的问题又来了,mGlobal是什么,他又是在哪里有了初始值的,这个等会再探索,现在接着分析 mGlobal.addView,mGlobal的类型是WindowManagerGlobal
,在WindowManagerGlobal的addView想对传进来的参数和合法性做检查,然后会判断该view是不是已经添加进来,然后再判断要添加的view是不是正在准备回收,正在准备会回收的view是不会执行添加的动作的。之后会创建出一个ViewRootImpl。需要注意的是 mViews存储的是所有window对应的view,mRoots存储的是window对应的ViewRootImpl,mParams存储的是所有window对应的布局参数。在注释4处会调用ViewRootImpl的setView方法,也就是把我们的view添加到ViewRootImpl中,
WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
.....
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view); // 注意 1
mRoots.add(root); // 注意 2
mParams.add(wparams); // 注意 1
try {
root.setView(view, wparams, panelParentView, userId); // 注意4
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
.....
}
到现在,我们遇到了三个重要的类 WindowManagerImpl、WindowManagerGlobal、 ViewRootImplWindowManagerImpl:确定 View 属于哪个屏幕,哪个父窗口
WindowManagerGlobal:管理整个进程 所有的窗口信息
ViewRootImpl:WindowManagerGlobal 实际操作者,操作自己的窗口
我们再总结几条结论加深理解:
每个Window都有自己的唯一的一个WindowManager,WindowManager负责维护它所在的Window里面的内容。
在Android平台上Window的具体实现是PhoneWindow、对应它的WindowManager是WindowManagerImpl。
WindowManagerImpl维护PhoneWindow中的什么内容呢? 答案:DecorView及其子View所组成的View树。
一个Activity有且只有一个PhoneWindow实例,一个Dialog同样有且只有一个PhoneWindow实例。
结合下面这张图更容易理解一些。
如上图:
一个Window都有一个WindowManager负责管理它的内容。
PhoneWindow的内容则交给WindowManagerImpl管理,其中的“内容”指的则是DecorView及其子View所组成的View树。
当WindowManagerImpl决定管理View树时,会给这个View树分配一个ViewRootImpl,ViewRootImpl则是负责与其他服务联络并指导View树的绘制工作。
Activity有PhoneWindow对象,Dialog同样也有自己的PhoneWindow对象
关于这部分详细的内容 这里有别人分析的详细内容捋一捋,到底怎么样去理解Window机制?
言归正传 回到ViewRootImpl的setView方法中,让我们来继续进行探索,setView会调用 requestLayout()(如下);在requestLayout中会先进行一次线程检查,最后执行scheduleTraversals方法。
而在ViewRootImpl.setView()中执行完了 requestLayout()之后,还会将 窗口添加到wms,还会处理输出事件,还会给view设置parant,也就是当前的ViewRootImpl。
ViewRootImpl.java
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
总结一下:
handleResumeActivity
--> 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);
ViewRootImpl.setView
--> requestLayout(); // 请求遍历
--> scheduleTraversals
--> mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
--> doTraversal
--> performTraversals(); // 绘制View
--> res = mWindowSession.addToDisplayAsUser // 将窗口添加到WMS上面 WindowManagerService
--> 事件处理
--> view.assignParent(this); // getParent ViewRootImpl
上面说到,在requestLayout会进行一次线程检查,即 checkThread();方法,在这个方法中,会用mThread 和当前的线程进行比较,只有一致的的时候才执行,否则报错,通过ViewRootImpl的构造函数可以看得出,mThread值是new ViewRootImpl时所在的线程。
ViewRootImpl.java
void checkThread() {
if (mThread != Thread.currentThread()) { 注意
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
ViewRootImpl.java
public ViewRootImpl(Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
mContext = context;
mWindowSession = session;
mDisplay = display;
mBasePackageName = context.getBasePackageName();
mThread = Thread.currentThread(); // 注意
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
mWidth = -1;
mHeight = -1;
mDirty = new Rect();
mTempRect = new Rect();
mVisRect = new Rect();
mWinFrame = new Rect();
mWindow = new W(this);
mLeashToken = new Binder();
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mPerformContentCapture = true; // also true for the first time the view is added
mAdded = false;
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
.......
在上面的代码中还有一个点需要注意的是 mDirty = new Rect(); 为了保证绘制的效率,控件树仅对需要重绘的区域进
绘制。这部分区域称为“脏区域”,即Dirty Area。当一个控件的内容发生变化而需要重绘时,它会通过View.invalidateO
方法将其需要重绘的区域沿着控件树提交给ViewRootmpl,并保存在ViewRootlmpl的mDirty成员中,最后通过scheduleTraversals0引发- -次 “遍历",
进而进行重绘工作。ViewRootlmpl 会保证仅有位于mDirty所描述的区域得到重绘,从而避免不必要的开销。
还有一个需要注意的是 mAttachInfo 这个参数,这个参数中保存了当前窗口的一些信息(从他的参初中就可以看得出)。
在scheduleTraversals中会拿到ui线程的handler和looper最后通过消息屏障的方式将绘制view的消息发送出去。消息的内容封装到了mTraversalRunnable中。消息被执行时会执行 doTraversal方法,然后会在doTraversal中调用performTraversals方法,绘制view主要是在performTraversals中完成的。
ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); // 注意
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
接下来进入到view绘制的核心 performTraversals();这个函数的代码众多,但我们关注以下几个点
performTraversals@ViewRootImpl.java
--> windowSizeMayChange |= measureHierarchy(); // 预测量
--> relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); // 布局窗口
--> performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // 控件树测量
--> performLayout(lp, mWidth, mHeight); // 布局
--> performDraw(); // 绘制
performTraversals(是Android源代码中最庞大的方法之一,因此在正式探讨它的实现之前最好先将其划分为以下几个工作阶段作为指导。
**预测量阶段**:这是进人performTraversals)方法后的第一个阶段,它会对控件树进行第一次测量。测量结果可以通过mView.
getMeasuredWidth0/HeightO)获得。在此阶段中将会计算出控件树为显示其内容所需的尺寸,即期望的窗口尺寸。在这个阶段中,View及其子
类的onMeasure0方法将会沿着控件树依次得到回调。
**布局窗口阶段**:根据预测量的结果,通过IWindowSession.relayout(方法向WMS请求调整窗口的尺寸等属性,这将引发WMS对窗口进行重新布局
,并将布局结果返回给ViewRootlmpl。最终测量阶段。预测量的结果是控件树所期望的窗口尺寸。然而由于在WMS中影响窗口布局的因素很多(参考
第4章),WMS不 一定会将窗口准确地布局为控件树所要求的尺寸,而迫于WMS作为系统服务的强势地位,控件树不得不接受WMS的布局结果。因此在这个阶段,
performTraversals 将以窗口的实际尺寸对控件进行最终测量。在这个阶段中,View 及其子类的onMeasure0方法将会沿着控件树依次被回调。
**布局控件树阶段:**完成最终测量之后便可以对控件树进行布局。测量确定的是控件的尺寸,而布局则是确定控件的位置。在这个阶段中,View 及其子类的onLayout()方法将会被回调。
**绘制阶段**:这是prfrormTraversals)的最终阶段。确定控件的位置与尺寸后,便可以对控件树进行绘制。在这个阶段中,View 及其子类的onDraw0方法将会被回调。
在于测量阶段, 只有窗口的宽是WRAP_CONTENT才进行预测量(悬浮窗口),measureHierarchy首先会用它期望的宽度进行测量,这一个值是一个固定的值,保存在系统资源里面,然后调用performMeasure进行测量,performMeasure测量完后,performMeasure会将控件树是否对传入的宽高满意的结果放到一个变量中,如果对第一次的测量结果满意,则退出预测量,否则 measureHierarchy会用(期望的宽度+最大宽度)/2 作为宽度调用performMeasure进行第二次测量,performMeasure会将控件树是否对传入的宽高满意的结果放到一个变量中,如果对第二次的测量结果满意,则退出预测量,否则 measureHierarchy会用最大宽度作为宽度调用performMeasure进行第三次测量(最终测量),这次不会再判断 空间树是否满意。
对于非悬浮窗口,即当LayoutParams.width被设置为MATCH_ PARENT 时,不存在协商过程,直接使用给定的desiredWindow Width/Height进行测量即可。而对于悬浮窗口,measureHierarchyo可以连续进行两次让步。因而在最不利的情况下,在ViewRootImpl的一
次“遍历”中,控件树需要进行三次测量,即控件树中的每- -个View.onMeasureO会被连续调用三次之多,如图6-7所示。所以相对于onLayout), onMeasure( 方法对性能的影响比较大)。(提示一下 参数中的host为DecorView) 在注释三处,可以理解为在第三次测量完后,如果控件树(DecorView)还是不满意 则会将 windowSizeMayChange = true 意味着后续还要测量。
ViewRootImpl.java
下面这是一段伪代码,和源码的流程有差异
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
......
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
悬浮窗口 协商出宽度 最多走三次 performMeasure
} else {
非悬浮窗口不存在协商过程,直接使用给定的desiredWindow Width/Height进行测量即可
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
}
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) { // 注意1
windowSizeMayChange = true;
}
......
}
performMeasure进入到View.measure(),进入到measure看看,在注释1处 调用了onMeasure()方法,在注释2处会们是否调用了setMeasuredDimension()进行判断,没有调用则抛出异常,这就是我们重写onMeasure()时必须调用setMeasuredDimension的原因,再重写的时候可以主动调用,也可以通过super.onMeasure() 进行调用
View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
onMeasure(widthMeasureSpec, heightMeasureSpec); // 注意1
.....
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { /// 注意2
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
在深入了解measure()和 onMeasure()之前,引入一个十分重要的概念:measureSpeac ,他的大概作用是:MeasureSpec 封装了从父View 传递给到子View的布局需求。每个MeasureSpec代表宽度或高度的要求。每个MeasureSpec都包含了size(大小)和mode(模式)。MeasureSpec表示的是一个32位的int值,它的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小SpecSize。MeasureSpec是View类的一个静态内部类,用来说明应该如何测量这个View。
MeasureSpec 一个32位二进制的整数型,前面2位代表的是mode,后面30位代表的是size。mode 主要分为3类,分别是
EXACTLY:父容器已经测量出子View的大小。对应是 View 的LayoutParams的match_parent 或者精确数值。
AT_MOST:父容器已经限制子view的大小,View 最终大小不可超过这个值。对应是 View 的LayoutParams的wrap_content
UNSPECIFIED:父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0X3 << MODE_SHIFT;
// 不指定测量模式, 父视图没有限制子视图的大小,子视图可以是想要
// 的任何尺寸,通常用于系统内部,应用开发中很少用到。
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
// 精确测量模式,视图宽高指定为match_parent或具体数值时生效,
// 表示父视图已经决定了子视图的精确大小,这种模式下View的测量
// 值就是SpecSize的值。
public static final int EXACTLY = 1 << MODE_SHIFT;
// 最大值测量模式,当视图的宽高指定为wrap_content时生效,此时
// 子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。
public static final int AT_MOST = 2 << MODE_SHIFT;
// 根据指定的大小和模式创建一个MeasureSpec
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
// 微调某个MeasureSpec的大小
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
if (mode == UNSPECIFIED) {
// No need to adjust size for UNSPECIFIED mode.
return make MeasureSpec(0, UNSPECIFIED);
}
int size = getSize(measureSpec) + delta;
if (size < 0) {
size = 0;
}
return makeMeasureSpec(size, mode);
}
}
那么view的MeasureSpec 是根据什么规则来生成的呢?如下 view的MeasureSpec 生成内容依赖于自己布局参数,和父view的布局参数:
// ViewGroup的measureChildWithMargins方法
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身
// 的LayoutParams有关,此外还和View的margin及padding有关
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);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimesion) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// padding是指父容器中已占用的空间大小,因此子元素可用的
// 大小为父容器的尺寸减去padding
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (sepcMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimesion == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size....
// find out how big it should be
resultSize = 0;
resultMode == MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
MeasureSpec的算法可以总结成如下的表中:
由前面的分析可知,控件树的测量流程是从performMeasure方法开始的,相关的核心代码流程如下。一文彻底搞懂Android View的绘制流程
private void perormMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
// 具体的测量操作分发给ViewGroup
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
// 在ViewGroup中的measureChildren()方法中遍历测量ViewGroup中所有的View
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
// 当View的可见性处于GONE状态时,不对其进行测量
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
// 测量某个指定的View
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
// 根据父容器的MeasureSpec和子View的LayoutParams等信息计算
// 子View的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// View的measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
// ViewGroup没有定义测量的具体过程,因为ViewGroup是一个
// 抽象类,其测量过程的onMeasure方法需要各个子类去实现
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
// 不同的ViewGroup子类有不同的布局特性,这导致它们的测量细节各不相同,如果需要自定义测量过程,则子类可以重写这个方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// setMeasureDimension方法用于设置View的测量宽高
setMeasureDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
// 如果View没有重写onMeasure方法,则会默认调用getDefaultSize来获得View的宽高
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 = sepcSize;
break;
}
return result;
}
接下来就进入到performLayout看看,performLayout的调用关系如下
performLayout
--> host.layout
--> onLayout(changed, l, t, r, b);
--> child.layout
// ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
// View.java
public void layout(int l, int t, int r, int b) {
...
// 通过setFrame方法来设定View的四个顶点的位置,即View在父容器中的位置
boolean changed = isLayoutModeOptical(mParent) ?
set OpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
...
onLayout(changed, l, t, r, b);
...
}
// 空方法,子类如果是ViewGroup类型,则重写这个方法,实现ViewGroup
// 中所有View控件布局流程
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
可以这样理解,performLayout会调用layou函数,如果目前的布局是一个viewGroup则 会调用 onLayout方法,在onLayout方法中,会遍历调用子view的layou方法,如果该子view是个viewGroup则会调用该viewGroup的onLayout方法,按照此规律不断的调用。
下面是LinearLayout的onLayout方法实现解析:
protected void onlayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l,)
}
}
// layoutVertical核心源码
void layoutVertical(int left, int top, int right, int bottom) {
...
final int count = getVirtualChildCount();
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasureWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
...
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
// 为子元素确定对应的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight);
// childTop会逐渐增大,意味着后面的子元素会被
// 放置在靠下的位置
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child,i)
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
接下来进入到 performDraw()的流程,大概的调用关系图下:
performDraw@ViewRootImpl.java
--> draw@ViewRootImpl.java
--> scrollToRectOrFocus 输入 弹出输入框
--> 硬件加速绘制 mAttachInfo.mThreadedRenderer.draw() 效果更好
--> 软件绘制 drawSoftware()
--> mView.draw(canvas);
--> onDraw(canvas);
--> dispatchDraw(canvas);
private void performDraw() {
...
draw(fullRefrawNeeded);
...
}
private void draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffest, yOffset,
scalingRequired, dirty)) {
return;
}
...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo,
int xoff, int yoff, boolean scallingRequired, Rect dirty) {
...
mView.draw(canvas);
...
}
// 绘制基本上可以分为六个步骤
public void draw(Canvas canvas) {
...
// 步骤一:绘制View的背景
drawBackground(canvas);
...
// 步骤二:如果需要的话,保持canvas的图层,为fading做准备
saveCount = canvas.getSaveCount();
...
canvas.saveLayer(left, top, right, top + length, null, flags);
...
// 步骤三:绘制View的内容
onDraw(canvas);
...
// 步骤四:绘制View的子View
dispatchDraw(canvas);
...
// 步骤五:如果需要的话,绘制View的fading边缘并恢复图层
canvas.drawRect(left, top, right, top + length, p);
...
canvas.restoreToCount(saveCount);
...
// 步骤六:绘制View的装饰(例如滚动条等等)
onDrawForeground(canvas)
}