简介
我们知道,在 Android 中,View 绘制告急包罗 3 大流程:
measure(丈量):告急用于确定 View 的丈量宽/高。
layout(布局):告急用于确定 View 在父容器中的放置位置。
draw(绘制):团结前面两步结果,将 View 真正绘制到屏幕上。
Android 中,告急有两种视图:View和ViewGroup,此中:
View:就是一个独立的视图
ViewGroup:一个容器组件,该容器可容纳多个子视图,即ViewGroup可容纳多个View或ViewGroup,且支持嵌套。
固然ViewGroup继续于View,但是在 View 绘制三大流程中,某些流程须要区分View和ViewGroup,它们之间的利用并不完全雷同,比如:
View和ViewGroup都须要举行 measure,确定各自的丈量宽/高。View只需直接丈量自身即可,而ViewGroup通常都必须先丈量全部子View,末了才华丈量自己
通常ViewGroup先定位自己的位置(layout),然后再定位其子View 位置(onLayout)
View须要举行 draw 过程,而ViewGroup通常不须要(固然也可以举行绘制),由于ViewGroup更多作为容器存在,起存储放置功能
measure 流程
对 View 举行丈量,告急包罗两个步骤:
求取 View 的丈量规格MeasureSpec。
依据上一步求得的MeasureSpec,对 View 举行丈量,求取得到 View 的终极丈量宽/高。
MeasureSpec
对于第一个步骤,即求取 View 的MeasureSpec,首先我们来看下MeasureSpec的源码界说:
// frameworks/base/core/java/android/view/View.javapublic 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; // 天生丈量规格 public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } // 获取丈量模式 public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } // 获取丈量巨细 public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } ...}MeasureSpec是View的一个公有静态内部类,它是一个 32 位的int值,高 2 位表现 SpecMode(丈量模式),低 30 位表现 SpecSize(丈量尺寸/丈量巨细)。
MeasureSpec将两个数据打包到一个int值上,可以镌汰对象内存分配,而且其提供了相应的工具方法可以很方便地让我们从一个int值中抽取出 View 的 SpecMode 和 SpecSize。
一个MeasureSpec表达的是:该 View 在该种丈量模式(SpecMode)下对应的丈量尺寸(SpecSize)。此中,SpecMode 有三种范例:
UNSPECIFIED:表现父容器对子View 未施加任何限定,子View 尺寸想多大就多大。
EXACTLY:如果子View 的模式为EXACTLY,则表现子View 已设置了确切的丈量尺寸,大概父容器已检测出子View 所须要简直切巨细。
这种模式对应于LayoutParams.MATCH_PARENT和子View 设置详细数值两种环境。
AT_MOST:表现自顺应内容,在该种模式下,View 的最大尺寸不能凌驾父容器的 SpecSize,因此也称这种模式为 最大值模式。
这种模式对应于LayoutParams.WRAP_CONTENT。
LayoutParams
对 View 举行丈量,最关键的一步就是计算得到 View 的MeasureSpec,子View 在创建时,可以指定差异的LayoutParams(布局参数),LayoutParams的源码告急内容如下所示:
// frameworks/base/core/java/android/view/ViewGroup.javapublic static class LayoutParams { ... /** * Special value for the height or width requested by a View. * MATCH_PARENT means that the view wants to be as big as its parent, * minus the parent's padding, if any. Introduced in API Level 8. */ public static final int MATCH_PARENT = -1; /** * Special value for the height or width requested by a View. * WRAP_CONTENT means that the view wants to be just large enough to fit * its own internal content, taking its own padding into account. */ public static final int WRAP_CONTENT = -2; /** * Information about how wide the view wants to be. Can be one of the * constants FILL_PARENT (replaced by MATCH_PARENT * in API Level 8) or WRAP_CONTENT, or an exact size. */ public int width; /** * Information about how tall the view wants to be. Can be one of the * constants FILL_PARENT (replaced by MATCH_PARENT * in API Level 8) or WRAP_CONTENT, or an exact size. */ public int height; ...}此中:
- LayoutParams.MATCH_PARENT:表现子View 的尺寸与父容器一样大(注:须要减去父容器padding部门空间,让父容器padding见效)
- LayoutParams.WRAP_CONTENT:表现子View 的尺寸自顺应其内容巨细(注:须要包罗子View 自己的padding空间)
- width/height:表现 View 的设置宽/高,即layout_width和layout_height设置的值,其值有三种选择:LayoutParams.MATCH_PARENT、LayoutParams.WRAP_CONTENT和 详细数值。
LayoutParams会受到父容器的MeasureSpec的影响,丈量过程会依据两者之间的相互束缚终极天生子View 的MeasureSpec,完成 View 的丈量规格。
简而言之,View 的MeasureSpec受自身的LayoutParams和父容器的MeasureSpec共同决定(DecorView的MeasureSpec是由自身的LayoutParams和屏幕尺寸共同决定,参考后文)。也因此,如果要求取子View 的MeasureSpec,那么首先就须要知道父容器的MeasureSpec,层层逆推而上,即终极就是须要知道顶层View(即DecorView)的MeasureSpec,这样才华一层层传递下来,这整个过程须要团结Activity的启动过程举行分析。
Activity 视图根本布局
我们知道,在 Android 中,Activity是作为视图组件存在,告急就是在手机上表现视图界面,可以供用户利用,Activity就是 Andorid 中与用户直接交互最多的体系组件。
Activity的根本视图条理布局如下所示:
Activity中,实际承载视图的组件是Window(更详细来说为PhoneWindow),顶层View 是DecorView,它是一个FrameLayout,DecorView内部是一个LinearLayout,该LinearLayout由两部门构成(差异 Android 版本或主题稍有差异):TitleView和ContentView,此中,TitleView就是标题栏,也就是我们常说的TitleBar或ActionBar,ContentView就是内容栏,它也是一个FrameLayout,告急用于承载我们的自界说根布局,即当我们调用setContentView(...)时,实在就是把我们自界说的布局设置到该ContentView中。
当Activity启动完成后,终极就会渲染出上述条理布局的视图。
DecorView 丈量规格
因此,如果我们要求取得到子View 的MeasureSpec,那么第一步就是求取得到顶层View(即DecorView)的MeasureSpec。大抵过程如下所示:
- 在Activity启动过程中,会调用到ActivityThread.handleResumeActivity(...),该方法就是 View 视图绘制的起始之处:
// frameworks/base/core/java/android/app/ActivityThread.javafinal void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { ... ActivityClientRecord r = performResumeActivity(token, clearHide); ... // 此处的 window 为与 Activity 绑定的 PhoneWindow,即 Activity.mWindow r.window = r.activity.getWindow(); // PhoneWindow 绑定的顶层视图:DecorView View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); // 获取与 Activity 绑定的 WindowManager,实际上是 PhoneWindow 的 WindowManager ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); ... // 添加 DecorView 到 PhoneWindow 上(相称于设置 Activity 根视图) wm.addView(decor, l); ...}此中,r.window.getDecorView()实际调用的是PhoneWindow.getDecorView(),其会返回顶层DecorView(不存在时会主动实例化):
// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.javapublic class PhoneWindow extends Window implements MenuBuilder.Callback { // This is the top-level view of the window, containing the window decor. private DecorView mDecor; ... @Override public final View getDecorView() { if (mDecor == null) { installDecor(); } return mDecor; } private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); ... } ... } protected DecorView generateDecor() { // 实例化 DecorView return new DecorView(getContext(), -1); } ...}然后,r.window.getAttributes()实际调用的是Window.getAttributes():
// frameworks/base/core/java/android/view/Window.javapublic abstract class Window { private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams(); ... public final WindowManager.LayoutParams getAttributes() { return mWindowAttributes; }}// frameworks/base/core/java/android/view/WindowManager.javapublic interface WindowManager extends ViewManager { ... public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { public LayoutParams() { // DecorView 的布局参数为 MATCH_PARENT super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); ... } }}这里可以看到,此处r.window.getAttributes()返回的是一个WindowManager.LayoutParams实例,对应的终极宽/高布局参数为LayoutParams.MATCH_PARENT,末了通过wm.addView(decor,l)将DecorView添加到WindowManager上(终极实在是设置到ViewRootImpl上),以是DecorView的布局参数为MATCH_PARENT。
- View 的绘制流程真正开始的地方为ViewRootImpl.performTraversals(),在此中,有如下代码片断:
// frameworks/base/core/java/android/view/ViewRootImpl.javaprivate void performTraversals() { ... int desiredWindowWidth; int desiredWindowHeight; ... // Ask host how big it wants to be windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight); ...}private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) { ... childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ...}此处的desiredWindowWidth和desiredWindowHeight是屏幕的尺寸,内部终极会调用到ViewRootImpl.getRootMeasureSpec(...),其源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.javaprivate 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;}ViewRootImpl.getRootMeasureSpec(...)见名知意,实在就是用来获取顶层View(即DecorView)的MeasureSpec,其逻辑如下:
- 当DecorView的LayoutParams为MATCH_PARENT时,阐明DecorView的巨细与屏幕一样大,而又由于屏幕巨细是确定的,因此,其 SpecMode 为EXACTLY,SpecSize 为windowSize,;
- 当DecorView的LayoutParams为WRAP_CONTENT时,阐明DecorView自顺应内容巨细,因此它的巨细不确定,但是最大不能凌驾屏幕巨细,故其 SpecMode 为AT_MOST,SpecSize 为windowSize;
- 别的环境为DecorView设置了详细数值巨细或UNSPECIFIED,故以DecorView为主,其 SpecMode 为EXACTLY,SpecSize 就是自己设置的值,即rootDimension;
团结我们上面的分析,由于DecorView的LayoutParams为MATCH_PARENT,因此,DecorView的MeasureSpec终极为:MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY),即DecorView的 SpecMode 为EXACTLY,SpecSize 为屏幕巨细。
默认丈量(measure)
颠末上述步骤求取得到 View 的MeasureSpec后,接下来就可以真正对 View 举行丈量,求取 View 的终极丈量宽/高:
Android 内部对视图举行丈量的过程是由View#measure(int, int)方法负责的,但是对于View和ViewGroup,其详细丈量过程有所差异。
因此,对于丈量过程,我们分别对View和ViewGroup举行分析:
- 如果父容器的丈量模式为UNSPECIFIED:即父容器巨细无限定,此时:
- 如果 子View 的LayoutParams为详细数值:则表现 子View 已明白设置了详细巨细,因此,此时 子View 的丈量巨细即为自己设置的值,即childDimension,丈量模式为EXACTLY。
- 如果 子View 的LayoutParams为MATCH_PARENT:表现 子View 的巨细撑满父容器,由于父容器巨细无限定,因此,子View 的巨细也是无限定的,以是,子View 的丈量模式为UNSPECIFIED,丈量巨细未知,通常设置为0,表现无限。
- 如果 子View 的LayoutParams为WRAP_CONTENT:表现 子View 自顺应内容巨细,由于父容器巨细无限定,因此,子View 的丈量巨细也是无限定的,以是其模式为UNSPECIFIED,丈量巨细无限,通常利用0举行表现。
上述的逻辑总结如下图所示:(注:图片泉源于互联网,侵删)
注:前面我们不停夸大:子View 的MeasureSpec是由其LayoutParams和父容器的MeasureSpec共同束缚构造而成,实在这部门逻辑就是ViewGroup#getChildMeasureSpec(...)方法负责的,可以很清楚看到,子View 的MeasureSpec就是在父容器MeasureSpec束缚下,与其自身LayoutParams共同协商决定的。
综上,无论是对View的丈量还是ViewGroup的丈量,都是由View#measure(int widthMeasureSpec, int heightMeasureSpec)方法负责,然后真正执行 View 丈量的是 View 的onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法。
详细来说,View直接在onMeasure(...)中丈量并设置自己的终极丈量宽/高。在默认丈量环境下,View的丈量宽/高由其父容器的MeasureSpec和自身的LayoutParams共同决定,当View自身的丈量模式为LayoutParams.UNSPECIFIED时,其丈量宽/高为android:minWidth/android:minHeight和其配景宽/高之间的较大值,别的环境皆为自身MeasureSpec指定的丈量尺寸。
而对于ViewGroup来说,由于布局特性的丰富性,只能自己手动覆写onMeasure(...)方法,实现自界说丈量过程,但是总的头脑都是先丈量 子View 巨细,终极才华确定自己的丈量巨细。
layout 流程
当确定了 View 的丈量巨细后,接下来就可以来确定 View 的布局位置了,也即将 View 放置到屏幕详细哪个位置。
View layout
View 的布局过程由View#layout(...)负责,其源码如下:
// android/view/View.java/** * @param l Left position, relative to parent * @param t Top position, relative to parent * @param r Right position, relative to parent * @param b Bottom position, relative to parent */@SuppressWarnings({"unchecked"})public void layout(int l, int t, int r, int b) { ... setFrame(l, t, r, b); ... onLayout(changed, l, t, r, b); ...}View#layout(...)告急就做了两件事:
- setFrame(...):首先通过View#setFrame(...)来确定自己的布局位置,其源码如下:
// android/view/View.javaprotected boolean setFrame(int left, int top, int right, int bottom) { ... // Invalidate our old position invalidate(sizeChanged); mLeft = left; mTop = top; mRight = right; mBottom = bottom;}setFrame(...)实在就是更新记载 View 的四个极点位置,这样 View 在父容器中的坐标位置就确定了。
- onLayout(...):setFrame(...)是用于确定 View 自身的布局位置,而onLayout(...)告急用于确定 子View 的布局位置:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}由于 View 不包罗子组件,因此其onLayout是一个空实现。
ViewGroup layout
ViewGroup 的布局流程由ViewGroup#layout(...)负责,其源码如下:
// android/view/ViewGroup.java@UiThreadpublic abstract class ViewGroup extends View implements ViewParent, ViewManager { ... @Override public final void layout(int l, int t, int r, int b) { ... super.layout(l, t, r, b); ... }可以看到,ViewGroup#layout(...)终极也是通过View#layout(...)完成自身的布局过程,一个留意的点是,ViewGroup#layout(...)是一个final方法,因此子类无法覆写该方法,告急是ViewGroup#layout(...)方法内部对子视图动画结果举行了相干设置。
由于ViewGroup#layout(...)内部终极调用的还是View#layout(...),因此,ViewGroup#onLayout(...)就会得到回调,用于处置处罚 子View 的布局放置,其源码如下:
// android/view/ViewGroup.java@Overrideprotected abstract void onLayout(boolean changed, int l, int t, int r, int b);由于差异的ViewGroup,其布局特性差异,因此ViewGroup#onLayout(...)是一个抽象方法,交由ViewGroup子类依据自己的布局特性,摆放其 子View 的位置。
draw 流程
当 View 的丈量巨细,布局位置都确定后,就可以终极将该 View 绘制到屏幕上了。
View 的绘制过程由View#draw(...)方法负责,其源码如下:
// android/view/View.javapublic void draw(Canvas canvas) { ... /* * 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) */ // Step 1, draw the background, if needed drawBackground(canvas); // skip step 2 & 5 if possible (common case) ... // Step 2, save the canvas' layers if (drawTop) { canvas.saveLayer(left, top, right, top + length, null, flags); } if (drawBottom) { canvas.saveLayer(left, bottom - length, right, bottom, null, flags); } if (drawLeft) { canvas.saveLayer(left, top, left + length, bottom, null, flags); } if (drawRight) { canvas.saveLayer(right - length, top, right, bottom, null, flags); } ... // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers ... if (drawTop) { ... canvas.drawRect(left, top, right, top + length, p); } if (drawBottom) { ... canvas.drawRect(left, bottom - length, right, bottom, p); } if (drawLeft) { ... canvas.drawRect(left, top, left + length, bottom, p); } if (drawRight) { ... canvas.drawRect(right - length, top, right, bottom, p); } ... // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas);}实在表明已经写的很清楚了,View#draw(...)告急做了以下 6 件事:
- 绘制配景:drawBackground(...)
- 如果有须要的话,生存画布图层:Canvas.saveLayer(...)
- 绘制自己:onDraw(...),其源码如下:
// android/view/View.javaprotected void onDraw(Canvas canvas) {}View#onDraw(...)是一个空方法,由于每个 View 的绘制都是差异的,自界说 View 时,通常会覆写该方法,手动绘制该 View 内容。
- 绘制子View:dispatchDraw(...),其源码如下:
// android/view/View.javaprotected void dispatchDraw(Canvas canvas) {}由于 View 没有子元素,因此其dispatchDraw是一个空实现。
查察下ViewGroup#dispatchDraw(...),其源码如下:
// android/view/ViewGroup.java@Overrideprotected void dispatchDraw(Canvas canvas) { ... final int childrenCount = mChildrenCount; final View[] children = mChildren; ... for (int i = 0; i < childrenCount; i++) { ... more |= drawChild(canvas, child, drawingTime); ... } ...}protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime);}可以看到,其内部告急就是遍历子View,末了通过child.draw(...)让子View自己举行绘制。
- 如果有须要的话,绘制淡化结果并规复图层:Canvas.drawRect(...)
- 绘制装饰:onDrawForeground(...),其源码如下:
// android/view/View.javapublic void onDrawForeground(Canvas canvas) { onDrawScrollIndicators(canvas); onDrawScrollBars(canvas); final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; ... foreground.draw(canvas); }}实在告急就是绘制滚动条,远景图片等视图相干的装饰。
绘制起始流程
我们知道,在Activity启动过程中,会调用到ActivityThread.handleResumeActivity(...),该方法就是 View 视图绘制的起始之处:
// frameworks/base/core/java/android/app/ActivityThread.javafinal void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { ... // 回调 Activity.onResume() 方法 ActivityClientRecord r = performResumeActivity(token, clearHide); ... // 获取当前 Activity 实例 final Activity a = r.activity; ... // 此处的 window 为与 Activity 绑定的 PhoneWindow,即 Activity.mWindow r.window = r.activity.getWindow(); // PhoneWindow 绑定的顶层视图:DecorView View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); // 获取与 Activity 绑定的 WindowManager,实际上是 PhoneWindow 的 WindowManager ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); ... // 添加 DecorView 到 PhoneWindow 上(相称于设置 Activity 根视图) wm.addView(decor, l); ...}可以看到,ActivityThread.handleResumeActivity(...)告急就是获取到当前Activity绑定的ViewManager,末了调用ViewManager.addView(...)方法将DecorView设置到PhoneWindow上,也即设置到当前Activity上。ViewManager是一个接口,WindowManager继续ViewManager,而WindowManagerImpl实现了接口WindowManager,此处的ViewManager.addView(...)实际上调用的是WindowManagerImpl.addView(...),源码如下所示:
// frameworks/base/core/java/android/view/WindowManagerImpl.javapublic final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); ... @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); } ...}WindowManagerImpl.addView(...)内部转发到WindowManagerGlobal.addView(...):// frameworks/base/core/java/android/view/WindowManagerGlobal.javapublic final class WindowManagerGlobal { ... public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... ViewRootImpl root; ... // 实例化一个 ViewRootImpl root = new ViewRootImpl(view.getContext(), display); ... // 将 ViewRootImpl 与 DecorView 关联到一起 root.setView(view, wparams, panelParentView); ... } ...}在WindowManagerGlobal.addView(...)内部,会创建一个ViewRootImpl实例,然后调用ViewRootImpl.setView(...)将ViewRootImpl与DecorView关联到一起:
// frameworks/base/core/java/android/view/ViewRootImpl.javapublic final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { ... public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ... // 将 DecorView 绑定到 ViewRootImpl.mView 属性上 mView = view; ... mWindowAttributes.copyFrom(attrs); ... // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); ... } ... @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { // 查抄是否处于主线程 checkThread(); ... scheduleTraversals(); } } ...}ViewRootImpl.setView(...)内部首先关联了传递过来的DecorView(通过属性mView指向DecorView即可创建关联),然后终极调用requestLayout(),而requestLayout()内部又会调用方法scheduleTraversals():
// frameworks/base/core/java/android/view/ViewRootImpl.javapublic final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { ... Choreographer mChoreographer; ... final class TraversalRunnable implements Runnable { @Override public void run() { // 开始执行绘制 doTraversal(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); ... void scheduleTraversals() { if (!mTraversalScheduled) { // 同一帧内不会多次调用遍历 mTraversalScheduled = true; // 发送一个同步屏蔽 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 将 UI 绘制任务发送到 Choreographer,回调触发 mTraversalRunnable,执行绘制利用 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ... } } ... void doTraversal() { ... performTraversals(); ... } ...}ViewRootImpl.scheduleTraversals()内部告急做了两件事:
调用MessageQueue.postSyncBarrier()方法发送一个同步屏蔽,同步屏蔽可以拦截Looper对同步消息的获取与分发,即到场同步屏蔽后,此时Looper只会获取和处置处罚异步消息,如果没有异步消息,则进入壅闭状态。
通过Choreographer.postCallback(...)发送一个Choreographer.CALLBACK_TRAVERSAL的异步视图渲染消息。由于前面已经发送了一个同步屏蔽,因此此处的视图绘制渲染消息会优先被处置处罚。
Choreographer.postCallback(...)会申请一次 VSYNC 制止信号,当 VSYNC 信号到达时,便会回调Choreographer.doFrame(...)方法,内部会触发已经添加的回调任务,Choreographer的回调任务有以下四种范例:
// 回调 INPUT 任务doCallbacks(Choreographer.CALLBACK_INPUT, mframeTimeNanos);// 回调 ANIMATIONdoCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);// 回调 View 绘制任务 TRAVERSALdoCallbacks(Choreographer,CALLBACK_TRAVERSAL, frameTimeNanos);// API Level 23 新增,COMMIT doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);因此,ViewRootImpl.scheduleTraversals(...)内部通过
<meta charset="utf-8">
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)发送的异步视图渲染消息就会得到回调,即回调mTraversalRunnable.run()方法,终极会执行doTraversal()方法,而doTraversal()内部又会调用performTraversals()方法,该方法才是真正开始执行 View 绘制流程的地方,其源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.javapublic final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { ... private void performTraversals() { 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(); ... } ...}综上,performTraversals()会依次调用performMeasure(...)、performLayout(...)和performDraw()三个方法,这三个方法会依次完成顶层View(即DecorView)的丈量(measure)、布局(layout)和绘制(draw)流程,详细详情请参考后文。
到此,我们才真正进入 View 绘制流程,总结一下上述流程,如下图所示:
performMeasure
书接前文,我们知道,真正开始 View 绘制流程是ViewRootImpl.performTraversals(),该方法内部首先举行的是performMeasure(...)流程:
// frameworks/base/core/java/android/view/ViewRootImpl.javaprivate void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { // 调用 DecorView.measure(...) mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}此处的mView实在就是DecorView,其赋值指向在ViewRootImpl.setView(...)中举行,可以看到,performMeasure(...)实际调用的是DecorView.measure(...),以是终极会回调DecorView#onMeasure(...)方法,其源码如下:
// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.javapublic class PhoneWindow extends Window implements MenuBuilder.Callback { ... private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { ... @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ... super.onMeasure(widthMeasureSpec, heightMeasureSpec); ... } ...}可以看到,DecorView#onMeasure(...)内部将丈量过程交由其父类,即FrameLayout举行处置处罚,那我们看下FrameLayout#onMeasure(...)源码:
// frameworks/base/core/java/android/widget/FrameLayout.javapublic class FrameLayout extends ViewGroup { ... @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 获取 子View 数量 int count = getChildCount(); ... // 最大高度 int maxHeight = 0; // 最大宽度 int maxWidth = 0; int childState = 0; for (int i = 0; i < count; i++) { // 获取 子View final View child = getChildAt(i); // 只对可见的 子View 举行丈量 if (mMeasureAllChildren || child.getVisibility() != GONE) { // 丈量子View measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); // 获取 子View 的布局参数 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); // 获取当前子View的宽度,包罗其外边距,记载子View的最大宽度 maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); // 记载子View的最大高度 maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); ... } } // Account for padding too // 最大宽度包罗远景偏移量:padding maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); // 最大高度包罗远景偏移量:padding maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width // 比力子View 和 体系发起的 子View 最小高度,获取两者中的较大值 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); // 比力子View 和 体系发起的 子View 最小宽度,获取两者中的较大值 maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { // 子View 高度和 远景图片高度比力,记载此中较大值 maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); // 子View 高度和 远景图片宽度比力,记载此中较大值 maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } // 记载丈量结果 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); ... } ...}FrameLayout的布局特性为:全部 子View 层叠在一起,以是FrameLayout的丈量宽/高就是其全部 子View 中最大的宽和高,因此FrameLayout#onMeasure(...)的核心逻辑就是遍历其全部子View,然后通过measureChildWithMargins(...)(该方法前面内容已详细先容)丈量子View,然后就可以获取 子View 的宽/高,记载此中最大的宽/高值,作为自己的丈量宽/高。
颠末以上步骤,DecorView的丈量就已经完成了。
综上,ViewRootImpl#performMeasure(...)实在就是对DecorView的丈量过程(DecorView#measure(...)),DecorView是一个FrameLayout,其丈量过程告急由FrameLayout#onMeasure(...)负责,内部告急丈量逻辑是先遍历全部子View,让 子View 先自己举行丈量(child.measure(...)),然后就可以获取 子View 的丈量巨细,记载全部 子View 中占比最大的丈量宽/高,作为自己的终极丈量巨细。
<meta charset="utf-8">
performLayout
ViewRootImpl#performMeasure(...)完成对DecorView的丈量后,接下来执行的是ViewRootImpl#performLayout(...),其源码如下:
// frameworks/base/core/java/android/view/ViewRootImpl.javaprivate void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { ... // cache mView since it is used so much below... final View host = mView; ... host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ...}此中,参数lp的width和height均为MATCH_PARENT,desiredWindowWidth和desiredWindowHeight为屏幕宽/高,mView为DecorView。
以是,performLayout(...)内部实在就是调用DecorView#layout(...),前面 layout 流程中先容过,ViewGroup#layout(...)内部终极会通过View#layout(...)举行布局,而View#layout(...)内部终极通过View#setFrame(...)方法记载四个极点位置,这样DecorView自己的布局位置就已确定了,即host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight())。
确定了DecorView自身的布局位置后,接下来就是要布局其 子View 了,因此,这里终极回调的是DecorView#onLayout(...)方法,其源码如下所示:
// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.javapublic class PhoneWindow extends Window implements MenuBuilder.Callback { ... private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { ... @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); ... } ...}DecorView#onLayout(...)内部转交给FrameLayout#onLayout(...)举行 子View 布局利用,其源码如下:
// frameworks/base/core/java/android/widget/FrameLayout.javapublic class FrameLayout extends ViewGroup { ... @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // 布局子View layoutChildren(left, top, right, bottom, false /* no force left gravity */); } void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { // 获取 子View 数量 final int count = getChildCount(); // 左边可放置起始点坐标 final int parentLeft = getPaddingLeftWithForeground(); // 右边可放置尽头坐标 final int parentRight = right - left - getPaddingRightWithForeground(); // 顶部可放置起始点坐标 final int parentTop = getPaddingTopWithForeground(); // 底部可放置尽头坐标 final int parentBottom = bottom - top - getPaddingBottomWithForeground(); // 遍历 子View for (int i = 0; i < count; i++) { // 获取 子View final View child = getChildAt(i); // 不放置状态为 GONE 的子View if (child.getVisibility() != GONE) { // 获取 子View 布局参数 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); // 获取 子View 丈量宽/高 final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); // 当前 子View 的布局左边界 int childLeft; // 当前 子View 的布局右边界 int childTop; ... child.layout(childLeft, childTop, childLeft + width, childTop + height); } } } ...}FrameLayout#onLayout(...)内部是通过FrameLayout#layoutChildren(...)举行 子View 的布局利用,其告急逻辑就是遍历全部 子View,计算得到 子View 的四个极点位置坐标,末了将结果传递给child.layout(...),让 子View 记载自己在父容器中的布局位置,完成 子View 的布局过程。
综上,ViewRootImpl#performLayout(...)就是对DecorView的布局过程,此过程会递归计算各个 子View 的布局位置,调用 子View 的布局方法,完成各个 子View 的布局。
performDraw
完成了performMeasure(...)和performLayout(...)后,末了一步就是performDraw(...)过程,其源码如下:
// frameworks/base/core/java/android/view/ViewRootImpl.javaprivate void performDraw() { ... draw(fullRedrawNeeded); ...}private void draw(boolean fullRedrawNeeded) { ... if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } ...}private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { ... mView.draw(canvas); ...}可以看到,ViewRootImpl#performDraw()内部会经由ViewRootImpl#draw(...)、ViewRootImpl#drawSoftware(...),终极执行的还是DecorView#draw(...)过程,其源码如下:
// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.javapublic class PhoneWindow extends Window implements MenuBuilder.Callback { ... private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { @Override public void draw(Canvas canvas) { super.draw(canvas); if (mMenuBackground != null) { mMenuBackground.draw(canvas); } } ...}由于FrameLayout没有覆写draw(...)方法,因此,super.draw(...)终极调用的是View#draw(...)方法,以是DecorView默认采取的就是 View 的绘制方法,详细绘制详情上文已先容过了,告急就是对DecorView的配景、内容、子View、滚动条等装饰视图举行绘制。
至此,View 绘制的整个流程已根本先容完毕。
总结
View 的绘制告急有以下一些核心内容:
- 三大流程:View 绘制告急包罗如下三大流程:
- measure:丈量流程,告急负责对 View 举行丈量,其核心逻辑位于View#measure(...),真正的丈量处来由View#onMeasure(...)负责。默认的丈量规则为:如果 View 的布局参数为LayoutParams.WRAP_CONTENT或LayoutParams.MATCH_PARENT,那么其丈量巨细为 SpecSize;如果其布局参数为LayoutParams.UNSPECIFIED,那么其丈量巨细为android:minWidth/android:minHeight和其配景之间的较大值。
自界说View 通常覆写onMeasure(...)方法,在其内一样寻常会对WRAP_CONTENT预设一个默认值,区分WARP_CONTENT和MATCH_PARENT结果,终极完资源身的丈量宽/高。而ViewGroup在onMeasure(...)方法中,通常都是先丈量子View,网络到相应数据后,才华终极丈量自己。
- layout:布局流程,告急完成对 View 的位置放置,其核心逻辑位于View#layout(...),该方法内部告急通过View#setFrame(...)记载自己的四个极点坐标(记载与对应成员变量中即可),完资源身的位置放置,末了会回调View#onLayout(...)方法,在其内完成对 子View 的布局放置。
注:差异于 measure 流程首先对 子View 举行丈量,末了才丈量自己,layout 流程首先是先定位自己的布局位置,然后才处置处罚放置 子View 的布局位置。
- draw:绘制流程,就是将 View 绘制到屏幕上,其核心逻辑位于View#draw(...),告急就是对 配景、自身内容(onDraw(...))、子View(dispatchDraw(...))、装饰(滚动条、远景等) 举行绘制。
注:通常自界说View 覆写onDraw(...)方法,完资源身的绘制即可,ViewGroup 一样寻常充当容器利用,因此通常无需覆写onDraw(...)。
- Activity 的根视图(即DecorView)终极是绑定到ViewRootImpl,详细是由ViewRootImpl#setView(...)举行绑定关联的,后续 View 绘制的三大流程都是均有ViewRootImpl负责执行的。
- 对 View 的丈量流程中,最关键的一步是求取 View 的MeasureSpec,View 的MeasureSpec是在其父容器MeasureSpec的束缚下,团结自己的LayoutParams共同丈量得到的,详细的丈量逻辑由ViewGroup#getChildMeasureSpec(...)负责。
DecorView的MeasureSpec取决于自己的LayoutParams和屏幕尺寸,详细的丈量逻辑位于ViewRootImpl#getRootMeasureSpec(...)。
末了,稍微总结一下 View 绘制的整个流程:
- 首先,当 Activity 启动时,会触发调用到ActivityThread#handleResumeActivity(..),其内部会履历一系列过程,天生DecorView和ViewRootImpl等实例,末了通过ViewRootImpl#setView(decor,MATCH_PARENT)设置 Activity 根View。
注:ViewRootImpl#setView(...)内容通过将其成员属性ViewRootImpl#mView指向DecorView,完成两者之间的关联。
- ViewRootImpl乐成关联DecorView后,其内部会设置同步屏蔽并发送一个CALLBACK_TRAVERSAL异步渲染消息,在下一次 VSYNC 信号到来时,CALLBACK_TRAVERSAL就会得到响应,从而终极触发执行ViewRootImpl#performTraversals(...),真正开始执行 View 绘制流程。
- ViewRootImpl#performTraversals(...)内部会依次调用ViewRootImpl#performMeasure(...)、ViewRootImpl#performLayout(...)和ViewRootImpl#performDraw(...)三大绘制流程,此中:
- performMeasure(..):内部告急就是对DecorView执行丈量流程:DecorView#measure(...)。DecorView是一个FrameLayout,其布局特性是层叠布局,所占的空间就是其 子View 占比最大的宽/高,因此其丈量逻辑(onMeasure(...))是先对全部 子View 举行丈量,详细是通过ViewGroup#measureChildWithMargins(...)方法对 子View 举行丈量,子View 丈量完成后,记载最大的宽/高,设置为自己的丈量巨细(通过View#setMeasuredDimension(...)),云云便完成了DecorView的丈量流程。
- performLayout(...):内部实在就是调用DecorView#layout(...),云云便完成了DecorView的布局位置,末了会回调DecorView#onLayout(...),负责 子View 的布局放置,核心逻辑就是计算出各个 子View 的坐标位置,末了通过child.layout(...)完成 子View 布局。
- performDraw():内部终极调用到的是DecorView#draw(...),该方法内部并未对绘制流程做任何修改,因此终极执行的是View#draw(...),以是告急就是依次完成对DecorView的 配景、子View(dispatchDraw(...)) 和 视图装饰(滚动条、远景等) 的绘制。
作者:Whyn
链接:https://www.jianshu.com/p/ee5d3bb5ab90
泉源:简书
著作权归作者全部。贸易转载请接洽作者得到授权,非贸易转载请注明出处。 |