美文网首页Android开发Android开发经验谈Android开发
就算不去火星种土豆,也请务必掌握的 Android 状态管理最佳

就算不去火星种土豆,也请务必掌握的 Android 状态管理最佳

作者: 奶盖ww | 来源:发表于2019-07-05 20:43 被阅读14次

    嗨,你终于来啦 ~ 等你好久啦~ 喜欢的小伙伴欢迎关注,我会定期分享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
    原文链接

    相关文章

      网友评论

        本文标题:就算不去火星种土豆,也请务必掌握的 Android 状态管理最佳

        本文链接:https://www.haomeiwen.com/subject/xsjyhctx.html