編輯:關於Android編程
【1 引言】
本文來源於一個bug,後來越走越遠跑偏了,從LinearLayouy-》View 》-MeasureSpec-》位運算-》計算機的編碼(原碼反碼補碼)這已經到計算機組成原理了~~於是權當做一次筆記記錄:
起源的bug:
使用流式布局時,布局內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)
那麼這個位運算是干嘛的,<<我記得是向左移位,看一下MODE_SHIFT 的定義,是30.那就是向左移30位,為什麼是30位呢?這個疑問先丟這裡,前有 & ,後有<<。我們可以好好去看一下位運算了。
===================================================================
【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。
<<=
左移位賦值。
>>=
右移位賦值。
>>>=
無符號右移位賦值。
&=
按位與賦值。
|=
按位或賦值。
^=
按位異或賦值。
以 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: *
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;這個變量默認是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是奇數還是偶數
目錄:1.Dialog概述2.Dialog中的重要角色3.常見幾種類型的Dialog簡單使用3.1 提示型Dialog實現3.2 List型Dialog實現3.3 單選型
在現有的技術條件下,內存永遠都是一個吃緊的資源,不用說是PC上會出現內存不足的可能,更不必說在移動設備上了。一旦出現內存不足就會導致系統卡頓,影響用戶體驗。而
作為Android開發者,工作中少不了要反編譯別人的apk,當然主要目的還是為了學習到更多,取彼之長,補己之短。今天就來總結一下Android反編譯和二次打包的一些知識。
在這裡簡單的介紹下怎麼統計自己研發的APP 的用戶活躍度,和使用量,以此來展示自己APP的用戶使用量!我們的APP都需要注入數據分析,以供我們實時的了解APP的下載和使用