Android - View 绘制流程

源代码 2024-10-8 14:27:20 60 0 来自 中国
简介
我们知道,在 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_PARENTLayoutParams.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举行表现。

    上述的逻辑总结如下图所示:(:图片泉源于互联网,侵删)
    3.png :前面我们不停夸大:子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 绘制流程,总结一下上述流程,如下图所示:
4.png 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
泉源:简书
著作权归作者全部。贸易转载请接洽作者得到授权,非贸易转载请注明出处。
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 14:17, Processed in 0.180475 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表