前言

上周在掘金巧遇一篇 “用设计模式管理状态” 文章,作为补充,在评论区安利我司封装商业级 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十六进制状态管理的资料请关注Devmax其它相关文章!

务必掌握的Android十六进制状态管理最佳实践的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  5. ios – UIColor到十六进制(网页颜色)

    有没有简单的方法将UIColor转换为十六进制值?

  6. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  7. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  8. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  9. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  10. ios – 如何使用Unicode十六进制值(UTF-16)在Swift中表达字符串

    我想在Swift中使用十六进制值编写一个Unicode字符串.我已经阅读了字符串和字符的documentation,所以我知道我可以使用特殊的Unicode字符直接在字符串如下:版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部