就算不去火星种土豆,也请务必掌握的 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 个状态要声明的时候呢?

相关文章
相关标签/搜索