務(wù)必掌握的Android十六進制狀態(tài)管理最佳實踐
前言
上周在掘金巧遇一篇 “用設(shè)計模式管理狀態(tài)” 文章,作為補充,在評論區(qū)安利我司封裝商業(yè)級 SDK 時常用的 “十六進制狀態(tài)管理機制”。
原以為無人對此感興趣,沒想到留言很快便收到文章作者回復(fù),且在評論區(qū)耐心和我探討設(shè)計模式 獨占式狀態(tài)機 和十六進制 復(fù)合狀態(tài)管理 使用場景區(qū)別。
遺憾的是,通過評論區(qū)只言片語,難讓人體會 “十六進制狀態(tài)管理” 真正魅力,
故今日我們以封裝商業(yè)級 SDK 為例,拆解我們是如何使用十六進制完成狀態(tài)管理,相信閱讀后你會豁然開朗。
我和十六進制的 “三次握手”
最初對十六進制產(chǎn)生興趣,或說知道它何時可派上用場,是 2015 年觀看電影《火星救援》時發(fā)現(xiàn)。
為和 “遠在 4 億公里外、電磁波需 40 分鐘才能完成一次完整請求響應(yīng)” 的地球通信,孑然一身主角 Mark 想到一辦法,即是通過十六進制和 ACSII 碼表:
將字符轉(zhuǎn)換成字母 ,這樣地球上的人便可通過控制 “Mark 從沙漠中掏來的 1997 年服役的探路者號(PathFinder)” 攝像頭偏移角度,來指明一連串字符,從而 Mark 可將它們轉(zhuǎn)譯成英文。
第二次接觸十六進制是在 2016 年,當(dāng)我閱讀 View/ViewGroup 源碼,發(fā)現(xiàn)狀態(tài)多通過十六進制管理,但當(dāng)時因不知為何這么使用、這樣使用究竟有什么好處,也就沒大注意。
@Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) { // We're already in this state, assume our ancestors are too return; } if (disallowIntercept) { mGroupFlags |= FLAG_DISALLOW_INTERCEPT; } else { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // Pass it up to our parent if (mParent != null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); } }
直到 2017 年夏天,我和一位彼時 3 年經(jīng)驗同事聯(lián)手完成核心項目重構(gòu)時,因同事提出使用十六進制管理狀態(tài),而親眼見證十六進制在狀態(tài)管理方面絕佳優(yōu)勢。
為紀念這一分享,此后每當(dāng)有新同事入職,我提供的培訓(xùn)課程必包含十六進制狀態(tài)管理。
使用十六進制前的混沌世界
該項目有個需求:當(dāng)指定圖形編輯模式,圖形工具欄按鈕狀態(tài)需隨之發(fā)生改變。
例如,存在 3 種圖形編輯模式,和 8 個圖形編輯按鈕。
模式 A 下,要求 按鈕1、按鈕2、按鈕3 可用,其余按鈕禁用。
模式 B 下,要求 按鈕1、按鈕4、按鈕5、按鈕6 可用,其余按鈕禁用。
模式 C 下,要求 按鈕1、按鈕7、按鈕8 可用,其余按鈕禁用。
如是傳統(tǒng)方式編寫,我們勢必會在類中為 3 個模式定義 boolean 變量,為 8 個按鈕狀態(tài)定義 boolean 變量。
那么模式切換時,就需將每個按鈕狀態(tài)的變量都 “清洗” 一遍。例如:
public void setModeA() { status1 = true; status2 = true; status3 = true; status4 = false; status5 = false; status6 = false; status7 = false; status8 = false; } public void setModeB() { status1 = true; status2 = false; status3 = false; status4 = true; status5 = true; status6 = true; status7 = false; status8 = false; } public void setModeC() { ... }
當(dāng)日后模式變多、按鈕狀態(tài)變多,類中就會滿是這種 setMode 方法,看起來很蠢,且密密麻麻的 true、false 極易出錯。
這是一點。
另一點是,如按鈕狀態(tài)是用 boolean 變量管理,那么狀態(tài)的存儲和讀取便難辦,
- 每個 boolean 變量都需轉(zhuǎn)換成 int 類型 0 或 1 存儲在數(shù)據(jù)庫中。
- 數(shù)據(jù)庫需為每個狀態(tài)準備一個字段。
- 讀取時又需負責(zé)將每個狀態(tài)轉(zhuǎn)譯回 boolean。
這工作量很大,且日后每添加或修改一狀態(tài),數(shù)據(jù)庫都需新增或修改字段,十分低效和不安全。
十六進制能很好解決這些問題
十六進制可做到:
- 通過狀態(tài)集的注入,一行代碼即可完成模式切換。
- 無論再多狀態(tài),都只需一個字段來存儲。狀態(tài)被存放在 int 類型狀態(tài)集中,可直接讀寫于數(shù)據(jù)庫。
十六進制運作機制
在具體了解十六進制是怎么做到狀態(tài)管理最佳實踐前,我們先簡單過一遍十六進制本身運作機制。
首先,在編程中,利用開頭 0x 表示十六進制數(shù)。
例如 0x0001,0x0002。
然后,十六進制的計算,可借助二進制 “按位計算” 方式理解。
二進制存在 與、或、異或、取反 等操作:
a & b,a | b,a ^ b,~a
例如,十六進制數(shù) 0x0004 | 0x0008,可理解為:
0100
|
1000
=
1100
十六進制 (0x0004 | 0x0008) & 0x0004 可得:
1100
&
0100
=
0100
也即 “狀態(tài)集” 包含某狀態(tài)時,再 & 該狀態(tài),便得非 0 結(jié)果。
于是,我們便可利用該特性完成狀態(tài)管理:
十六進制狀態(tài)管理實戰(zhàn)
- 首先定義一個 “狀態(tài)集” 變量,用于存放 “當(dāng)前狀態(tài)集”,例如:
private int STATUSES;
- 然后定義十六進制狀態(tài)常量,和 “模式狀態(tài)集”,例如:
private final int STATUS_1 = 0x0001; private final int STATUS_2 = 0x0002; private final int STATUS_3 = 0x0004; private final int STATUS_4 = 0x0008; private final int STATUS_5 = 0x0010; private final int STATUS_6 = 0x0020; private final int STATUS_7 = 0x0040; private final int STATUS_8 = 0x0080; ? private final int MODE_A = STATUS_1 | STATUS_2 | STATUS_3; private final int MODE_B = STATUS_1 | STATUS_4 | STATUS_5 | STATUS_6; private final int MODE_C = STATUS_1 | STATUS_7 | STATUS_8;
- 當(dāng)需往 “狀態(tài)集” 添加狀態(tài)時,就通過 “或” 運算。例如:
STATUSES | STATUS_1
- 當(dāng)需從 “狀態(tài)集” 移除狀態(tài)時,就通過 “取反” 運算。例如:
STATUSES & ~ STATUS_1
- 當(dāng)需判斷 “狀態(tài)集” 是否包含某狀態(tài)時,就通過 “與” 運算。結(jié)果為 0 即代表無,反之有。
public static boolean isStatusEnabled(int statuses, int status) { return (statuses & status) != 0; }
- 當(dāng)需切換模式時,可直接將預(yù)先定義的 “模式狀態(tài)集” 賦予給 “狀態(tài)集” 變量。例如:
STATUSES = MODE_A;
如此,復(fù)雜度從 m * n 驟減為 m + n,隨著日后模式和狀態(tài)增多,十六進制優(yōu)勢將指數(shù)級增長。
是不是超簡潔?再也無需定義和修改各種 “setModeXXX” 方法。
而且這還只是一半。另一半是關(guān)于十六進制狀態(tài)的存取。
十六進制狀態(tài)存取實戰(zhàn)
由于狀態(tài)集是 int 類型,因而我們最少只需一個字段,即可存儲狀態(tài)集:
insert into tableXXX TITLE,DATE,STATUS values ('xxx','20190703',32)
讀取也十分簡單,讀取后直接賦值給 STATUSES 即可。
除此之外,你還可直接在 SQL 中通過按位計算來查詢。例如查詢包含狀態(tài) 0x0004 的記錄:
select * from tableXXX where STATUS & 4 != 0
小結(jié)
未用十六進制的日子里,狀態(tài)管理是個繁瑣、極易出錯的操作。
有了十六進制后:
- 模式管理復(fù)雜度從 m * n 驟減至 m + n。
- 模式切換無需手動清洗,只需為狀態(tài)集變量注入預(yù)置的常量狀態(tài)集。
- 模式存取一步到位。
- 模式存儲只需一個數(shù)據(jù)表字段。
- 可直接在數(shù)據(jù)庫中完成查詢狀態(tài)。
作為額外附贈的答疑
Q1: 細心朋友可能注意到,聲明狀態(tài)都是 1、2、4、8,然后進一位繼續(xù)。
為何這樣使用?
因為當(dāng)十六進制轉(zhuǎn)成二進制計算時,十六進制每位數(shù)占 4 個二進制位,例如:
0x0001 0x0004 0x0020
?? ?? ??
0001 0100 0010 0000
且唯有獨占每個二進制位,我們才能區(qū)分出不同狀態(tài)。
因而我們對十六進制數(shù)的每一位只安排 1、2、4、8,一旦用完,就前進一位繼續(xù)。
Q2: 既然如此,狀態(tài)聲明為何不直接用二進制來表示?
原因很簡單 —— 一目了然 0x4218 和密密麻麻 0100001000011000b,在代碼中聲明哪個更費時、更費眼、更易出錯? —— 特別是當(dāng)有 20、30 個狀態(tài)要聲明呢?
以上就是務(wù)必掌握的Android十六進制狀態(tài)管理最佳實踐的詳細內(nèi)容,更多關(guān)于Android十六進制狀態(tài)管理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
教你3分鐘了解Android 簡易時間軸的實現(xiàn)方法
本篇文章主要介紹了教你3分鐘了解Android 簡易時間軸的實現(xiàn)方法,具有一定的參考價值,有興趣的可以了解一下2017-07-07Kotlin構(gòu)造函數(shù)與成員變量和init代碼塊執(zhí)行順序詳細講解
這篇文章主要介紹了Kotlin構(gòu)造函數(shù)與成員變量和init代碼塊執(zhí)行順序,kotlin里面的構(gòu)造函數(shù)分為主構(gòu)造函數(shù)和次構(gòu)造函數(shù)。主構(gòu)造函數(shù)只能有一個,次構(gòu)造函數(shù)個數(shù)不限制,可以有一個或者多個2022-11-11Android使用addView動態(tài)添加組件的方法
這篇文章主要為大家詳細介紹了Android使用addView動態(tài)添加組件的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-09-09Android?Studio?2022.1.1創(chuàng)建項目的Gradle配置問題
這篇文章主要介紹了Android?Studio?2022.1.1創(chuàng)建項目的Gradle配置問題,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04