一件Intent教我的事: Bitwise Operation

A.K.A. Learning bitwise operation from Intent(Android Framework)

Carter Chen
10 min readJul 6, 2019
Credit:https://www.maxpixel.net/photo-2925912

寫在前面

最近開發了一個功能,code review 的時候夥伴告訴我其實也可以使用 Bitwise 的方式來表示功能中的大概將近十個表示狀態的 boolean 值。我對 Bitwise 本來也就只有停留在聽過的階段,剛好趁這次的機會學習了一下,並且這邊分享一下學習的結果。

給個栗子🌰

「三八阿花吹喇叭~DO~SO~LA~SI~FA~」

還記得楊祐寧在電影「總舖師」裡面曾說:「就算是番茄炒蛋,每個媽媽做出來的味道都不一樣。」

沒錯,番茄炒蛋是記憶中的味道,也不管你到底有沒有吃過媽媽做的番茄炒蛋,或是根本你媽媽就不會做番茄炒蛋,今天的例子就是要用番茄炒蛋。

這會是個什麼樣的例子呢?讓我們繼續看下去:

假設今天有一個餐館(Restaurant),他有一道料理,叫番茄炒蛋(Tomato Egg Stir-fry)。每當客人點菜的時候,餐館會為客人提供(serve — )這道料理,所以會請廚師(chef)來進行料理(cook — )。

但廚師要料理前,會先確認番茄炒蛋的原料(番茄、蛋、油)是否備齊,否則,他就不工作。

寫成code之後大概會長成這樣:

上面使用了early-return,其實也可以改成下面這樣:

目前這樣看起來閱讀性非常地好呀!非常地清楚明瞭,不是嗎?

但目前是因為只有3種狀態:

  • isTomatoPrepared
  • isEggPrepared
  • isOilPrepared

當如果有更多的狀態要保存,就必須列舉更多的boolean,用掉更多的記憶體,但目標卻只有一個 :讓廚師決定要不要開始做番茄炒蛋。

其實有一個我覺得算是比較難以理解,但卻非常精緻(Elegant)的解法:

「Bitwise Operation」。

Bitwise Operation in Android

身為一個Android工程師,我想大家一定或多或少都用過這個神奇的東西,就拿startActivity()來說吧:

Java版:

Kotlin版:

從範例中可以看到,Android Framework下的Intent類別,就有個setFlags()方法,我們透過setFlags()指定了

FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP

intent這個instance。

這邊的「|」就是bitwise operator的「or」,是一個bitwise operator,詳細可參考下面的網站,有進一步的解說:

Bitwise Operator

我們這邊就引用維基百科的資料,快速的帶大家了解一下bitwise operator:

AND(在Java中以&表示)

定義:兩個相同長度的二進位數值進行AND運算,如果在相同位置上的位元數值「都是1」的話,運算後在對應位置上的結果也會為1:

    0101 (十進位的5)
AND 0011 (十進位的3)
= 0001 (十進位的1)

因為0101的第一個位元和0011的第一個位元都是1,其他位置並沒有都是1,所以AND運算的結果就是0001

    0110 (十進位的6)
AND 0001 (十進位的1)
= 0000 (十進位的0)

因為01100001並沒有相同位置上都是1的部分,所以AND運算的結果就是0000

OR(在Java中以|表示)

定義:兩個相同長度的二進位數值進行OR運算,如果在相同位置上的位元數值「有一者為1」的話,運算後在對應位置上的結果會是1:

   0101 (十進位的5)
OR 0011 (十進位的3)
= 0111 (十進位的7)

因為0101的第一個位元與第三個位元都是1,做OR運算時可以斷定結果的第一個位元與第三個位元都是1。

然後0011的第一個位元與第二個位元都是1,做OR運算時可以斷定結果的第一個位元與第二個位元都是1。

所以可以得出結果的第一個、第二個與第三個位元都是1,也就是0111

   0110 (十進位的6)
OR 0001 (十進位的1)
= 0111 (十進位的7)

因為0110的第二個位元與第三個位元都是1,做OR運算時可以斷定結果的第二個位元與第三個位元都是1。

然後0001的第一個位元是1,做OR運算時可以斷定結果的第一個位元是1。

所以可以得出結果的第一個、第二個與第三個位元都是1,也就是0111

Intent用Bitwise operator來幹嘛?

AND與OR的運算與使用方法可以在Intent這個類別中看得非常清楚(請搭配服用source code)。當我們要啟動Activity時,有時候會特別透過setFlags()來指定flag,來讓啟動的Activity達到我們想要的效果:

看一下Intent的source code,可以發現setFlags()mFlags的setter方法:

mFlags保存的狀態有這些,光和Activity有關的就有22種狀態:

坦白說如果今天因為要記錄每個flag個別的狀態,而根據每個情況都設計一個boolean來進行保存,在不同的地方針對不同的flag去指定truefalse,這樣在flag的數量一多時看起來真的會滿亂的……例如我想在執行A()的時候改變一下狀態,執行B()的時候改變一下狀態,寫起來可能會變成:

那在Intent中是怎麼去管理這麼多flag的狀態呢?它使用了int這個primitive data type來保存。在Java中,int是一個容量為32bit的資料類型,而一個bit可以是1或0,所以可以想像成:

1與0可以想像成「有」與「無」、「是」與「非」、「對」與「錯」:

由此可見一個int可以容納32個truefalse,我們可以使用一個int來代表至多32個truefalse的組合。

FLAG_ACTIVITY_CLEAR_TOPFLAG_ACTIVITY_SINGLE_TOP 來說,它們分別代表的是0x040000000x20000000

在透過FLAG_ACTIVITY_CLEAR_TOP|FLAG_ACTIVITY_SINGLE_TOP 使用OR運算後setFlags()IntentIntentmFlags的欄位就會變成:

達成了使用一個變數(mFlags)來紀錄多種狀態的效果。

新增/移除flag與判斷

除了直接調用setFlags()來對mFlags設定值以外,Intent還有提供addFlags()removeFlags()等方法,來讓我們新增與移除mFlags 中紀錄的狀態:

新增Flag

addFlags() 是單純的OR運算。

移除Flag

removeFlags() 則是先將要移除的flag換成補數,再進行AND運算。假設我們現在的mFlags1111,我們想要移除的flag R0001

mFlags 1111 
R 0001

先對R做補數運算(把1變成0,0變成1),得出R’

mFlags  1111 
R' 1110

再對mFlagsR’進行AND運算:

    mFlags 1111 
AND R' 1110
= 1110

就可以得到結果1110,成功地將0001從原來的1111中移除。

判斷某個Flag是否已啟用(bit = 1)

Intent中的方法為isDocument(),判斷方式是讓mFlags與要判斷的flag進行AND運算。假設我們現在的mFlags1111,我們想判斷flag F(0001) 是否啟用:

    mFlags 1111 
AND R 0001
= 0001

mFlags 與要判斷的flag進行AND運算後,得出的結果與要判斷的flag一致,就可以斷定這個flag已經啟用。

那如果我們現在的mFlags0011,我們想判斷flag G(1000) 是否啟用呢?

    mFlags 0011
AND G 1000
= 0000

mFlagsG AND運算後的結果為0000 ,因此可以判斷G 這個flag未被啟用。

所以bite-twice operator到底是啥?我看了還是不懂,怎麼辦?

應該是bitwise operator啦。不過說實話這真的不是個很好懂的東西,確實需要一段時間來消化與體會😝但是不用擔心,我這邊提供了一個比較語意化/比較白話/比較好消化的程式碼,可以把他copy下來,自己調整數值來細細咀嚼與體會:

這種方式真的有這麼好嗎?可以完全取代掉boolean嗎?

當然當一個feature很單純,只有兩三個boolean在表示狀態的話,是沒有必要這麼做的,如何取捨,要看個人與專案的需求。使用到目前為止,我覺得bitwise operator的好處大概就是:

  • 用一個field來表示所有狀態,相較於boolean可以省下更多記憶體空間。(當然現在手機記憶體動輒好幾GB,省下的記憶體空間微乎其微,但這種做法很容易可以在記憶體受限的韌體程式碼中發現)
  • 相較於多個boolean值各自擁有自己的truefalse狀態,需要個別maintain,使用bitwise operator的方式只需要判斷某個flag是不是已經啟用就行了。
  • 可以在一個地方列出所有的flag,除了一目瞭然以外,也不用擔心這個flag會被改成truefalse(好像跟第二點一樣齁)。
  • 暫時想不到了,可能就是潮吧!

結語

以上是這次研究bitwise operator的一些心得紀錄,好像不小心認真了……不過還是希望可以幫大家認識這個神奇的東西。如果有錯誤、或是有需要補充的地方,再請大家留言給我,謝謝大家😁😁😁

--

--

No responses yet