為什麼Android原始碼中都使用16進位制進行狀態管理?

jimuzz發表於2021-06-01

前言

在Android原始碼中,對於“多狀態”的管理總是通過16進位制數字來表示,類似這種格式:

//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;

那麼,你有沒有想過為什麼遇到多狀態的管理,就需要用到16進位制?

簡單的狀態表示

來舉個實際的例子,我們作為一個人,身上肯定會有很多標籤,比如帥氣、可愛、博學、機智、懶惰、小氣

針對這些標籤,我們就可以設定不同的人設:

//定義實體類
	data class Person(var tag : String)

//修改標籤
	val person1 = Person("帥氣")

 //判斷標籤
 	fun isCute():Boolean{
 		return person1.tag == "可愛"
 	}

當一個人只有一個標籤的時候是很簡單的,直接賦值或者取值判斷即可。但是,如果一個人有多個標籤呢?

也很簡單,使用集合儲存即可:

    val person2 = Person(mutableListOf())
    person2.tags.add("帥氣")
    person2.tags.add("可愛")

    person2.tags.remove("可愛")

    person2.tags.contains("可愛")	

但是用到集合之後,這個計算就變得比較複雜了,由於removecontains方法都是通過遍歷集合的方式實現的,從時間複雜度角度看的話,當刪除某個標籤或者判斷某個標籤是否存在的時間複雜度都是O(n)

有沒有什麼辦法讓多個標籤也像剛才的單個標籤那麼簡單地使用操作呢?

二進位制運算

當然有啦,不然這篇文章也不會有了,在這之前,我們先複習下二進位制的幾種運算。

  • 1、按位與(&)

當兩個對應位的值都為1,則結果為1,否則為0。

舉例:0x1 & 0x4

0001 &
0100
     =
0000
  • 2、按位或(|)

當兩個對應位的值都只要有一位是1,則結果為1。

舉例:0x1 | 0x4

0001 |
0100
     =
0101
  • 3、取反( ~ )

將一個數按位取反。

舉例:~ 0x1

0001 ~
     =
1110     

好了,有了這三種運算,我們的狀態管理就足夠了。

引入16進位制

接下來,就來完成一個完整的狀態管理例子。

//設定所有狀態對應的16進位制值

//可愛,對應二進位制0001
val TAG_CUTE = Ox1  
//帥氣,對應二進位制0010 
val TAG_HANDSOME = Ox2
//博學,對應二進位制0100
val TAG_LEARNED = Ox4

var personTag = 0

狀態增加

如果一個二進位制數字想留下另一個二進位制數字的痕跡(數字1的痕跡),我們可以通過或運算,這樣只要第二個數字某位上有1,那麼最終的結果在同樣的位數肯定也是1。

所以,我們可以通過這個方法來完成狀態增加的功能:

//增加可愛狀態
personTag |= TAG_CUTE

0000 |
0001 
=
0001

這樣操作之後,personTag的第四位上的數字就為1了,也就帶有TAG_CUTE這個標記了。

狀態移除

按照上述的邏輯,狀態的移除其實就是需要把對應的位數從1改為0。

假設personTag現在的值變成了二進位制數0111

如果要刪除TAG_CUTE屬性,就需要把第四位的1改為0。那麼我們可以做的操作就是先對TAG_CUTE取反,也就是把0001,變成了1110。然後再和personTag進行與運算,這樣第四位肯定就會變為0,而其他位上面的值不變。

//personTag為二進位制數0111
personTag &= ~TAG_CUTE

0001 ~
=
1110 &
0111
=
0110

完成對TAG_CUTE狀態的移除。

狀態判斷

同理,對是否有某個狀態的判斷,其實就是判斷在某個位上是否值為1。
所以我們只需要對狀態進行 與運算,如果結果為0,就代表沒有這個狀態,否則就代表有這個狀態。

//personTag為二進位制數0111
(personTag & TAG_CUTE) != 0

0111 &
0001
=
0001

結果不為0,所以代表personTag 包含了 TAG_CUTE 這個狀態。

注意的點

細心的朋友可能會發現,剛才我們用到的16進位制值,跳過了Ox3這個值,這是為什麼呢?

其實不難發現,所謂的通過16進位制管理狀態,其實是通過二進位制來管理狀態,歸根結底是通過二進位制中的1所在的位數來進行管理。

所以我們對狀態賦值,需要選取單獨佔有一位的二進位制值,比如 0001 ,0010,0100,1000,10000等等。

如果用了其他值會發生什麼呢?舉個例子,增加Ox3的TAG。

//懶惰,對應二進位制0011
val TAG_LAZY = Ox3


//增加可愛狀態
personTag |= TAG_CUTE
//增加帥氣狀態
personTag |= TAG_HANDSOME

在我們增加了可愛和帥氣狀態之後,personTag的二進位制值為 0011

這時候再對它進行判斷,是否含有懶惰狀態:

//是否含有懶惰狀態
(personTag & TAG_LAZY) != 0

0011 &
0011 
=
0011

結果不為0,難道我們增加了懶惰狀態嗎?很明顯沒有,我不懶但是卻說我懶,這是誣陷!

所以你明白狀態取值的範圍了嗎?

為什麼是16進位制?

到此,通過16進位制管理狀態的功能已經實現了,很明顯這種方式管理狀態要簡便許多,其根本原理就是通過二進位制的計算來完成對狀態的管理。

有人又要問了,既然本質是通過二進位制來完成管理,那麼用10進位制來表示也可以啊,比如上述的例子:

//設定所有狀態對應的10進位制值

//可愛,對應二進位制0001
val TAG_CUTE = 1  
//帥氣,對應二進位制0010 
val TAG_HANDSOME = 2
//博學,對應二進位制0100
val TAG_LEARNED = 4

var personTag = 0

這跟16進位制不是一樣麼?

從根本來說,確實是一樣的,但是16進位制有16進位制的好處,這就涉及到16進製為什麼被設計出來的原因了。

在計算機中,一個位元組有八位,最大值為 1111 1111。對應的10進位制數是255,對應的16進位制是 FF。
所以半個位元組用16進位制是可以通過一個字母就能表示,而轉換成10進位制就是一個無規律的數字。
為了方便,程式碼中一般使用16進位制來表示 二進位制,就是因為其可以和二進位制進行一個更方便直觀的轉換。

總結

今天和大家介紹了下原始碼中常用的通過16進位制轉換2進位制來管理狀態的方法。

簡單的、基礎的道理解決大問題,這也許就是大道從簡的含義?

拜拜

感謝大家的閱讀,有一起學習的小夥伴可以關注下我的公眾號——碼上積木❤️❤️
每日一個知識點,積少成多,建立知識體系架構。
這裡有一群很好的Android小夥伴,歡迎大家加入~

相關文章