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

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

前言

上周在掘金巧遇一篇 “用设计模式管理状态” 文章,作为补充,在评论区安利我司封装商业级 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。
  • 模式切换无需手动清洗,只需为状态集变量注入预置的常量状态集。
  • 模式存取一步到位。
  • 模式存储只需一个数据表字段。
  • 可直接在数据库中完成查询状态。

作为额外附赠的答疑

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

为何这样使用?

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

0x0001  0x0004    0x0020
                 
  0001    0100   0010 0000

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

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

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

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

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

(0)

相关推荐

  • Andorid 状态管理之Lifecycle浅析

    目录 原理概述 构成 模型 源码 1. addObserver(observer) 2. sync() 3. setCurrentState.moveToState 监听 其他 1. Application.ActivityLifecycleCallbacks 总结 原理概述 Lifecycle主要用于Activity.Fragment这一类具有状态的组件的状态监听,最主要的一个作用就是将原先Activity.Fragment中的大量的生命周期相关的回调函数移出View层文件,从设计角度上来说,

  • Android 十六进制状态管理实例详解

    目录 背景 示例 实现思路 代码测试 十六进制 总结 背景 最近需要实现一个状态管理类: 在多种场景下,控制一系列的按钮是否可操作. 不同场景下,在按钮不可操作的时候,点击弹出对应的Toast. 随着场景数量的增加,这个管理类的实现,就可能会越来越复杂. 刚好看到大佬的文章,顺便学习和实践一下.参考学习:就算不去火星种土豆,也请务必掌握的 Android 状态管理最佳实践 示例 还是用大佬那个例子. 例如,存在 3 种模式,和 3个按钮,按钮不可用的时候弹出对应的 Toast. 模式 A 下,要

  • Android源码使用16进制进行状态管理的方法

    前言 在Android源码中,对于"多状态"的管理总是通过16进制数字来表示,类似这种格式: //ViewGroup.java protected int mGroupFlags; static final int FLAG_CLIP_CHILDREN = 0x1; private static final int FLAG_CLIP_TO_PADDING = 0x2; static final int FLAG_INVALIDATE_REQUIRED = 0x4; private s

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

    目录 前言 我和十六进制的 “三次握手” 使用十六进制前的混沌世界 十六进制能很好解决这些问题 十六进制运作机制 十六进制状态管理实战 十六进制状态存取实战 小结 作为额外附赠的答疑 前言 上周在掘金巧遇一篇 “用设计模式管理状态” 文章,作为补充,在评论区安利我司封装商业级 SDK 时常用的 “十六进制状态管理机制”. 原以为无人对此感兴趣,没想到留言很快便收到文章作者回复,且在评论区耐心和我探讨设计模式 独占式状态机 和十六进制 复合状态管理 使用场景区别. 遗憾的是,通过评论区只言片语,难

  • Java日志API管理最佳实践详解

    概述 对于现在的应用程序来说,日志的重要性是不言而喻的.很难想象没有任何日志记录功能的应用程序运行在生产环境中.日志所能提供的功能是多种多样的,包括记录程序运行时产生的错误信息.状态信息.调试信息和执行时间信息等.在生产环境中,日志是查找问题来源的重要依据.应用程序运行时的产生的各种信息,都应该通过日志 API 来进行记录. 很多开发人员习惯于使用 System.out.println.System.err.println 以及异常对象的 printStrackTrace 方法来输出相关信息.这

  • vuex项目中登录状态管理的实践过程

    目录 工具: 登录场景: 实践: 场景1思考与实践 场景2思考与实践 总结 工具: chorme浏览器安装Vue.js devtools方便调试 登录场景: 页面的导航处或其他地方有时会显示用户现在的登录状态,状态可分为:未登录,正在登录(loading),登录成功并显示用户名. 有些页面是不需要登录就可以让用户浏览的,但是有些页面必须登录才可以进入浏览. 实践: 场景1思考与实践 用vuex创建一个数据仓库 //src目录下新建一个store目录,创建index.js如下 //创建数据仓库 i

  • Crashlytics Android 异常报告统计管理(详解)

    简介 Crashlytic 成立于2011年,是专门为移动应用开者发提供的保存和分析应用崩溃信息的工具.Crashlytics的使用者包括:支付工具Paypal, 点评应用Yelp, 照片分享应用Path, 团购应用GroupOn等移动应用. 2013年1月,Crashlytics被Twitter收购,成为又一个成功的创业产品.被收购之后,由于没有了创业公司的不稳定因素,我们更有理由使用它来分析应用崩溃信息. 使用Crashlytics的好处有: 1.Crashlytics不会漏掉任何应用崩溃信

  • 前端的状态管理(上)

    目录 1.什么是前端状态管理? 2.Vuex 3.Bus 总线 4.web storage 前言: 提到状态管理大家可能马上就想到:Vuex.Redux.Flux.Mobx等等方案.其实不然,不论哪种方案只要内容一多起来似乎都是令人头疼的问题,也许你有适合自己的解决方案又或者简单的注释和区分模块,今天来聊一聊前端的状态管理,如果你有好的建议或问题欢迎在下方留言提出. 1.什么是前端状态管理? 举个例子:图书馆里所有人都可以随意进书库借书还书,如果人数不多,这种方式可以提高效率减少流程,一旦人数多

  • Android音频焦点管理实例详解

    目录 音频焦点管理的意义 音频焦点管理的行为准则 版本兼容 在Android 8.0(API 26) 之前对音频焦点具体处理实现 8.0 之后实现 延迟获取焦点 自动降低音量 音频焦点请求方式 响应音频焦点更改 暂时性失去焦点 永久性失去焦点 附音频基础知识 总结 音频焦点管理的意义 两个或两个以上的 Android 应用可同时向同一输出流播放音频.系统会将所有音频流混合在一起.虽然这是一项出色的技术,但却会给用户带来很大的困扰.为了避免所有音乐应用同时播放,Android 引入了“音频焦点”的

  • React状态管理器Rematch的使用详解

    目录 Rematch使用 1. Rematch介绍 2. Rematch特性 3. 基本使用 Rematch使用 1. Rematch介绍 Rematch是没有样板文件的Redux的最佳实践,没有action types. action creators, 状态转换或thunks. 2. Rematch特性 Redux 是一个了不起的状态管理工具,由良好的中间件生态系统和优秀的开发工具支持.Rematch 以 Redux 为基础,减少样板文件并强制执行最佳实践. 小于 2kb 的大小 无需配置

随机推荐