【1 引言】
本文来源于一个bug,后来越走越远跑偏了,从LinearLayouy-》View 》-MeasureSpec-》位运算-》计算机的编码(原码反码补码)这已经到计算机组成原理了~~于是权当做一次笔记记录:
起源的bug:
使用流式布局时( 关于流式布局可见:http://blog.csdn.net/zxt0601/article/details/50533658),布局内item太多,经过看log,已经达到四个屏幕的高度,而最外层没有嵌套ScrollView,导致无法滑动~内容显示不全,后来我在最外层嵌套了ScrollView发现还是无法滑动,我就怀疑是我自定义的流式布局onMeasure()方法没有写好,经过log分析,原来是在MeasureSpec为UNSPECIFIED 时,我返回的是父控件允许的最大的高度(Match_parent),应该是返回该View想要的高度(wrap_content,四个屏幕的高度), ok bug虽然解决了,但是我就好奇了系统的源码是怎么写的。我知道LinearLayout的vertical模式,如果内容太多,外面套一个ScrollView,是可以滑动的,说明它是可以适应内容的高度的。
ok,那就看呗~原本是想趁机好好看一下LinearLayout 的vertical里是怎么onMeasure的,结果看进去发现它调用了一个 View类的 resolveSizeAndState(int size, int measureSpec, int childMeasuredState) 方法,返回想要的高度,ok 那我们继续看~,它内部当然免不了调用MeasureSpec.getMode(measureSpec) MeasureSpec.getSize(measureSpec) 方法,这两个方法 和 MeasureSpec类的另外一个方法 makeMeasureSpec()我们应该都不陌生,在resolveSizeAndState()方法里,经过一番比较,最终返回一个值作为高度。我又点进去MeasureSpec里查看,发现里面各种位运算,好吧 位运算,我都有点忘了~那么我就查查资料先好好了解位运算吧。结果看看位运算,算来算去感觉和我记忆里不太一样了,于是我又去看了原码反码补码。。。一路就这么任性的跑偏了,来到了计算机组成原理的范畴。。。
这里插一句,其实不止这一个类,系统源码里大量使用到了位运算,是因为位运算比较高效。虽然用其他运算符也能实现同样的效果,可是效率却不如位运算来的高。那位运算有啥缺点呢。就是可读性差了点,我们日常“凡人”开发,难免要组员维护 甚至后人维护你的代码,如果都用位运算,别人阅读你的代码的难度难免会增加。
ok~让我们逆转时光,回到我们故事的起点,LinearLayout。Start!
===================================================================
【2 LinearLayout的onMeasure()】
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
判断如果竖直方向就走measureVertical(widthMeasureSpec, heightMeasureSpec);,否则走measureHorizontal(widthMeasureSpec, heightMeasureSpec);(如果源码都能这么简单直接,那就太好啦。)
/**
* Measures the children when the orientation of this LinearLayout is set
* to {@link #VERTICAL}.
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
* @param heightMeasureSpec Vertical space requirements as imposed by the parent.
*
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onMeasure(int, int)
*/
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
tips:其实阅读源码时,不要小看方法头部的注释哦~一般都能给我们一些重要的提示信息。
这里简单翻译一下:当然LinearLayout的方向(orientation)设置为Vertical时,便调用这个方法测量子view。第一个参数widthMeasureSpec,是由父控件传递的水平方向的空间要求,第二个参数heightMeasureSpec 也是由父控件传递的,竖直方向的空间要求。
这里有个结论可以记一下先:view 的widthMeasureSpec 和heightMeasureSpec 是由view自己设置的width和height 和 父控件的width height 共同决定的。
measureVertical方法前面几句是定义变量,
然后就是熟悉的,MeasureSpec.getMode()获得水平 和 竖直方向上的测量模式。ok 逃不掉的,点getMode方法进去看吧~
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
===================================================================
【3 MeasureSpec】
这个方法位于View->MeasureSpec->
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
方法体代码倒是少,就用了&与操作 位运算,有点晕晕的,还是看看注释写的是什么意思吧:通过提供的测量Spec 提取测量模式,关于return 值,就是自定义View里onMeasure方法判断的那三种:关于这三个常量的定义,在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;
UNSPECIFIED (测量规范模式:父控件没有对控件强加任何约束。控件可以是它希望的任何大小。),那么从这我们也可以得出我们文首,我今天遇到的bug,在这种模式下,应该返回我们计算的高度,而非父布局的最大高度。
EXACTLY (测量规格模式:父控件已经为控件确定一个确切的大小。不管控件想要多大。一般是设置了明确的值或者是MATCH_PARENT)。
AT_MOST (测量规格模式:表示子布局限制在一个最大值内,一般为WARP_CONTENT)
那么这个位运算是干嘛的,<
===================================================================
【4 位运算铺垫知识】
关于位运算,说实话,我和同事讨论了一下,可能我们的段位比较低,一致认为是比较难理解,难一眼看出值,在稍微复杂一点的位运算时,我和她都是要用草稿纸写出来才能得到正确的结果。
了解位运算前,我们先复习一下一些java和计算机的基础知识。
以下是java的基本数据类型,以及在内存中所占的位数。(另外,一个字节等于8位)。记住int 为32,第6节会用到。
数据类型 所占位数
byte 8
boolean 8
short 16
int 32
long 64
float 32
double 64
char 16
在计算机中,参与运算的是二进制数的补码形式,(为什么用补码,就不深究了,我挖不动了,因为我感觉已经离题很远了。。。,当初老师讲的已经忘记= =!,网上搜到的结论如下,采用补码进行运算有两个好处,一个就是刚才所说的统一加减法;二就是可以让符号位作为数值直接参加运算,而最后仍然可以得到正确的结果符)
而二进制数有四种表现形式:原码,反码,补码,移码。
以byte类型的变量来举例(只有8位 ),(参考自 http://blog.csdn.net/liushuijinger/article/details/7429197)
原码:
就是一个数的二进制形式
X=+3 , [X]原= 0000 0011 X=-3, [X]原= 1000 0011
位数不够的用0补全。
其中,最高位为符号位:正数为0,负数为1。剩下的n-1位表示该数的绝对值,
PS:正数的原、反、补码都一样:0的原码跟反码都有两个,因为这里0被分为+0和-0。
反码:
反码就是在原码的基础上,符号位不变其他位按位取反 (就是0变1,1变0)就可以了。
X=-3,[X]原= 1000 0011 ,[X]反=1111 1100
补码:
补码就是在反码的基础上+1.
X=-3,[X]原= 1000 0011 ,[X]反=1111 1100,[X]补=1111 1101
移码:(其实我上大学时考试都没用过移码。。。不知道什么用 如果有知道的 可以评论告诉我一下 谢谢)
不管正负数,只要将其补码的符号位取反即可。
X=-3,[X]原= 1000 0011 ,[X]反=1111 1100,[X]补=1111 1101,[X]移=01111 1101
===================================================================
【5 位运算】
通过5,我们一定要记住,
位运算时,参与运算的都是补码,且正数 正反补码相同。
Java的位运算(bitwise operators)直接对整数类型的位进行操作,
由于数据类型所占字节是有限的,而位移的大小却可以任意大小,所以可能存在位移后超过了该数据类型的表示范围,于是有了这样的规定:
如果为int数据类型,且位移位数大于32位,则首先把位移位数对32取模,不然位移超过总位数没意义的。所以4>>32与4>>0是等价的。
如果为long类型,且位移位数大于64位,则首先把位移位数对64取模,若没超过64位则不用对位数取模。
如果为byte、char、short,则会首先将他们扩充到32位,然后的规则就按照int类型来处理。
位运算符具体如下表:
运算符 | |
左移位,在低位处补0 | |
右移位,若为正数则高位补0,若为负数则高位补1 | |
>>> | 无符号右移位,无论正负都在高位补0 |
与(AND),对两个整型操作数中对应位执行布尔代数,两个位都为1时输出1,否则0。 | |
或(OR),对两个整型操作数中对应位执行布尔代数,两个位都为0时输出0,否则1。 | |
非(NOT),一元运算符。 | |
异或(XOR),对两个整型操作数中对应位执行布尔代数,两个位相等0,不等1。 | |
<<=< span=””> | 左移位赋值。 |
>>= | 右移位赋值。 |
>>>= | 无符号右移位赋值。 |
按位与赋值。 | |
按位或赋值。 | |
按位异或赋值。 |
以 int型变量 -5,为例,
[-5]原=1000 0000 0000 0000 0000 0000 0000 0101,
[-5]补=1111 1111 1111 1111 1111 1111 1111 1010,
[-5]补=1111 1111 1111 1111 1111 1111 1111 1011,
在实际中,位运算比较令人头疼的也就是前三个移位运算,于是写了个demo验证:
public static void main(String[] args) {
/**
* java位运算:
* << 左移位,在低位处补0
* >> 右移位,若为正数则高位补0,若为负数则高位补1
* >>> 无符号右移位,无论正负都在高位补0
* &与(AND),对两个整型操作数中对应位执行布尔代数,两个位都为1时输出1,否则0。
* |或(OR),对两个整型操作数中对应位执行布尔代数,两个位都为0时输出0,否则1。
* ~非(NOT),一元运算符。
* ^异或(XOR),对两个整型操作数中对应位执行布尔代数,两个位相等0,不等1。
*/
/**
以 int型变量 -5,为例,
[-5]原=1000 0000 0000 0000 0000 0000 0000 0101,
[-5]补=1111 1111 1111 1111 1111 1111 1111 1010,
[-5]补=1111 1111 1111 1111 1111 1111 1111 1011,
*/
// 1、左移( << )
// 1111 1111 1111 1111 1111 1111 1111 1011 然后左移2位后,低位补0://
// 1111 1111 1111 1111 1111 1111 1110 1100 运算结果
// 1000 0000 0000 0000 0000 0000 0001 0100 :运算结果的原码:十进制下为 - (4+16) = -20
System.out.println("-5 << 2= " + (-5 << 2));// 运行结果是-20
// 2、右移( >> ) 高位补符号位
// 1111 1111 1111 1111 1111 1111 1111 1011 然后右移2位,高位补1:
// 1111 1111 1111 1111 1111 1111 1111 1110 运算结果
// 1000 0000 0000 0000 0000 0000 0001 0010 :运算结果的原码:十进制下为 - (2) = -2
System.out.println("-5 >> 2= " + (-5 >> 2));// 运行结果是-2
// 3、无符号右移( >>> ) 高位补0
// 1111 1111 1111 1111 1111 1111 1111 1011 右移2位,高位补0
// 0011 1111 1111 1111 1111 1111 1111 1110 运算结果 (符号位是0,运算结果应该是一个很大的正数)
System.out.println("-5 >>> 2= " + (-5 >>> 2));// 结果是1073741822
}
关于其它的位运算,在程序里一般都是正数的位运算,比较简单。就不举例。
好了饶了一圈,已经懵逼了,越走越深,是时候慢慢回去了。
===================================================================
【6 回顾MessureSpec】
这个时候我们再回到第3节,回过头看看MeasureSpec定义的这五个常量:
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
MODE_SHIFT为什么是30,我们第四节提过,int类型的位数是32位,32-30=2位, 2位可以表示四个数字,正好就是对应了这四个常量,0 1 2 3 。MODE_MASK,UNSPECIFIED,EXACTLY,AT_MOST,之所以是 0 1 2 3 右移30位
其实MeasureSpec类这么写,就是想用一个int类型,来同时存储测量模式(最高两位,2的2次方=4种信息,3种测量模式,和一个帮助值),和测量值(低30位,测量值可最大取2的30次方-1)。
为了验证我们的结论,我们顺着往下看源码,五个常量定以后,就是makeMeasureSpec方法:
/**
* Creates a measure specification based on the supplied size and mode.
*
* The mode must always be one of the following:
*
* {@link android.view.View.MeasureSpec#UNSPECIFIED}
* {@link android.view.View.MeasureSpec#EXACTLY}
* {@link android.view.View.MeasureSpec#AT_MOST}
*
*
*
Note: On API level 17 and lower, makeMeasureSpec's
* implementation was such that the order of arguments did not matter
* and overflow in either value could impact the resulting MeasureSpec.
* {@link android.widget.RelativeLayout} was affected by this bug.
* Apps targeting API levels greater than 17 will get the fixed, more strict
* behavior.
*
* @param size the size of the measure specification
* @param mode the mode of the measure specification
* @return the measure specification based on size and mode
*/
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
这个方法根据传入的测量大小size 和 测量模式mode,来生成MeasureSpec值。
mode必须是UNSPECIFIED EXACLTY AT_MOST三个常量之一,
这里注释也提到,API17以下 这个方法就是这样实现的,传入参数的顺序(不对?)会导致结果的溢出。RelativeLayout就受到此bug的影响,API17以上,修正了这个bug,它会返回更严格的行为。啥意思?看看代码能不能告诉我们答案。
/**
* Use the old (broken) way of building MeasureSpecs.
*/
private static boolean sUseBrokenMakeMeasureSpec = false;
// Older apps may need this compatibility hack for measurement.
sUseBrokenMakeMeasureSpec = targetSdkVersion <= jelly_bean_mr1;<="" code="">
这个变量默认是false,在View的初始化函数里 判断,如果版本17及以下,就是true,
如果是true,makeMeasureSpec就直接把size +mode 作为返回结果了。为什么它敢直接+,这么任性!我还以为它会经过复杂的计算返回给我们MeasureSpec值呢,初看源码的话,肯定会有这种想法。
不过这正印证了我们的结论,由于MeasureSpec用高两位存储测量模式,低30位存储测量值,对于size来说,它的高两位是00,对于mode来说,它的低30位全是0,所以它们相加彼此互不冲突,正好可以用一个int来表示两种相关的信息。
不过如果这么粗暴简单的直接相加,的确称不上严格(strict), 虽然在理想状况下(mode 只有高两位有值,低30位都为0, size只有低30位有值,高位全为0),不会出错。但是如果单方面有异常发生,它会导致mode 和 size的值都混乱。
例如:
如果size传了个超过30位的的值(假设是31位,01xx xxxx xxxx……..),但是mode的值是正确的(为EXACLTY:0100 0000 0…..),按照API17以下的方法,在两者直接相加得到的MeasureSpec里,size由于溢出30位,其值就是剩下的xxxxxx,但是mode由于存储在高2位,它的值也将受到影响,01+01 = 10 ,测量mode将成为AT_MOST的值。
在API大于17的情况下,它返回的值是 return (size & ~MODE_MASK) | (mode & MODE_MASK);
MODE_MASK是高两位为1,其余30位全是0的数,即 11 00 0000 0000 …….
~MODE_MASK,对其取非,则为 高两位为0,其余30全为1的数,即00 11 1111 1111 ……
所以正常情况下,size高两位为0,低30位为测量值, &~MODE_MASK后,不会受到任何影响。而异常情况下,高两位可能不为0,不过就算如此,&~MODE_MASK 后,其高两位一定是00,后30代表测量值,这样就不会发生上例里提到的影响mode的情况。
同理 mode 如果出现异常,经过 &MODE_MASK的位运算后,其后30位一定是0,它的异常也不会波及到size里。
经过&的处理,此时 使用 + 或者 | 都是一样的效果了。只是将mode 和 size合并至一个int里。
这样至少保证,mode size单方面某一个值出错,不会影响到另一个值。
makeMeasureSpec()方法看完了,紧随其后的是makeSafeMeasureSpec()方法。
/**
* 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);
}
这是一个hide的方法,这个方法 和makeMeasureSpec相似,但是如果测量模式是UNSPECIFIED,它会直接返回size为0,它只是为了兼容系统部件和旧的app,内部使用的。查看了一下变量的定义,和赋值,这应该是6.0新增的方法:
/**
* Always return a size of 0 for MeasureSpec values with a mode of UNSPECIFIED
*/
static boolean sUseZeroUnspecifiedMeasureSpec = false;
// In M and newer, our widgets can pass a "hint" value in the size
// for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers
// know what the expected parent size is going to be, so e.g. list items can size
// themselves at 1/3 the size of their container. It breaks older apps though,
// specifically apps that use some popular open source libraries.
sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < M;
我们平时使用还是直接调用MeasureSpec.makeMeasureSpec();就好。
经过上面一番洗礼,现在再看getMode()和getSize()这双子星兄弟就简单多了。
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
/**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
getMode():通过 位与运算,剥掉MeasureSpec的低30位,将MeasureSpec的低30位全部置0,留下高两位的值,即mode值,
getSize():通过位与运算,砍掉MeasureSpec的高两位,将其高两位全部置0,留下低30位的值,正是size的值。
该类还剩最后两个方法没有分析,一个是toString(),忽略~
另外一个就是 adjust()方法,该方法没有注释~在此鄙视一下Google大神,哈哈,不过它是一个
该方法传入MeasureSpec 和一个delta偏移量,给MeasureSpec的size追加上这个delta偏移量,并调用makeMeasureSpec()方法返回MeasureSpec。不过如果是UNSPECIFIED类型,就不调整size。如果调整size后,size小于0,会修正到0。
看到这个方法,我觉得我好像知道了,makeMeasureSpec()方法在API17以后修正了算法的意义,因为在调用adjust方法时,如果传入的delta过大,是会导致size+delta超出30位的,这个时候老的makeMeasureSpec()方法,不仅size值错了,连mode也会被殃及池鱼。
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);
}
不过该方法是protected类型的,我们平时也用不到~。
随便搜了一下,View只在measure()方法里调用了它,
===================================================================
【7 总结】
一个bug引发的血案,求知欲驱使着我从Android源码一路看到java基础知识 计算机组成原理,顺着一路走下来,基本功更加扎实了。
同事昨天问我,你看源码补码的这个有啥用啊, 有啥意义啊,
我想说的是,它不会短时间让我飞多快多高,但它能决定我最终能飞多快多高。
大家一起努力吧!
====================================================================
【8 补充】
我个人觉得 位运算和正则表达式有些相似,所以把收集的一些位运算的用法发出来,
原谅我原文地址忘了,sorry。
1. 判断int型变量a是奇数还是偶数
a&1 = 0 偶数
a&1 = 1 奇数
2. 求平均值,比如有两个int类型变量x、y,首先要求x+y的和,再除以2,但是有可能x+y的结果会超过int的最大表示范围,所以位运算就派上用场啦。
(x&y)+((x^y)>>1);
3. 对于一个大于0的整数,判断它是不是2的几次方
((x&(x-1))==0)&&(x!=0);
4. 比如有两个int类型变量x、y,要求两者数字交换,位运算的实现方法:性能绝对高效
x ^= y;
y ^= x;
x ^= y;
5. 求绝对值
int abs( int x )
{
int y ;
y = x >> 31 ;
return (x^y)-y ; //or: (x+y)^y
}
6. 取模运算,采用位运算实现:
a % (2^n) 等价于 a & (2^n – 1)
7. 乘法运算 采用位运算实现
a * (2^n) 等价于 a << n
8. 除法运算转化成位运算
a / (2^n) 等价于 a>> n
9. 求相反数
(~x+1)
10 a % 2 等价于 a & 1