Rabbits 1.0.0

《Rabbits 1.0.0》 cover

如果只是想查看Rabbits 1.0.0的变化以及新版本的使用方法,可以直接查看最后一节。

路由在移动开发中的作用

除了路由器,最早知道路由这个概念还是很早之前接触到的PHP框架,通过对url进行匹配、解析,将请求转发到某个方法并渲染前端模板。事实上路由就是把一个请求转发到对应的处理器上。

和WEB开发中URL对应页面或后端处理逻辑类似,移动端“打开页面”的动作,同样可以当做请求处理。Activity之间的跳转使用Intent来完成,Intent其实就可以看作是向ActivityManager发送的一个请求。

Intent intent = new Intent(this, NextActivity.class);
this.startActivity(intent);

以上代码在项目中可能非常常见,那么为什么还要剥离出一个模块去代替已经很简洁的Intent模式呢?

最开始的需求开始来自于项目。对我来说,就是项目中有很多WEB页面和NATIVE页面相互跳转的逻辑,最初的实现就是覆写shouldOverrideUrlLoading方法,得到URL后根据domain、path等来进行匹配,再分别使用Intent打开对应的Activity。这样当然也能满足需求,但是当页面越来越多的时候,处理跳转逻辑的类就包含了大量的if-else语句,以及重复的逻辑,修改起来也比较麻烦。因为项目没有使用热更新,如果增加一个到某个页面的跳转链接,也需要更新客户端,同时让跳转逻辑继续膨胀。

开发Rabbits最初的动机就来源于此,使用URL作为请求的标识符,让Rabbits作为NATIVE和WEB页面跳转的桥梁

当然使用URL的好处就不仅于此了,在路由这件事上,URL相当于对页面的抽象,而Rabbits相当于对跳转实现(Intent)的抽象。抽象了一层之后就可以在抽象与实现之前加入更多的可能性。比如URL的解析,可以解析到页面,同样也可以解析到静态方法,或者其他各种服务;比如可以实现包含各种逻辑的拦截器;再比如实际使用Intent跳转的步骤,因为Intent这层已经被抽象了,只需要实现抽象接口,我们可以开发出其他具体实现,比如Fragment的跳转,比如仅仅是打印一行Log。

这么来看,Rabbits(或者说路由)不仅仅是Intent的替代品,而是“跳转”这件事的替代品,是一个抽象下的另一种更丰富的实现。

Rabbits 1.0.0 幕后

调用链

Rabbits最初版完成之后,我就一直在项目中使用了,也确实没出过问题,项目不大,性能也没有压力(在启动时会在主线程加载Assets目录下的json文件,不过也确实测试过即便再增加几百行,对耗时也只有几ms的影响)。但毕竟原来的结构是在没使用路由之前设计的,在实际项目中用到之后还是能发现设计上考虑不周的地方。比如mappings.json实际价值不高,拦截器的逻辑冗余,以及obtain方法对API调用流的破坏等等。

正是使用中的这些不适,让我重新思考Rabbits的结构。当我重新思考Rabbits的实现方式时,我发现Rabbits内部的种种逻辑与调用关系,其实就是一个以startActivity()为结尾的调用链,最初的URL经过一层层的加工,最终成为构建Intent的参数。当我得到这个结论之后,我就有了对Rabbits重构的思路:

把URL到startActivity()这整个过程,看做一个调用链,每一个步骤都是调用链上的一个环节,最初的跳转请求(在1.0.0中由Action类实现)在这个链上传递,最终由最后一个环节执行startActivity()完成跳转。

三个主要环节

有了链条,就要开始添加环节了,依据早期版本的结构,我提取出三个主要环节:匹配组装执行

《Rabbits 1.0.0》 3steps

匹配
通过URL匹配到在编译期间生成的TargetInfo。涉及到URL的分级匹配、参数解析。

组装
根据TargetInfo、请求参数、控制参数等创建Intent对象(Fragment对象)。

执行
获取前一个环节生成的Intent对象(Fragment)根据传入的跳转起始对象,执行startActivity()或类似方法,完成跳转过程。

拦截器

通过前文的解释,你可能已经猜到了,事实上每一个环节,都是一个拦截器。因为有调用链的结构作为基础,每一个环节,都可以当做是这个调用链上的一个拦截器,而自定义的拦截器,当然也可以和上面三个主要环节一样,“平等的”嵌入到调用链条当中。下图展示了1.0.0版本Rabbits的工作流程。

《Rabbits 1.0.0》 workflow

Rabbits 1.0.0

改变

核心结构敲定之后,注解、API等就像顺水推舟。完成后的1.0.0版本从内部完成了进化。如果你之前有了解过Rabbits,有3个非常明显的变化。

  1. 取消了mappings.json的设计,显然启动时的耗时更少,同时结构更简单,代码量降低。
  2. 初始化流程更加简便,scheme、domain配置,拦截器,Fallback处理等等都在一个链式调用里完成。(这个变化也带来一个副作用,如果拦截器比较多,那么这个链式调用会很长,可以通过减少匿名内部类的数量解决。当然,不使用链式调用也是可以的。: P)
  3. 多Module的使用变得简单,通过编译参数解决Module间的依赖和关联问题(主要是路由表的整合),同时也不依赖运行时。

延续

1. From-To-Start 模式

我把这个链式调用叫做 From-To-Start 模式,简称FTS(cool)。FTS是Rabbits最初版本就使用的,当时的想法根据对“跳转”这个过程的直觉理解设计API,跳转其实就是从一个页面到另一个页面的过程,那么api干脆就叫做from和to好了。于是就有了如下调用模式:

Rabbit.from(this).to(P.TEST).start();

经过在项目中的实践,我发现FTS符合直觉的调用方式真的很上瘾,以至于有些地方再用到Intent的时候反而觉得很别扭。

2. P文件

在设计Rabbits初版的时候,我就意识到如果项目中直接使用字符串常量作为跳转的URL,那么项目中会存在难以维护的隐患:URL改动可能涉及到很多地方,不同URL甚至可能对应同一个页面,如果要改变URL的指向涉及到的地方就更多了。我当然想到了使用常量来减少字面量的数量,但是既然可以通过注解完成路由表的注册过程,那么常量为什么不能通过注解处理器生成呢?

mappings.json存在的时候,通过page name就可以直接生成对应的字符串常量定义类P.java。但现在这个便捷条件已经消失了。1.0.0版本的P文件,根据URL直接转换而成,为了兼容空字符串(或根路径),根据URL生成的常量名都会带有P_前缀。但是使用起来仍然很方便。

使用

这里介绍下Rabbits的常规使用,更多内容可以参见Github上的wiki

1. Cradle引入

dependencies {
    implementation "com.kyleduo.rabbits:rabbits:1.0.0"
    annotationProcessor "com.kyleduo.rabbits:compiler:1.0.0"
}

2. 注解

@Page("/test")
public class TestActivity extends AppCompatActivity {}

@Page(value="/test", alias="TEST", flags=1<<4, variety=["/test/{param}"])
public class TestActivity extends AppCompatActivity {}

最简单的注解可以只包含path部分,Rabbits会在scheme和domain合法的情况下对path进行匹配。针对这种注解,Rabbits生成的P文件会包含如下常量定义:String P_TEST = "/test";

第二个例子稍微复杂一点,alias会影响P文件中生成的常量名,其他参数的含义和使用方法参见wiki和demo项目吧。

3. 初始化

Rabbit.init(RabbitConfig.get()
                    .schemes("demo")
                    .domains("rabbits.kyleduo.com"));

这应该算是最简单的初始化了,设置好scheme和domain即可。scheme和domain都可以设置多个,第一个会作为默认值。

4. From-To-Start 模式

Rabbit.from(this).to(P.TEST).start();

ALL DONE 以上就是从引入依赖到完成第一次跳转的全部步骤啦。

References

项目地址:Github
项目wiki:wiki
原文链接:kyleduo.com

    原文作者:kyleduo
    原文地址: https://www.jianshu.com/p/6093a95e7bdb
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞