就算不去火星種土豆,也請務必掌握的 Android 狀態管理最佳實踐!

前言

很高興見到你!java

上上週我在掘金碰巧遇到了一篇 用設計模式管理狀態 的文章,一時興奮不已,在評論區安利了,一直以來我司在封裝商業級 SDK 時,使用的十六進制狀態管理機制。sql

原覺得會無人對此感興趣,沒想到,留言很快就收到文章做者的回覆,而且在評論區耐心地和我探討了設計模式的 獨佔式狀態機 和十六進制的 複合狀態管理 在使用場景上的區別。數據庫

遺憾的是,經過評論區的隻言片語,並不能讓人體會到 十六進制狀態管理 的真正魅力。編程

因而做爲回饋,我特意分享了這一篇:當咱們封裝商業級 SDK 時,咱們是怎麼使用十六進制來完成狀態管理。設計模式

😉安全

此外,是隻有封裝 SDK 這種大動做,才值得使用十六進制嗎?不是的,偏偏相反,正由於 十六進制狀態管理是如此地普適,乃至於連封裝 SDK 都優先使用這種方式。bash

考慮到部分讀者可能對十六進制自己不太瞭解,本文會連同十六進制一塊兒介紹。ide

因此若是閱讀完這篇文章,你對 十六進制的狀態管理 有了感性的認識,那個人願望也就達到了。工具

我和十六進制的 「三次握手」

最開始對十六進制產生了興趣,或者說,知道了它在何時能派上用場,是在 2015 年觀看《火星救援》這部電影時發現的。ui

13.gif

爲了和 「遠在 4 億千米外、電磁波須要 40 分鐘才能完成一次完整的請求響應的」 地球通訊,形單影隻的主角 Mark 想到了一個辦法,就是經過十六進制和 ACSII 碼錶:

將字符轉換成字母 ,這樣地球上的人就能夠經過控制 「Mark 從沙漠中掏來的、1997 年服役的」 探路者號(PathFinder)的攝像頭偏移角度,來指明一連串的字符,從而 Mark 能夠將它們轉譯成英文。

12.gif

第二次接觸十六進制是在 2016 年,當我閱讀 View/ViewGroup 源碼時,發現一些狀態標記都是經過十六進制狀態管理,但當時由於不知道爲什麼這麼使用、這樣使用究竟有什麼好處,也就沒大注意。

@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 年經驗的同事,聯手完成當年扛鼎項目的核心功能時,因同事提出使用十六進制管理狀態,而親眼見證了十六進制在狀態管理方面的絕佳優點。

爲了記念同事的這一分享,此後每當有新同事入職,我提供的培訓課程必包含十六進制狀態管理。

4.png

使用十六進制前的混沌世界

該項目有個需求:當指定圖形編輯的模式時,圖形工具欄的按鈕狀態要隨之發生配套性地變化。

例如,存在 3 種圖形編輯模式,和 8 個圖形編輯按鈕。

模式 A 下,要求 按鈕一、按鈕二、按鈕3 可用,其餘按鈕禁用。

模式 B 下,要求 按鈕一、按鈕四、按鈕五、按鈕6 可用,其餘按鈕禁用。

模式 C 下,要求 按鈕一、按鈕七、按鈕8 可用,其餘按鈕禁用。

map.png

若是是傳統方式編寫,咱們勢必會在類中爲 3 個模式定義 boolean 變量,爲 8 個按鈕狀態定義 boolean 變量。

那麼在模式切換時,就須要將每一個按鈕狀態的變量都 「清洗」 一遍。例如:

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() {
    ...
}
複製代碼

那要是往後模式變多、按鈕狀態變多,類中就會盡是這種 setMode 的方法,看起來很蠢,並且密密麻麻的 true、false,極容易出錯。

這是一點。

另外一點就是,若是按鈕狀態是用 boolean 變量來管理,那麼狀態的存儲和讀取怎麼辦呢?

  • 每一個 boolean 變量都要轉換成 int 類型的 0 或 1 存儲在數據庫中。
  • 數據庫須要爲每一個狀態準備一個字段。
  • 讀取的時候又要負責將每一個狀態轉譯回 boolean。

這工做量也太大了!並且往後每添加或修改一個狀態,數據庫都要新增或修改字段,這很是低效和不安全!

十六進制能很好地解決這些問題

十六進制能夠作到:

  • 經過狀態集的注入,一行代碼便可完成模式的切換。
  • 不管再多的狀態,都只須要一個字段來存儲。狀態被存放在 int 類型的狀態集中,能夠直接向數據庫寫入或讀取。

十六進制的運做機制

在具體瞭解十六進制是怎麼作到狀態管理最佳實踐以前,咱們先簡單過一遍十六進制自己的運做機制。

首先,在編程中,利用開頭 0x 表示十六進制數。

例如 0x0001,0x0002。

而後,十六進制的計算,咱們能夠藉助二進制的 「按位計算」 方式來理解。

二進制存在 與、或、異或、取反 等操做:

a & b,a | b,a ^ b,~a
複製代碼

例如,十六進制數 0x0004 | 0x0008,能夠理解爲:

0100 
 |
1000
 =
1100
複製代碼

十六進制 (0x0004 | 0x0008) & 0x0004 能夠獲得:

1100 
 &
0100
 =
0100
複製代碼

也即狀態集中包含某狀態時,再與上該狀態,就會獲得非 0 的結果。

因而,咱們就能夠利用這個特性來完成狀態管理:

十六進制的狀態管理實戰

  • 首先咱們定義一個狀態集變量,用來存放當前模式的狀態集,例如:
private int STATUSES;
複製代碼
  • 而後咱們定義十六進制狀態常量,和模式狀態集,例如:
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;
複製代碼
  • 當咱們須要往狀態集中添加狀態時,就經過或運算。例如:
STATUSES | STATUS_1
複製代碼
  • 當咱們須要從狀態集中移除狀態時,就經過取反運算。例如:
STATUSES & ~ STATUS_1
複製代碼
  • 當咱們須要判斷狀態集中是否包含某狀態時,就經過與運算。結果爲 0 即表明無,反之有。
public static boolean isStatusEnabled(int statuses, int status) {
   return (statuses & status) != 0;
}
複製代碼
  • 當咱們須要切換模式時,咱們能夠直接將預先定義好的 「模式狀態集」 賦予給狀態集變量。例如:
STATUSES = MODE_A;
複製代碼

如此,複雜度從 m * n 驟減爲 m + n,隨着往後模式和狀態的增多,十六進制的優點將指數級增加!

是否是超簡潔?不再須要定義和修改各類 「setModeXXX」 方法了。

並且這還只是一半。另外一半是關於十六進制狀態的存取。

十六進制的狀態存取實戰

因爲狀態集是 int 類型,於是咱們最少只需一個字段,便可存儲狀態集:

insert into tableXXX TITLE,DATE,STATUS values ('xxx','20190703',32)
複製代碼

讀取也十分簡單,讀取後直接賦值給 STATUSES 便可。

除此以外,你還能夠直接在 SQL 中經過按位計算來查詢!例如查詢包含狀態 0x0004 的記錄:

select * from tableXXX where STATUS & 4 != 0
複製代碼

綜上

在沒有十六進制的日子裏,狀態管理是個繁瑣的、極易出錯的操做。

有了十六進制後:

  • 模式管理的複雜度從 m * n 驟減至 m + n。
  • 模式的切換無需手動清洗,只需爲狀態集變量注入預置的常量狀態集。
  • 模式的存取一步到位。
  • 模式的存儲只需一個數據表字段。
  • 可直接在數據庫中完成查詢狀態。

這樣說,你理解了嗎?

xzl短

看不過癮?這裏只爲你 而準備了一份 簡潔有力的 《重學安卓》認知地圖 😉

做爲額外附贈的答疑

Q1: 細心的朋友可能注意到了,上文聲明的狀態都是 一、二、四、8,而後進一位繼續。

爲何這樣使用呢?

由於當十六進制轉成二進制來計算時,十六進制的每位數佔 4 個二進制位,例如:

0x0001  0x0004    0x0020
   👇      👇        👇
  0001    0100   0010 0000
複製代碼

而且,惟有獨佔每個二進制位,咱們才能區分出不一樣的狀態。

於是咱們對十六進制數的每一位只安排 一、二、四、8,一旦用完,就前進一位繼續。

Q2: 那既然如此,狀態的聲明爲什麼不直接用二進制來表示呢?

—— 一目瞭然的 0x4218 和密密麻麻的 0100001000011000b,在代碼中聲明 哪一個更費時、更容易出錯呢? —— 特別是當有 20、30 個狀態要聲明的時候呢?

相關文章
相關標籤/搜索