前言
在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("可愛")
但是用到集合之後,這個計算就變得比較複雜了,由于
remove
和
contains
方法都是通過周遊集合的方式實作的,從時間複雜度角度看的話,當删除某個标簽或者判斷某個标簽是否存在的時間複雜度都是
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小夥伴,歡迎大家加入~