Android中存在着各種各樣的Flag的使用,這篇文章主要研究一下這種Flag的運用方式。
爲什麼要用Flag
程序開發中,不同的場景可能需要不同的控制流;不同的控制流中,後置的控制流可能依賴於前置控制流的某一個屬性或狀態,這個時候,我們可以考慮引入標誌位Flag,記錄前置控制流中的屬性或狀態,在後置控制流執行時再去訪問。在相對簡單的場景中,我們完全可以這麼處理:
boolean success = false;
public void putElephantIntoFridge(){
//step 1
openTheDoor();
//step 2
putElephant();
//step3
closeTheDoor();
}
private void closeTheDoor() {
if (success) {
lockTheDoor();
} else {
runAway();
}
}
private void putElephant() {
Log.i(TAG, "putElephant: ");
Random random = new Random();
success = random.nextInt(2) == 1;
}
private void openTheDoor() {
Log.i(TAG, "openTheDoor: ");
}
private void lockTheDoor() {
Log.i(TAG, "lockTheDoor: ");
}
private void runAway(){
Log.i(TAG, "runAway: ");
}
像上面這種比較簡單的控制流,我們可以直接考慮加一個標誌位flag,來確定後續控制流的執行邏輯。 但是,如果控制流程中會有多個case影響到後續的執行分支,或者這些case對應的flag需要傳遞給其他的函數或方法,一昧地添加新的flag,要麼會讓整個函數控制流變得無比混亂,要麼就會徒增大量的離散flag,導致關聯的控制流需要新增多個flag輸入參數。
這種情況下,我們可以考慮將flag聚攏在一起,通過狀態集StateSet或者位運算Bit Operation來消除離散的flag信息,我們現在主要來探討一下使用位運算來聚合flag的方案。
位運算與Flag
開始之前,先簡單複習一下位運算的幾個運算符:
操作符符 | 運算 | 使用 | 結果 | 例子 |
---|---|---|---|---|
& |
按位與 | exp1 & exp2 |
按位比較exp1和exp2的data,如果都爲1,結果爲1,否則結果爲0 | 1100&1010 = 1000 |
| |
按位或 | exp1 | exp2 |
按位比較exp1和exp2的data,如果有一個爲1,結果爲1;否則結果爲0 | 1100 | 1010 = 1110 |
^ |
按位異或 | exp1 ^ exp2 |
按位比較exp1和exp2的data,如果取值不同,結果爲1;否則結果爲0 | 1100 ^ 1010 =0110 |
~ |
按位非 | !exp |
按位翻轉exp每一位的data,使得1變爲0,0變爲1 | ~1100 =0011 |
<< |
左移 | exp<<n |
將exp向左位移n位,丟棄左側溢出的位數,右側補0 | 1 << 1 = 2 , 1<<3 = 8 |
>> |
右移 | exp>>n |
將exp向右位移n位,丟棄右側b位信息,左側補0 | 2 >> 1 = 1 , 1>>2 = 1/2 |
基於上述的運算符,我們可以考慮將二元的flag映射爲某一個bit的取值,0 或 1,從而可以將多個flag最終彙總成一個總的flag集合,這個時候,我們仍需要提供獲取原始flag的方法。這裏&
運算符剛好可以給我們提供類似於取值的能力:
A&B
:判斷A中是否有對應的B flag的取值,如果沒有,那麼A&B=0
A|B
:往A中添加B,在每一個bit代表的flag都相互獨立的前提下,我們可以將所有的flag彙總到一個參數裏面A&~B
:移除A中B的flag,相當於重置B的flag。A^B
: 判斷A和B的每一位是否有不同的bit取值,可以用來判斷兩個Flag是否有變化
如果一個flag有超過2個選項,那麼,單純使用一個bit沒法完成flag的展示。此時,我們可以通過擴展bit的位數來實現,以flag有3個選項爲例:
int FLAG_1 = 0b0001;
int FLAG_2 = 0b0010;
//int FLAG_3 = 0b0011;//wrong,當FLAG_1|FLAG_2時,有歧義
int FLAG_3 = 0b0100;
int MASK = 0b0111;
int PFLAG_1 = 0b1000;
int flag = PFLAG_1 | FLAG_1;//0b1001
boolean flag1Set = (flag & FLAG_1) != 0;//true
int flagValue = flag & MASK;
boolean flag1Set2 = flagValue == FLAG_1;//true
boolean flag2Set = (flag & FLAG_2) != 0;//false
需要注意的是,擴展了bit的位數之後,獲取原始flag需要通過掩碼MASK來獲取,掩碼相關的知識可以參考: en.wikipedia.org/wiki/Mask_(… 簡單來說,通過設置掩碼MASK指定bit的取值爲1,再通過&
運算,可以從混合的flag中提取到原始的flag。
Android中View Flag
基於以上的內容,我們可以看下Android 中View的Flag的相關使用。
以下內容基於Android 29源碼
在View中,存在着mViewFlags
以及mPrivateFlags
, mPrivateFlags2
, mPrivateFlags3
這些flag,在現有的邏輯中,mViewFlags
主要會記錄UI展示相關的View狀態,如是否可獲焦,是否可點擊,是否可長按點擊等,而其餘的三個privateFlags則是記錄了view繪製流程的控制,或者當前是否正在執行動畫,或者是否已經觸發了layout等等。簡單地展示一下mPrivateFlags
關聯的MASK
/*
* Masks for mPrivateFlags, as generated by dumpFlags():
*
* |-------|-------|-------|-------|
* 1 PFLAG_WANTS_FOCUS
* 1 PFLAG_FOCUSED
* 1 PFLAG_SELECTED
* 1 PFLAG_IS_ROOT_NAMESPACE
* 1 PFLAG_HAS_BOUNDS
* 1 PFLAG_DRAWN
* 1 PFLAG_DRAW_ANIMATION
* 1 PFLAG_SKIP_DRAW
* 1 PFLAG_REQUEST_TRANSPARENT_REGIONS
* 1 PFLAG_DRAWABLE_STATE_DIRTY
* 1 PFLAG_MEASURED_DIMENSION_SET
* 1 PFLAG_FORCE_LAYOUT
* 1 PFLAG_LAYOUT_REQUIRED
* 1 PFLAG_PRESSED
* 1 PFLAG_DRAWING_CACHE_VALID
* 1 PFLAG_ANIMATION_STARTED
* 1 PFLAG_SAVE_STATE_CALLED
* 1 PFLAG_ALPHA_SET
* 1 PFLAG_SCROLL_CONTAINER
* 1 PFLAG_SCROLL_CONTAINER_ADDED
* 1 PFLAG_DIRTY
* 1 PFLAG_DIRTY_MASK
* 1 PFLAG_OPAQUE_BACKGROUND
* 1 PFLAG_OPAQUE_SCROLLBARS
* 11 PFLAG_OPAQUE_MASK
* 1 PFLAG_PREPRESSED
* 1 PFLAG_CANCEL_NEXT_UP_EVENT
* 1 PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH
* 1 PFLAG_HOVERED
* 1 PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK
* 1 PFLAG_ACTIVATED
* 1 PFLAG_INVALIDATED
* |-------|-------|-------|-------|
*/
由於位數的限制,單個flag的能支持彙總的flag最多也就32個,而隨着版本的增加,View中新增了部分新的屬性,需要新的flag去控制邏輯。因此,現在的private的彙總flag已經有3個了。隨着後續Android版本的提升,更多的feature會被引入,可能後面我們能看到更多的privateFlags出現在代碼中。
幾個常見的Flag解析
Visibility
控制View的可見性應該是我們在開發中比較常操作的事情,在控制Visibility中,實際上我們調用的View.setVisibility
,直接對接了mViewFlags
的修改,
public void setVisibility(@Visibility int visibility) {
setFlags(visibility, VISIBILITY_MASK);
}
...
void setFlags(int flags, int mask) {
final boolean accessibilityEnabled =
AccessibilityManager.getInstance(mContext).isEnabled();
final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility();
int old = mViewFlags;
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
.....
}
在獲取view的可見性時,實際上是通過VISIBLITY_MASK
,從mViewFlags
中直接訪問出來的。
@Visibility
public int getVisibility() {
return mViewFlags & VISIBILITY_MASK;
}
public static final int VISIBLE = 0x00000000;
/**
* This view is invisible, but it still takes up space for layout purposes.
* Use with {@link #setVisibility} and <a href="#attr_android:visibility">{@code
* android:visibility}.
*/
public static final int INVISIBLE = 0x00000004;
/**
* This view is invisible, and it doesn't take any space for layout
* purposes. Use with {@link #setVisibility} and <a href="#attr_android:visibility">{@code
* android:visibility}.
*/
public static final int GONE = 0x00000008;
/**
* Mask for use with setFlags indicating bits used for visibility.
* {@hide}
*/
static final int VISIBILITY_MASK = 0x0000000C;
這裏其實可以看到,掩碼需要能夠覆蓋到各個可選項的各個bit,從0b0000 0000
, ob0000 0100
, 0b0000 1000
, 三者中找到最小的,可覆蓋三個可選項的掩碼,應該就是0b0000 1100
, 即0x000C
了
Focusable
同樣地,Focusable的對應API中,也包含着對三個FLAG和一個MASK的應用
public static final int NOT_FOCUSABLE = 0x00000000;
public static final int FOCUSABLE = 0x00000001;
public static final int FOCUSABLE_AUTO = 0x00000010;
/**
* Mask for use with setFlags indicating bits used for focus.
*/
private static final int FOCUSABLE_MASK = 0x00000011;
而判斷View是否可獲焦的API: isFocusable
,實際上也可以這麼實現:
//原版
public final boolean isFocusable() {
return FOCUSABLE == (mViewFlags & FOCUSABLE);
}
//修改版
public final boolean isFocusable() {
return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK);
}
···
結語
除了View相關代碼使用到了Flag之外,Android中還有很多地方使用到這種Flag參數來簡化參數,比如常見的Intent.setFlag
, MeasureSpec
, WindowManager.LayoutParams
等等,有興趣的童鞋可以找其他的資料深入瞭解一下,本文就不再一一介紹了。
這裏有我自己整理的一些進階資料與面試題:https://shimo.im/docs/VYcc3wyJRpy9jJ83/
推給大家一個技術交流會裙: