Hub库的由来

在准备重构知天气时,决定使用谷歌推出的
Android Architecture Components架构,同时借鉴微信Android模块化架构重构实践的模块化的思想,抛弃之前的MVP,单一模块的方式对项目进行重构,同时积累一些经验来对工作中的项目进行优化。AAC和模块化的优点就不多介绍了,AAC很容易引入,谷歌在Android的API层面提供了大量的支持,模块化主要的难点就是模块间没有直接引用,如何通信的问题(以下用module来代替模块,也就是Android开发中的nodule),了解到的有主要以下几种思想:

  1. 建立一个公共的module,每个功能module需要暴露的接口放到公共的module,实现类在各个module里,在公共module里存在一个接口管理类,一般是一个map,在初始化的时候,每个module将自己的的接口和实现类放到map(接口注册),每个module需要其他module的功能时,通过接口拿到实现类进行功能调用(接口访问)。这就像一种“SDK”的方式,公共module为各功能module提供接口和数据结构,这种方式是最容易想到的,也是早期的很多项目使用的一种方式,接口化的方式优点是结构清晰直观,调用链易追踪,对IDE更友好(可在IDE中直接跳转),协议变化直接反映在编译上,维护接口也简单。但缺点是由于注册初始化,要严格按照调用的先后顺序注册,设计正确的生命周期,相互依赖的关系链也需要花时间设计,而这个过程是“危险”的,各种借口依赖也是易变的。
  2. Event通知类型的,如使用EventBus或者RxBus等事件总线的方式,这种方式虽然可以完成相应的跨module,解耦,但是却是最不推荐的方式,每种次通信都要定义一种类型,繁琐,从本质上来讲也不适合,EventBus或者RxBus更应该偏向于通知的思想,一对多,而且返回值也不好处理。而module间通信更倾向于功能提供,一般是一对一的,有时同步返回值,所以Event事件总线方式应该是最不合适的。
  3. 使用路由框架,常用的开源库有ARouter,ActivityRouter等,通过Url来实现页面间的跳转,降低页面间的耦合,同时ARouter也支持提供服务的功能,这种模式可能也是现在很多项目使用的一种方式,但是我觉得这种路由的框架可能更偏重于页面跳转的场景,大部分的项目只需要能实现module间的接口提供和简单的页面跳转就足够了,同时因为都是通过字符串来代表协议,和接口的形式比起来不是那么直观。

Hub是主要基于以上第1种方式,同时提取其他两种方式的优点实现的一个module间服务提供(非service概念)、页面跳转的简洁框架,主要特点:

  1. 通过控制反转实现module间服务提供、Activity跳转,Activity支持参数自动处理传递的参数,不需要繁琐的注解标注
  2. 多接口支持。优点在于不必要暴露所有接口,只需将需要的接口暴露,比如一个服务可能支持多个功能,但是有些功能只需要再module内使用,有些需要提供给其他module,这样就可以抽离出多个接口,只需要将需要暴露的放到基础module里。
  3. 支持多进程Activity跳转的参数自动处理
  4. 接口化的通信方式类似于“SDK”+数据结构,面向接口编程,更清晰直观, 对IDE更友好(可在IDE中直接跳转),协议变化直接反映在编译上,维护接口也简单
  5. 不需要繁琐的判空处理,服务的实现类或者路径的Activity不存在时不会崩溃。Activity跳转失败可以通过方法返回值来感知,做一些异常处理
  6. Less is more, simple is better!使用简单,功能强大

简单的使用方式

Hub文档

实现原理

上图是Hub库的总体框架图,代码主要包括4个部分,Annotation主要是注解部分,Compiler是在编译期处理注解,控制反转主要是在这里实现,Hub是提供接口和运行期逻辑控制中心,还有就是通过apt在编译期生成的类,这些类的功能时帮助运行期通过接口和其他信息来找到相应的实现。

主要的处理部分是在ImpHub和ActivityHub里,这部分就是通过传入的接口,找到Compiler里生成的一定规则命名的Helper类,继而得到接口的具体实现类或者跳转到相应的页面,源码都很容易理解,更多细节可以通过源码来了解,其中处理逻辑中用到了动态代理的思想,不熟悉的可以参考之前写的这篇文章代理模式的学习与应用

技术特点

  1. 尽量少的生成类和反射,实现Hub的过程中为了减少运行期反射的使用,尽可能少的生成一些帮助类,在通过接口得到服务类的时候直接返回对象而不是Class,这样可以尽量避免反射生成对象的所带来的消耗。
    生成类文件很少

    在查找Activity的跳转路径时,具体的Activity和参数赋值只需要一个class文件就能完成所有的功能,这个跳转赋值过程中也只需一次 Class.forName过程。
  1. 初始化不消耗性能,不占据时间,不存在时序问题,如果不需要Activity的跳转,初始化都不要,App启动过程无消耗

  2. 可扩展性很强大,可自由定制。接口的方式看似局限性很大,但实际上动态代理的方式可扩展性很强,方式又及其简单,不需要经过中间过多的转换和包装,因为方法和接口上可以通过注解的来传递任何你想自定义的信息。

这个库最初是在知天气重构过程中产生的,当初的目的也很纯粹,就是想要一种能在module间方便使用的通信框架,在对比各种已存在的解决方案的情况下选择自己来实现,不是为了重复造轮子,而是为了更适合自己项目,以及自己以及团队的开发习惯,尽量的简洁易用已经够用,后续这个库就被用在了公司的项目中,已经有较多的线上使用过程。

Less is more, simple is better! 合适的才是最好的