务必掌握的Android十六进制状态管理最佳实践

2022-09-27 12:12:04

目录前言我和十六进制的三次握手使用十六进制前的混沌世界十六进制能很好解决这些问题十六进制运作机制十六进制状态管理实战十六进制状态存取实战小结作为额外附赠的答疑前言上周在掘金巧遇一篇用设计模式管理状...

目录
前言
我和十六进制的 “三次握手”
使用十六进制前的混沌世界
十六进制能很好解决这些问题
十六进制运作机制
十六进制状态管理实战
十六进制状态存取实战
小结
作为额外附赠的答疑

前言

上周在掘金巧遇一篇 “用设计模式管理状态” 文章,作为补充,在评论区安利我司封装商业级 SDK 时常用的 “十六进制状态管js理机制”。

原以为无人对此感兴趣,没想到留言很快便收到文章作者回复,且在评论区耐心和我探讨设计模式 独占式状态机 和十六进制 复合状态管理 使用场景区别。

遗憾的是,通过评论区只言片语,难让人体会 “十六进制状态管理” 真正魅力,

故今日我们以封装商业级 SDK 为例,拆解我们是如何使用十六进制完成状态管理,相信阅读后你会豁然开朗。

我和十六进制的 “三次握手”

最初对十六进制产生兴趣,或说知道它何时可派上用场,是 2015 年观看电影《火星救援》时发现。

为和 “远在 4 亿公里外、电磁波需 40 分钟才能完成一次完整请求响应” 的地球通信,孑然一身主角 Mark 想到一办法,即是通过十六进制和 ACSII 码表:

将字符转换成字母 ,这样地球上的人便可通过控制 “Mark 从沙漠中掏来的 1997 年服役的探路者号(PathFinder)” 摄像头偏移角度,来指明一连串字符,从而 Mark 可将它们转译成英文。

务必掌握的Android十六进制状态管理最佳实践

第二次接触十六进制是在 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 年经验同事联手完成核心项目重构时,因同事提出使用十六进制管理状态,而亲眼见证十六进制在状态管理方面绝佳优势。

为纪念这一分享,此后每当有新同事入职,我提供的培训课程必包含十六进制状态管理。

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

该项目有个需求:当指定图形编辑模式,图形工具栏按钮状态需随之发生改变。

例如,存在 3 种图形编辑模式,和 8 个图形编辑按钮。

模式 A 下,要求 按钮1、按钮2、按钮3 可用,其余按钮禁用。

模式 B 下,要求 按钮1、按钮4、按钮5、按钮6 可用,其余按钮禁用。

模式 C 下,要求 按钮1、按钮7、按钮8 可用,其余按钮禁用。

务必掌握的Android十六进制状态管理最佳实践

如是传统方式编写,我们势必会在类中为 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。
模式切换无需手动清洗,只需为状态集变量注入预置的常量状态集。
模式存取一步到位。
模式存储只需一个数据表字段。
可直接在数据库中完成查询状态。

作为额外附赠的答疑

Q1: 细心朋友可能注意到,声明状态都是 1、2、4、8,然后进一位继续。

为何这样使用?

因为当十六进制转成二进制计算时,十六进制每位数占 4 个二进制位,例如:

0x0001  0x0004    0x0020
                 
  0001    0100   0010 0000

且唯有独占每个二进制位,我们才能区分出不同状态。

因而我们对十六进制数的每一位只安排 1、2、4、8,一旦用完,就前进一位继续。

Q2: 既然如此,状态声明为何不直接用二进制来表示?

原因很简单 —— 一目了然 0x4218 和密密麻麻 0100001000011000b,在代码中声明哪个更费时、更费眼、更易出错? —— 特别是当有 20、30 个状态要声明呢?

以上就是务必掌握的android十六进制状态管理最佳实践的详细内容,更多关于Android十六进制状态管理的资料请关注我们其它相关文章!