为什么SpecMode是高两位而不是低两位

为什么安卓MeasureSpec中SpecMode是高两位而不是低两位

在 Android 开发中,我们也时常见到位运算的身影。在进行自定义 View 的时候,都会用到 int makeMeasureSpec(int size, int mode) 方法去获取 View 的尺寸和测量模式,那么它是怎么把两个变量组装成一个的呢?简单地讲就是用一个 32 位二进制数字中的高两位来存储测量模式 MeasureMode,用低 30 位来存储尺寸 MeasureSize,MeasureSpec 是 android.view.View 类中的一个内部类,关键代码如下:

 

/**  
 * 三种SpecMode: 
 * 1.UNSPECIFIED 
 * 父 ViewGroup 没有对子View施加任何约束,子 view 可以是任意大小。这种情况比较少见,主要用于系统内部多次measure的情形,
 * 用到的一般都是可以滚动的容器中的子view,比如ListView、GridView、RecyclerView中某些情况下的子view就是这种模式。
 * 一般来说,我们不需要关注此模式。
 * 2.EXACTLY 
 * 该 view 必须使用父 ViewGroup 给其指定的尺寸。对应 match_parent 或者具体数值(比如30dp)
 * 3.AT_MOST 
 * 该 View 最大可以取父ViewGroup给其指定的尺寸。对应wrap_content
 *  
 * MeasureSpec使用了二进制去减少对象的分配。 
 */  
public class MeasureSpec {  
        // 进位大小为2的30次方(int的大小为32位,所以进位30位就是要使用int的最高位和第二高位也就是32和31位做标志位)  
        private static final int MODE_SHIFT = 30;  
 
        // 运算遮罩,0x3为16进制,10进制为3,二进制为11。3向左进位30,就是11 00000000000(11后跟30个0)  
        // (遮罩的作用是用1标注需要的值,0标注不要的值。因为1与任何数做与运算都得任何数,0与任何数做与运算都得0)  
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  
 
        // 0向左进位30,就是00 00000000000(00后跟30个0)  
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
        // 1向左进位30,就是01 00000000000(01后跟30个0)  
        public static final int EXACTLY     = 1 << MODE_SHIFT;  
        // 2向左进位30,就是10 00000000000(10后跟30个0)  
        public static final int AT_MOST     = 2 << MODE_SHIFT;  
 
        /** 
         * 根据提供的size和mode得到一个详细的测量结果 
         */  
        // 第一个return:
        // measureSpec = size + mode;   (注意:二进制的加法,不是十进制的加法!)  
        // 这里设计的目的就是使用一个32位的二进制数,32和31位代表了mode的值,后30位代表size的值  
        // 例如size=100(4),mode=AT_MOST,则measureSpec=100+10000...00=10000..00100  
        // 
        // 第二个return:
        // size &; ~MODE_MASK就是取size 的后30位,mode & MODE_MASK就是取mode的前两位,最后执行或运算,得出来的数字,前面2位包含代表mode,后面30位代表size
        public static int makeMeasureSpec(int size, int mode) {  
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode &  MODE_MASK);
            }
        }  
 
        /** 
         * 获得SpecMode
         */  
        // mode = measureSpec & MODE_MASK;  
        // MODE_MASK = 11 00000000000(11后跟30个0),原理是用MODE_MASK后30位的0替换掉measureSpec后30位中的1,再保留32和31位的mode值。  
        // 例如10 00..00100 & 11 00..00(11后跟30个0) = 10 00..00(AT_MOST),这样就得到了mode的值  
        public static int getMode(int measureSpec) {  
            return (measureSpec & MODE_MASK);  
        }  
 
        /** 
         * 获得SpecSize 
         */  
        // size = measureSpec &  ~MODE_MASK;  
        // 原理同上,不过这次是将MODE_MASK取反,也就是变成了00 111111(00后跟30个1),将32,31替换成0也就是去掉mode,保留后30位的size  
        public static int getSize(int measureSpec) {  
            return (measureSpec &  ~MODE_MASK);  
        }  
}  

 

 

位运算常用的操作符有以下几种:

  1. 或运算符| : 0|0=0,0|1=1,1|1=1
  2. 与运算符& : 0&0=0,0&1=0,1&1=1
  3. 非运算符~ : ~0=1,~1=0
  4. 异或运算符^ : 相同为 0,不同为 1:0^0=0,1^0=1,0^1=1,1^1=0
  5. 右移运算符 >> 和左移运算符 << : 001<<2=100,110>>1=11

在 MeasureSpec 类中,getMode 方法是将参数 measureSpec 与 MODE_MASK 进行与运算,MODE_MASK 可以理解为 SpecMode 的掩码,运算的结果是保留measureSpec 的高两位,剩下的后 30 位置 0,得到的是 MeasureMode。

getSize 方法是先将 MODE_MASK 取反再跟 measureSpec 进行与运算,结果是高两位为 0 低 30 位不变的值,即 SpecSize。

makeMeasureSpec 方法中,size & ~MODE_MASK 的结果是 size 的 SpecSize,mode & MODE_MASK 的结果是 SpecMode,将他们进行或操作,得到的就是是两者的叠加值。