最近项目中越来越多使用组件化,所以记录一下由此产生的最基本的一个问题:路由。
安卓页面间的跳转想必是每一个学习安卓之人最先接触的,最基本的方式就是直接调用startActivity方法。这种方法在以前整个工程都是单模块的时候没啥大问题,简单也没有必要去重新写新的方法。但是随着目前安卓开发趋向于模块化组件化,如果想用这种方式实现组件间的跳转,势必会造成各个组件相互依赖,造成高耦合度,这就违背了组件化非常重要的一个目的:解耦。这样下来组件就只能适用于当前工程,随着项目页面越来越多,本来作为组件的那部分却移入了越来越多项目中的依赖,这时如果另一个工程想移入这个组件,估计开发人员要炸了,因为每次移入组件,都会有一堆报红,因为这个组件还有很大一部分对原来工程里的逻辑。
解决思路肯定是解耦,首先想到利用利用scheme进行跳转,统一一个Activity,内部根据Uri获取到domain,再进行统一判断进行跳转,但是这样不够通用,每个activity都得配置一个判断,换一个工程我们又得重新配置一遍,所以不够完美。这样行不通那利用反射机制呢?每个跳转都得配置包名跟类名,这个组件换个工程我们得把所有类名更换一遍,还不如上一个方法。。。。
我们想要的是不用每个activity都配置一遍跳转的逻辑,只需要根据一个参数就能自动给我们配置好跳转,这样耦合性相当低了。那以上这些都行不通的话,有没有现成的解决方案呢?
答案是当然有,阿里给我们提供了一个ARouter框架,耦合性低,适合多模块,支持拦截跟定制,路由也只需提供一个注解,无需自定义逻辑,完美解决了上述问题。使用起来也非常简单,就不做记录了,搜一下去github上阿里写了详细的使用说明跟api用法。这里我们需要了解的是,ARouter是如何提供了一套简单的实现,解决了上述问题,本人借鉴了部分源码,将基础逻辑剥离了出来,整理成了个简单的路由框架。
首先要想了解ARouter原理,你需要先熟悉以下几个知识基础:1.注解跟注解处理器,2.Java apt技术,3.JavaPoet动态依赖注入框架(这个比较重要,其他几个带注解的框架如EventBus,ButterKnife的基本原理也是这个框架)。这些就不做介绍了,不然又是个长篇大论。
我们根据使用ARouter步骤的顺序开始整理思路。
首先跟ARouter一样,先创建三个模块,两个java library跟一个android library:
分别是注解模块,api模块跟注解处理器模块。
一:注解
先从注解模块开始,我们先定义注册路由地址的注解:
这里设置成对类注解,并设置成编译时注解,比较简单。
一:注解处理器
定义完注解我们需要处理注解处理器。先添加好依赖,引入注解模块,javapoet跟谷歌的auto service主动注册处理器框架:
定义注解处理器RouterProcessor:
在上面添加自动注册的注解,接下来需要实现注解处理器的几个方法:
这里process方法中核心代码集中在creatJavaCode中,该方法的作用是获取注解信息生成java文件,生成类添加了方法,配置了包名跟实现的接口,我们看下代码:
这里根据拿到的注解外部类的信息编译时自动生成代码,代码的作用是将注解的外部类的路由信息跟路径保存在一个map中。
我们看看生成的代码,跟我们构建代码部分设置的一样,存储的类实现了IRouterZ接口(到后面就知道为什么要专门实现一个接口而不只是添加方法了):
这样一来最核心的路由信息存储部分就完成了,它为我们省去了每个activity配置跳转信息的操作,不得不感慨javapoet这个功能是真的强大。。。
三.读取
接下来跳转页面我们就根据存下来的路由map进行跳转,那么问题来了,编译完之后我们如何从动态生成的代码中获取到路由信息呢?
ARouter的思路是在初始化的时候就从生成的所有dex中去寻找实现了IRouterZ的类,这部分类即是动态构建代码时生成的,然后传一个相同的map进去依次执行所有的onLoad方法,这样所有的路由信息就全部都保存到了我们自己创建的map中去,思路有了我们看看具体代码:
既然是在初始化时候实现那肯定是Init方法作为入口:
init方法没啥信息,我们看LogisticsCenter.init(context)里面执行了什么:
再顺势跳到registerComm里面:
可以看出,这个方法从生成代码时候配置的包名里面遍历所有的class,找到实现了IRouterZ的类,最后执行onLoad方法,将路由信息存入Warehouse.routeMap中去,Warehouse.routeMap是个存放path跟路由信息的map:
三.路由跳转
好了,最关键的配置,存储跟读取都实现了,最后一步的跳转其实相对来说就很简单了,因为在存储的时候我们获取到注解的外部类,比如activity,fragment,获取到了这些类其实最后就用最基本的startActivity就行了,当然,路由信息的结构在写框架时你可以自己定义,所以跳转前判断逻辑也可能不尽相同,这里我们还是参照ARouter写,以activity为例:
首先activity中调用:
我们依次看这几个方法,先看build:
可以看出build方法实际上是根据地址new了一个PostCard对象,PostCard继承了路由信息所在的类,因此在这里它当做路由信息来用。
bundle方法实际上就是个set方法,将Bundle数据传给postcard对象。
最后关键的navigation方法肯定是跳转了,我们看下怎么实现的:
跳到ZRouter的navigation方法中去:
因为最开始Postcard只有path被赋值了,单单靠这个无法完成跳转,那么一直低调不被注意的Warehouse.routeMap终于派上用场了,这个我们把它称之为路由表,在LogisticsCenter.complete(postcard);方法中,我们根据路由表完善了Postcard的各个参数,我们看看:
setDestination设置了目标类,setRouteType设置了跳转的类型,这里是activity,因此根据上面代码执行的是startActivity(postcard)方法,再看这个方法:
这部分就很简单了,设置启动模式,设置intent传参,最后在主线程运行startActivity完成跳转。
由此,ARouter的基本功能也就实现了,看到最后可以总结出,归根结底还是运用的startActivity,只不过判断信息都巧妙的存放在了路由表里而已。