為什么源碼中都使用16進制進行狀態(tài)管理?
前言
在Android源碼中,對于“多狀態(tài)”的管理總是通過16進制數(shù)字來表示,類似這種格式:
- //ViewGroup.java
 - protected int mGroupFlags;
 - static final int FLAG_CLIP_CHILDREN = 0x1;
 - private static final int FLAG_CLIP_TO_PADDING = 0x2;
 - static final int FLAG_INVALIDATE_REQUIRED = 0x4;
 - private static final int FLAG_RUN_ANIMATION = 0x8;
 - static final int FLAG_ANIMATION_DONE = 0x10;
 - private static final int FLAG_PADDING_NOT_NULL = 0x20;
 
那么,你有沒有想過為什么遇到多狀態(tài)的管理,就選擇用16進制?
簡單的狀態(tài)表示
來舉個實際的例子,我們作為一個人,身上肯定會有很多標(biāo)簽,比如帥氣、可愛、博學(xué)、機智、懶惰、小氣。
針對這些標(biāo)簽,我們就可以設(shè)定不同的人設(shè):
- //定義實體類
 - data class Person(var tag : String)
 - //修改標(biāo)簽
 - val person1 = Person("帥氣")
 - //判斷標(biāo)簽
 - fun isCute():Boolean{
 - return person1.tag == "可愛"
 - }
 
當(dāng)一個人只有一個標(biāo)簽的時候是很簡單的,直接賦值或者取值判斷即可。但是,如果一個人有多個標(biāo)簽?zāi)?
也很簡單,使用集合存儲即可:
- val person2 = Person(mutableListOf())
 - person2.tags.add("帥氣")
 - person2.tags.add("可愛")
 - person2.tags.remove("可愛")
 - person2.tags.contains("可愛")
 
但是用到集合之后,這個計算就變得比較復(fù)雜了,由于remove和contains方法都是通過遍歷集合的方式實現(xiàn)的,從時間復(fù)雜度角度看的話,當(dāng)刪除某個標(biāo)簽或者判斷某個標(biāo)簽是否存在的時間復(fù)雜度都是O(n)。
有沒有什么辦法讓多個標(biāo)簽也像剛才的單個標(biāo)簽?zāi)敲春唵蔚厥褂貌僮髂?
二進制運算
當(dāng)然有啦,不然這篇文章也不會有了,在這之前,我們先復(fù)習(xí)下二進制的幾種運算。
- 1、按位與(&)
 
當(dāng)兩個對應(yīng)位的值都為1,則結(jié)果為1,否則為0。
舉例:0x1 & 0x4
- 0001 &
 - 0100
 - =
 - 0000
 
- 2、按位或(|)
 
當(dāng)兩個對應(yīng)位的值都只要有一位是1,則結(jié)果為1。
舉例:0x1 | 0x4
- 0001 |
 - 0100
 - =
 - 0101
 
- 3、取反( ~ )
 
將一個數(shù)按位取反。
舉例:~ 0x1
- 0001 ~
 - =
 - 1110
 
好了,有了這三種運算,我們的狀態(tài)管理就足夠了。
引入16進制
接下來,就來完成一個完整的狀態(tài)管理例子。
- //設(shè)定所有狀態(tài)對應(yīng)的16進制值
 - //可愛,對應(yīng)二進制0001
 - val TAG_CUTE = Ox1
 - //帥氣,對應(yīng)二進制0010
 - val TAG_HANDSOME = Ox2
 - //博學(xué),對應(yīng)二進制0100
 - val TAG_LEARNED = Ox4
 - var personTag = 0
 
狀態(tài)增加
如果一個二進制數(shù)字想留下另一個二進制數(shù)字的痕跡,我們可以通過或運算,這樣只要第二個數(shù)字某位上有1,那么最終的結(jié)果在同樣的位數(shù)肯定也是1。
所以,我們可以通過這個方法來完成狀態(tài)增加的功能:
- //增加可愛狀態(tài)
 - personTag |= TAG_CUTE
 - 0000 |
 - 0001
 - =
 - 0001
 
這樣操作之后,personTag的第四位上的數(shù)字就為1了,也就帶有TAG_CUTE這個標(biāo)記了。
狀態(tài)移除
按照上述的邏輯,狀態(tài)的移除其實就是需要把對應(yīng)的位數(shù)從1改為0。
假設(shè)personTag現(xiàn)在的值變成了二進制數(shù)0111。
如果要刪除TAG_CUTE屬性,就需要把第四位的1改為0。那么我們可以做的操作就是先對TAG_CUTE取反,也就是把0001,變成了1110。然后再和personTag進行與運算,這樣第四位肯定就會變?yōu)?,而其他位上面的值不變。
- //personTag為二進制數(shù)0111
 - personTag &= ~TAG_CUTE
 - 0001 ~
 - =
 - 1110 &
 - 0111
 - =
 - 0110
 
完成對TAG_CUTE狀態(tài)的移除。
狀態(tài)判斷
同理,對是否有某個狀態(tài)的判斷,其實就是判斷在某個位上是否值為1。所以我們只需要對狀態(tài)進行 與運算,如果結(jié)果為0,就代表沒有這個狀態(tài),否則就代表有這個狀態(tài)。
- //personTag為二進制數(shù)0111
 - (personTag & TAG_CUTE) != 0
 - 0111 &
 - 0001
 - =
 - 0001
 
結(jié)果不為0,所以代表personTag 包含了 TAG_CUTE 這個狀態(tài)。
注意的點
細心的朋友可能會發(fā)現(xiàn),剛才我們用到的16進制值,跳過了Ox3這個值,這是為什么呢?
其實不難發(fā)現(xiàn),所謂的通過16進制管理狀態(tài),其實是通過二進制來管理狀態(tài),歸根結(jié)底是通過二進制中的1所在的位數(shù)來進行管理。
所以我們對狀態(tài)賦值,需要選取單獨占有一位的二進制值,比如0001 ,0010,0100,1000,10000等等。
如果用了其他值會發(fā)生什么呢?舉個例子,增加Ox3的TAG。
- //懶惰,對應(yīng)二進制0011
 - val TAG_LAZY = Ox3
 - //增加可愛狀態(tài)
 - personTag |= TAG_CUTE
 - //增加帥氣狀態(tài)
 - personTag |= TAG_HANDSOME
 
在我們增加了可愛和帥氣狀態(tài)之后,personTag的二進制值為 0011。
這時候再對它進行判斷,是否含有懶惰狀態(tài):
- //是否含有懶惰狀態(tài)
 - (personTag & TAG_LAZY) != 0
 - 0011 &
 - 0011
 - =
 - 0011
 
結(jié)果不為0,難道我們增加了懶惰狀態(tài)嗎?很明顯沒有,我不懶但是卻說我懶,這是誣陷!
所以你明白狀態(tài)取值的范圍了嗎?
為什么是16進制?
到此,通過16進制管理狀態(tài)的功能已經(jīng)實現(xiàn)了,很明顯這種方式管理狀態(tài)要簡便許多,其根本原理就是通過二進制的計算來完成對狀態(tài)的管理。
有人又要問了,既然本質(zhì)是通過二進制來完成管理,那么用10進制來表示也可以啊,比如上述的例子:
- //設(shè)定所有狀態(tài)對應(yīng)的10進制值
 - //可愛,對應(yīng)二進制0001
 - val TAG_CUTE = 1
 - //帥氣,對應(yīng)二進制0010
 - val TAG_HANDSOME = 2
 - //博學(xué),對應(yīng)二進制0100
 - val TAG_LEARNED = 4
 - var personTag = 0
 
這跟16進制不是一樣么?
從根本來說,確實是一樣的,但是16進制有16進制的好處,這就涉及到16進制為什么被設(shè)計出來的原因了。
在計算機中,一個字節(jié)有八位,最大值為 1111 1111。對應(yīng)的10進制數(shù)是255,對應(yīng)的16進制是 FF。所以半個字節(jié)用16進制是可以通過一個字母就能表示,而轉(zhuǎn)換成10進制就是一個無規(guī)律的數(shù)字。為了方便,代碼中一般使用16進制來表示 二進制,就是因為其可以和二進制進行一個更方便直觀的轉(zhuǎn)換。
總結(jié)
今天和大家介紹了下源碼中常用的通過16進制轉(zhuǎn)換2進制來管理狀態(tài)的方法。
簡單的、基礎(chǔ)的道理解決大問題,這也許就是大道從簡的含義?















 
 
 







 
 
 
 