Android进阶知识:绘制流程(上)

889 阅读9分钟

1、前言

之前写过一篇Android进阶知识:事件分发与滑动冲突,主要研究的是关于AndroidView事件分发与响应的流程。关于View除了事件传递流程还有一个很重要的就是View的绘制流程。一个Activity界面从启动到绘制完成出现在眼前,这中间经历了哪些过程。每一个View的大小、位置、形状是怎么确定的。这些也都是自定义View的必备知识,所以也是很有必要来学习一下。因为要从Window初始化一直到子View绘制结束,涉及的内容有点多所以分成几篇来写。这第一篇先是一些基础知识,主要包括View基础,Android中的坐标系,MeasureSpec类和Window相关知识总结。

2、View基础

2.1 View是什么?

ViewAndroid中所有控件的基类,无论是TextViewImageView还是ButtonCheckBox都是继承自View,可以说我们的应用界面就是由各式各样的View组成的。

//ImageView继承了View
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
}
//ImageView继承了View
public class ImageView extends View {
}
//ImageView继承了TextView
public class Button extends TextView {
}
//ImageView继承了CompoundButton
public class CheckBox extends CompoundButton {
}
//ImageView继承了Button
public abstract class CompoundButton extends Button implements Checkable {
}

2.2 ViewGroup是什么?

ViewGroup从名字就可以看出来表示一组View,它可以包含多个View。平时常用的LinearLayoutRelativeLayoutFrameLayout等都是继承自ViewGroup,并且ViewGroup也是继承自View

//LinearLayout继承了ViewGroup
public class LinearLayout extends ViewGroup {
}
//RelativeLayout继承了ViewGroup
public class RelativeLayout extends ViewGroup {
}
//FrameLayout继承了ViewGroup
public class FrameLayout extends ViewGroup {
}
//ViewGroup继承了View
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
}

又因为不同的应用界面都由不同的ViewViewGroup组成,所以最终的结构会形成一个树,如下图。

View树

3、坐标系

Android中有两种坐标系,一个Android坐标系,一个是View坐标系。Android坐标系以屏幕左上角为原点,向右为x轴正方向,向下为y轴正方向。View坐标系以父View的左上角为原点,同样向右为x轴正方向,向下为y轴正方向。Android中也提供了获取所在坐标的方法。在View中,有mLeftmTopmRightmBottom四个成员变量并且提供对应的get获取方法,这四个值就存储了这个View相对于父布局的所在位置坐标。除此之外,在View的事件响应方法onTouchEvent中会返回一个MotionEvent对象,这个对象提供了两对方法getXgetYgetRawXgetRawY,分别获得当前触摸点在所在View内的坐标和当前触摸点在Android坐标系内的坐标。具体可以看下面这张图。

坐标轴

  • View中的方法:

    getLeft(): 获取View的左边到其父布局左边的距离。
    getTop(): 获取View上边到其父布局上边的距离。
    getRight(): 获取View右边到其父布局左边的距离。
    getBottom(): 获取View下边到其父布局上边的距离。

  • MotionEvent中的方法:

    getX(): 获取触摸点距离所在View左边的距离。
    getY(): 获取触摸点距离所在View上边的距离。
    getRawX(): 获取触摸点到整个屏幕左边的距离。
    getRawY(): 获取触摸点到整个屏幕顶边的距离。

4、MeasureSpec类

MeasureSpecView类里的一个内部类,表示测量规格,它的作用是封装了从父布局传递到子级的布局需求。每个MeasureSpec代表宽度或高度的要求。MeasureSpec由测量尺寸size和测量模式mode组成。

如上图,MeasureSpec里代表了一个32位的int类型。高两位表示测量模式,低30位表示测量大小。其中测量模式分为以下三种:

  • UNSPECIFIED 模式
    UNSPECIFIED模式下,父View不会约束子View大小,一般用于系统内部例如ListViewScrollView等。
  • EXACTLY 模式
    EXACTLY模式下,父View为子View测量出所需要的大小,一般对应match_parent属性,强制大小充满父布局和父布局一样大,或者具体数值,比如100dp
  • AT_MOST 模式
    AT_MOST模式下,父View为子View提供一个最大的尺寸大小,子View大小可以任意由自己决定,但是最大不能超过这个尺寸,一般对应wrap_content属性,自适应大小。

MeasureSpec类的源码不是很多,具体如下。

 public static class MeasureSpec {
        //移位大小
        private static final int MODE_SHIFT = 30;
        //0x3二进制为11,11 << 30 结果为:11 00000000 00000000 00000000 000000 (30个0)用于后面做与运算
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
        //UNSPECIFIED模式:父View没有对子View施加任何约束。它可以是任意大小。
        //0 << 30 结果为:00 00000000 00000000 00000000 000000 
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        //EXACTLY模式:父View已经为子View确定了确切的大小。不管子View想要多大,他都会得到这些界限。
        //1 << 30 结果为:01 00000000 00000000 00000000 000000
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        //AT_MOST模式:子View可以任意大,但不能超过父View的大小
        //2 << 30 结果为:10 00000000 00000000 00000000 000000
        public static final int AT_MOST     = 2 << MODE_SHIFT;
        /**
         * 根据size和mode创建一个MeasureSpec
         */
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
           //sUseBrokenMakeMeasureSpec的值API小于17位true大于17位false
            if (sUseBrokenMakeMeasureSpec) {
                //兼容API17以前的,通过size+mode获得一个32位int的MeasureSpec
                return size + mode;
            } else {
              //API17以后更加严格,采用位运算,防止溢出
              //例如:             size:4  mode:AT_MOST
              //~MODE_MASK为:     00 11111111 11111111 11111111 111111
              //size & ~MODE_MASK:00 00000000 00000000 00000000 000100
              //mode :            10 00000000 00000000 00000000 000000
              //MODE_MASK:        11 00000000 00000000 00000000 000000
              //mode & MODE_MASK:  10 00000000 00000000 00000000 000000 
              //(size & ~MODE_MASK) | (mode & MODE_MASK) 最终结果:
              //                   10 00000000 00000000 00000000 000100 
              return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        /**
         * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
         * will automatically get a size of 0. Older apps expect this.
         *
         * @hide internal use only for compatibility with system widgets and older apps
         */
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

        /**
         * 从MeasureSpec中获取mode
         */
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        /**
         * 从MeasureSpec中获取size
         */
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
                // No need to adjust size for UNSPECIFIED mode.
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }
        /**
         * Returns a String representation of the specified measure
         * specification.
         *
         * @param measureSpec the measure specification to convert to a String
         * @return a String with the following format: "MeasureSpec: MODE SIZE"
         */
        public static String toString(int measureSpec) {
            int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            StringBuilder sb = new StringBuilder("MeasureSpec: ");
            if (mode == UNSPECIFIED)
                sb.append("UNSPECIFIED ");
            else if (mode == EXACTLY)
                sb.append("EXACTLY ");
            else if (mode == AT_MOST)
                sb.append("AT_MOST ");
            else
                sb.append(mode).append(" ");
            sb.append(size);
            return sb.toString();
        }
    }

源码里已经加了注释了,这里再来说一下,首先是几个成员变量。

  • MODE_SHIFT表示移位大小。
  • MODE_MASK是进行位运算的遮罩。
  • UNSPECIFIEDEXACTLYAT_MOST分别对应三种测量模式。

下图是这几个值的二进制表示。

接着来看makeMeasureSpecgetSizegetMode这几个主要的方法。

 public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
           //sUseBrokenMakeMeasureSpec的值API小于17位true大于17位false
            if (sUseBrokenMakeMeasureSpec) {
                //兼容API17以前的,通过size+mode获得一个32位int的MeasureSpec
                return size + mode;
            } else {
              //API17以后更加严格,采用位运算,防止溢出
              return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

makeMeasureSpec方法作用是根据sizemode创建一个MeasureSpec,可以看到根据API等级不同,实现也不同,API17之前是直接将sizemode相加,API17之后是采用位运算的方式,位运算集体看下面这张图。

makeMeasureSpec方法

        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

getSizegetMode这俩方法分别是从测量规格MeasureSpec中获取到对应测量大小和测量模式,具体计算看下图。

getSize方法和getMode方法
关于MeasureSpec的内容就是这些,具体的运用等到看到绘制流程时再说。

5、Window相关

Window是一个抽象的窗体的概念,每个Activity初始化默认会创建一个Window,界面上所有的View都会添加到这个Window上。Android中的Window类也是个抽象类,它的实现类是PhoneWindow。关于Window的知识点很多,这里就简单介绍下和Window有关的概念,了解下与Window有关的类的作用,主要是帮助理解后面View加载到Window过程。

Window相关的有这几个类和他们的作用:

  • Window:窗体抽象类。
  • PhoneWinow:Window的具体实现类,对View进行管理。
  • WindowManager:是个接口,用来管理Window。
  • WindowManagerImpl:WindowManager的实现类,包含对Window各种操作(添加、删除、更新)的方法。
  • WindowManagerService(WMS):WindowManager的管理者,负责对窗口的管理、Surface的管理等。
  • ViewRootImpl:所有View的根,将Window和View联系起来。
  • DecorView:顶级View。

由上面这张图可以清楚的看出应用界面的层级,其中Window又由WindowManager来管理,进而会通过ViewRootImpl中的IWindowSession进行Binder通信,最终通过WMS把窗口Surface进行绘制到屏幕上。下面这张图简单描述了这个逻辑。

6、总结

这一篇内容主要是梳理了一些绘制流程中要用到的基础知识,比较简单,为的是之后在看具体流程代码的时候更加顺利。下一篇就开始看具体绘制流程了。