嗨,你终于来啦 ~ 等你好久啦~ 喜欢的小伙伴欢迎关注,我会定期分享Android知识点及解析,还会不断更新的BATJ面试专题,欢迎大家前来探讨交流,如有好的文章也欢迎投稿。
前言
很高兴见到你!
上上周我在掘金碰巧遇到了一篇 用设计模式管理状态 的文章,一时兴奋不已,在评论区安利了,一直以来我司在封装商业级 SDK 时,使用的十六进制状态管理机制。
原以为会无人对此感兴趣,没想到,留言很快就收到文章作者的回复,并且在评论区耐心地和我探讨了设计模式的 独占式状态机 和十六进制的 复合状态管理 在使用场景上的区别。
遗憾的是,通过评论区的只言片语,并不能让人体会到 十六进制状态管理 的真正魅力。
于是作为回馈,我特地分享了这一篇:当我们封装商业级 SDK 时,我们是怎么使用十六进制来完成状态管理。
此外,是只有封装 SDK 这种大动作,才值得使用十六进制吗?不是的,恰恰相反,正因为 十六进制状态管理是如此地普适,乃至于连封装 SDK 都优先使用这种方式。
考虑到部分读者可能对十六进制本身不太了解,本文会连同十六进制一起介绍。
所以如果阅读完这篇文章,你对 十六进制的状态管理 有了感性的认识,那我的愿望也就达到了。
我和十六进制的 “三次握手”
最开始对十六进制产生了兴趣,或者说,知道了它在什么时候能派上用场,是在 2015 年观看《火星救援》这部电影时发现的。
为了和 “远在 4 亿公里外、电磁波需要 40 分钟才能完成一次完整的请求响应的” 地球通信,孑然一身的主角 Mark 想到了一个办法,就是通过十六进制和 ACSII 码表:
将字符转换成字母 ,这样地球上的人就可以通过控制 “Mark 从沙漠中掏来的、1997 年服役的” 探路者号(PathFinder)的摄像头偏移角度,来指明一连串的字符,从而 Mark 可以将它们转译成英文。
第二次接触十六进制是在 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 可用,其他按钮禁用。
如果是传统方式编写,我们势必会在类中为 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。
- 模式的切换无需手动清洗,只需为状态集变量注入预置的常量状态集。
- 模式的存取一步到位。
- 模式的存储只需一个数据表字段。
- 可直接在数据库中完成查询状态。
这样说,你理解了吗?
如果您想第一时间看我的后期文章,可以关注一下,不定期推送Android技术文章。如果觉得文章还不错,记得点赞~
本文原作者:KunMinX
原文链接
网友评论