前言
在上一篇文章中慢~再听我讲一遍Activity的启动流程以源码执行顺序为流程分析了从应用点击到 Activity.onCreate 的过程,但是用户还是处于看不到页面的状态,那么之后的哪些操作才会让用户才能看到我们的页面呢?所以此文主要是接上一篇文章继续分析。
- Activity 用于 UI 界面的显示和处理,但是 Activity 并不会直接管理 View。
- Window 主要用于承载View ,而且由 PhoneWindow(Window的实现类) 来承载 View,它持有根 View,即DecorView ,Window 通过 WindowManager 把自己和 DecorView 关联起来。
- ViewRootImpl 管理 DecorView,它是 WMS 和 DecorView 连接纽带,其次它还管理 view 的绘制,事件分发。
要让 view 显示出来,主要是上面几个对象的创建和关联,其次就是处理所有的 View。
一.绘制的前置工作
1.创建Window
Window 的字面意思就是窗口,是一种抽象的概念,它居于页面的最顶级,提供可绘制UI和响应输入事件的区域, PhoneWindow 是 Window 的具体实现。
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) {
...
//创建PhoneWindow对象
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...
//为window设置windowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
...
}
在 Activity.attach
中完成了 PhoneWindow 的创建,由此可见,每个 Activity 都有一个与之对应的 window,但是如果这么多 Window 都要和 WMS一一通信,那么既浪费资源,又容易出问题,所以它们统一由 WindowManager 来管理,WindowManager 是一个接口,它的实现是 WindowManagerImpl。
2.创建DecorView
DecorView 是一个继承自 FrameLayout 的 View,它是整个 view 的原始容器,包含一个 LinearLayout,它有两个子元素 TitleView 和 ContentView,一般在 onCreate
中 setContentView
就是把 xml 布局解析后填充到这个区域。
@Override
public void setContentView(int layoutResID) {
...
//mContentParent是ContentView的父容器 未初始化为null
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...
//解析xml文件 填充view
mLayoutInflater.inflate(layoutResID, mContentParent);
...
}
private void installDecor() {
...
if (mDecor == null) {
//generateDecor 中 返回了DecorView的实例
mDecor = generateDecor(-1);
...
}
...
if (mContentParent == null) {
//通过findviewbyid(R.id.content) 找到ContentView
mContentParent = generateLayout(mDecor);
...
}
}
handleLaunchActivity
方法会调用Activity.onCreate
方法,在这个方法里调用setContentView
,加载我们的布局到 DecorView 中。
3.关联DecorView与ViewRootImpl
ViewTree 是View 的层级关系,需要依附于 Window 来显示,而且也需要被管理,所以由 ViewRoot 连接 DecorView 和 WindowManager,并且管理着 View 的测量、布局、绘制、包括触摸等相关事件。
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {
//调用onResume
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
if (r.window == null && !a.mFinished && willBeVisible) {
//获取到Window和DecorWindow
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//得到了WindowManager
ViewManager wm = a.getWindowManager();
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//addView由WindowManagerGlobal实现
wm.addView(decor, l);
}
}
}
}
//WindowManager.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
synchronized (mLock) {
//创建了ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
...
//将ViewTree与ViewRootImpl关联 后续会对他进行操做
root.setView(view, wparams, panelParentView, userId);
}
}
//ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
//保证i第一次的绘制
requestLayout();
...
}
```
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
//添加同步屏障优先处理绘制事件
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//注册回调vsync信号回调 mTraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
//开始绘制
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
上述整体关系的创建和关联流程图如下,有需要的同学可以据此查看源码:
二.绘制的整体流程
绘制的流程从performTraversals
开始,依次调用performMeasure
,performLayout
,performDraw
,它们对应着measure
,layout
,draw
三个流程,measure 用于计算 view 在 ui 界面上的区域大小,layout 用于计算 view 的位置,draw 用于绘制 view 的内容。
//ViewRootImpl.java
private void performTraversals() {
...
//根据mWidth/mHeight(窗口的宽高)和lp(LayoutParams)获取当前测量规则
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
//执行测量流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//执行布局流程
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//执行绘制流程
performDraw();
}
1.measure流程
1.1.测量规则
先来分析performTraversals
方法中getRootMeasureSpec
方法,其表面意思获取根 view 的测量规则,所有 view 在测量阶段都共同遵循一个规则,即根据父view的 MeasureSpec 和自身的 LayoutParams 决定如何测量,但也有例外,比如 DecorView 是根据窗口的大小和自身的 LayoutParams 决定的。
LayoutParams 指定视图的高度和宽度等参数,它有三种方式:
- 具体值 比如10dp
- MATCH_PARENT 表示子容器想和父容器一样大
- WRAP_CONTENT 表示容器包裹其内容就可以
MeasureSpec有三种模式:
- EXACTLY 父容器给出了明确的小大,view的最终大小就是这个值,对应LayoutParams的MATCH_PARENT和具体值
- AT_MOST 父容器给出了可用的大小,view不能超过这个值,对应LayoutParams的WRAP_CONTENT
- UNSPECIFIED 父容器不对子容器做限制
接下来结合源码看一下例子:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
//LayoutParams.MATCH_PARENT对应MeasureSpec的EXACTLY模式
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
//LayoutParams.WRAP_CONTENT对应MeasureSpec的AT_MOST模式
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
//这种情况就是填了具体值 组建规则时直接用具体的值
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
//将模式和数值结合按规则结合为一个32位的int值 节约内存
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
通过判断 rootDimension(view的宽度/高度信息) 对应 LayoutParams 的三种模式,根据三种模式配对出 MeasureSpec 的组建方式。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
//mView(DecorView)开始measure
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
1.2.View的测量
View 的 measure
方法中会去调用 View 的 onMeasure
方法,如下。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension
会设置View的宽和高而且必须在onMeasure
里设置,接下来看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:
//此处直接使用父类的specSize
result = specSize;
break;
}
return result;
}
在这个方法中 View 的大小其实已经取决于父类传递的 MesureSpec 中的 specSize,而且这里的 AT_MOST 和 EXACTLY 取的值一样,那么 wrap_content 其实是失去了该有的效果的,所以如果直接继承 view 要重写 onMeasure 方法判断 wrap_content 模式添加默认的值。
1.3.ViewGroup的测量
对于 DecorView 来说,实际执行测量工作的是 FrameLayout 的 onMeasure,但是在分析 FrameLayout.onMeasure 前,我们先来看一下 ViewGroup 的测量流程,ViewGroup 是一个抽象类,没有实现 onMeasure (因为每个子类的实现方式不同,比如 LinearLayout、RelativeLayout),而是由子类实现,但是它提供了 measureChildren
,代码如下:
//ViewGroup.java
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
//遍历所有的子view调用measureChild方法
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measureChild
方法的大致意思是:取出子 view 的 LayoutParams ,然后通过getChildMeasureSpec
创建子 View 的 MeasureSpec ,再将 MeasureSpec 传递给子 View 进行测量,这样一套下来就全部都经历了测量的流程。
接下来看一下 FrameLayout 的onMeasure
:
//FrameLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
for (int i = 0; i < count; i++) {
//循环所有的子view
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
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);
childState = combineMeasuredStates(childState, child.getMeasuredState());
...
}
}
...
//记录结果
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
上面的代码中measureChildWithMargins
对所有的子 view 测量了一遍,如果有 padding 和 margin 也会参与计算,经过了以上计算后,我们得到了 maxHeight 和 maxWidt h的最终值,使用setMeasuredDimension
保存到 mMeasuredWidth 与 mMeasuredHeight 成员变量中,它们不仅包含了 size 还包含了 state,所以会先调用 resolveSizeAndState
处理这两个值。
2. layout流程
经过performMeasure
流程,ViewTree中各个元素的大小已经被记录下来,接下来会进入另一个遍历的流程,即Layout,它的作用是ViewGroup用来确定子元素的位置信息。
public void layout(int l, int t, int r, int b) {
...
//记录四个位置setFrame,l,t,r,b代表此View与父View的左上右下边框的距离。
boolean changed = isLayoutModeOptical(mParent)?setOpticalFrame(l,t,r,b):setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
...
}
...
}
DecorView 的 layout 实际上调用了 View 的 layout 方法,它的意思就是通过setFrame
方法来设定 View 的四个顶点的位置,View 的四个顶点一旦确定,那么 View 在父容器中的位置也就确定了,接着会调用onLayout
方法,我们来看看 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();//child在measure中测量到的宽度
final int height = child.getMeasuredHeight();//measure高度
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;
//计算childLeft
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;
}
//计算childTop,类似与计算childLeft
...
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
上述代码可见,循环所有子 View 然后调用其 layout 方法传入此 View 的 layout 信息,这里主要计算了 childLeft 和 childTop 这两个值,对于一个矩形,得到 left,top,width 和 height 就已经知道了它的大小。layout 结束后,view 的大小和位置就已经确定了。
3. draw流程
一个对象的layout确定后,才可以执行draw,代码如下:
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
//绘制背景
// Step 1, draw the background, if needed
int saveCount;
drawBackground(canvas);
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
//绘制自己的内容
// Step 3, draw the content
onDraw(canvas);
//绘制childen
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
//第六步,绘制装饰(前景/滚动条)
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
...
}
整体上做了以下几步:
- 绘制背景
- 绘制自己的内容
- 绘制childen
- 绘制装饰(前景/滚动条)
总结
在这一部分中,要先梳理好整个View的框架,比如 Activity,Window,ViewRoot,WindowManagerImpl 之间的关系,然后再看整个 ViewTree 的遍历以及处理会比较好理解。
最后
如果本文提供了有效信息还烦请点个小赞,如果有什么出入可以评论或私信我,感谢。