一、为什么需要路由框架

1.原生的路由方案存在的问题

首先谈一谈原生的路由方案存在的问题以及为什么需要路由框架。我们所使用的原生路由方案一般是通过显式intent和隐式intent两种方式实现的,而在显式intent的情况下,因为会存在直接的类依赖的问题,导致耦合非常严重;而在隐式intent情况下,则会出现规则集中式管理,导致协作变得非常困难。而且一般而言配置规则都是在Manifest中的,这就导致了扩展性较差。除此之外,使用原生的路由方案会出现跳转过程无法控制的问题,因为一旦使用了StartActivity()就无法插手其中任何环节了,只能交给系统管理,这就导致了在跳转失败的情况下无法降级,而是会直接抛出运营级的异常。

这时候如果考虑使用自定义的路由组件就可以解决以上的一些问题,比如通过URL索引就可以解决类依赖的问题;通过分布式管理页面配置可以解决隐式intent中集中式管理Path的问题;自己实现整个路由过程也可以拥有良好的扩展性,还可以通过AOP的方式解决跳转过程无法控制的问题,与此同时也能够提供非常灵活的降级方式。

2.为什么要用路由组件

前面提到的主要是开发与协作中的问题,而使用一款路由框架时还会涉及到其他的两个大方面:一方面是组件化,而另一方面就是Native和H5的问题。刚才所提到的主要是开发和协作中作为开发者所需要面对的问题,而一旦一款APP达到一定体量的时候,业务就会膨胀得比较严重,而开发团队的规模也会越来越大,这时候一般都会提出组件化的概念。组件化就是将APP按照一定的功能和业务拆分成多个小组件,不同的组件由不同的开发小组来负责,这样就可以解决大型APP开发过程中的开发与协作的问题,将这些问题分散到小的APP中。目前而言组件化已经有非常多比较成熟的方案了,而自定义路由框架也可以非常好地解决整个APP完成组件化之后模块之间没有耦合的问题,因为没有耦合时使用原生的路由方案肯定是不可以的。


另外一个问题就是Native与H5的问题,因为现在的APP很少是纯Native的,也很少会有纯H5的,一般情况下都是将两者进行结合。这时候就需要非常便捷并且统一的跳转方案,因为在H5中是无法使用StartActivity()跳转到Native页面的,而从Native跳转到H5页面也只能通过配置浏览器的方式实现。

二、路由框架的特点

为了解决以上的问题就需要实现一个自定义的路由框架,而路由框架一般都具有以下的三种特点:

  • 分发:把一个URL或者请求按照一定的规则分配给一个服务或者页面来处理,这个流程就是分发,分发是路由框架最基本的功能,当然也可以理解成为简单的跳转。
  • 管理:将组件和页面按照一定的规则管理起来,在分发的时候提供搜索、加载、修改等操作,这部分就是管理,也是路由框架的基础,上层功能都是建立在管理之上。
  • 控制:就像路由器一样,路由的过程中,会有限速、屏蔽等一些控制操作,路由框架也需要在路由的过程中,对路由操作做一些定制性的扩展,比方刚才提到的AOP,后期的功能更新,也是围绕这个部分来做的。

三、ARouter的7个优势

  • 优势一:直接解析URL路由,解析参数并赋值到对应目标字段的页面中。
  • 优势二:支持多模块项目,因为现在很少有APP是单模块的项目,一般都是多模块单工程的,由不同的团队负责不同的模块开发,这时候支持多模块项目开发就显得尤为重要。
  • 优势三:支持InstantRun,目前很多路由框架并不支持InstantRun,而InstantRun是Google在AndroidStudio2.0阿尔法版本中提供的新功能,其类似于代码的日更新,其只不过面向的是开发过程,这样做可以在开发的过程中减少开发和编译的次数,可以简单地将代码修改即时地同步到APK中,从而可以大规模降低开发复杂度。
  • 优势四:允许自定义拦截器,ARouter是支持拦截器的,而拦截器其实就是AOP的实现,可以自定义多个拦截器解决一些面向行为编程上出现的问题。
  • 优势五:ARouter可以提供IoC容器,IoC其实就是控制反转,这一部分做过服务端开发的朋友可能比较了解,因为服务端开发经常用到的Spring框架能够提供的一个非常重要的能力就是控制反转。
  • 优势六:映射关系自动注册,在页面不是很多的小型APP上面,自动注册并不会体现出太大优势,但是对于大型APP而言,可能页面数量已经达到的几十个或者数百个,在这样的情况下,自动注册就显得非常重要了,因为不可能将每一个页面都通过代码的方式进行注册。
  • 优势七:灵活的降级策略,ARouter可以提供很多种降级策略供用户自行选择,而原生的路由方案存在无法灵活降级的问题,StartActivity()一旦失败将会抛出运营级异常。

四、ARouter底层原理

接下来进入分享的第二部分:ARouter的技术方案。其实如果大家看过ARouter的源码就会知道ARouter提供了两个SDK,分别是面向两个不同的阶段。本身API这个SDK是面向运行期的,而Compiler这个SDK则是作用于编译期的,从工程上ARouter就是划分成了这两个SDK。

最基础的就是Compiler这个SDK,其内部有三个处理器,分别是:Route Processor,Interceptor Processor以及Autowire Processor,通过名字就可以看出这三个处理器分别是处理路径路由、拦截器和进行自动装配的。而API的SDK是用户在运行期使用的,这一部分主要分为四层。最上层是Launcher层,这一层是开发者可以直接用到的,其实所有的API都是在这一层中。在Launcher层的下一层就是Frossard层,从上图中可以看到Frossard层也是绿色的,表示这一层也是可以被外部调用的,Frossard层其实包含了三部分,分别是:Service、Callback和Template,这里的Service概念和服务端的Service概念是相似的,也是在客户端的简单引申,但是却不同于Android组件中的Service,这里的Service是ARouter抽象出来的概念,从本质上讲,这里的Service是接口,从意义上讲是将一定的功能和组件封装成接口,并对外提供能力。Template则是模板,主要用于在编译期执行的SDK,这个SDK会在编译期生成一些映射文件,而这些映射文件会按照Template组件中提供的模板来生成,这样按照一定的规则和约束生成映射文件也方便Route在运行的时候进行读取。再往下一层就完全是SDK的内部实现了,这一层包括了Ware House、Thread、Log、Exception以及Class工具。Ware House主要存储了ARouter在运行期间加载的一些配置文件以及映射关系;而Thread则是提供了线程池,因为存在多个拦截器的时候以及跳转过程中都是需要异步执行的;Class工具则是用于解决不同类型APK的兼容问题的。再下一层就是Logistics Center,从名字上翻译就是物流中心,整个SDK的流转以及内部调用最终都会下沉到这一层,当然也会按照功能模块进行划分。

下图是按照功能组件的方式来对于整个框架进行划分的,其实ARouter在设计上使用了三种思想:Bootstrapping、Extensibility以及Simple & Enough。首先,ARouter的组件是自举的,这个概念借鉴了编程中的自举;除此之外ARouter组件还具有良好的扩展性,因为像Route这样的东西是整个APK的基础组件,不可能经常变更,也不可能经常升级,所以应该具有良好的扩展性,而不需要通过经常升级来解决问题;而ARouter最重要的宗旨就是简单并且够用,ARouter不会有非常复杂的使用方式和调用方式,但是功能却是非常全面的。

可以从图中看出ARouter的最外面一层就是Route,这一层是整个框架的基础,而这一层也应该非常稳定,几乎不会发生变更。再往上一层就是Service层,这一层是依赖于底层的Route构建起来的,也就是说Service层是通过Route才实现的功能。再往上一层就是Interceptor层,拦截器层则是通过Service的机制实现的,拦截器和Service都会作用于整个路由的过程中,所以说组件之间是自举的,因为Service和Interceptor在没有Route时是不会出现的,它们都是由Route层构建起来的,反过来又会作用于Route层,这也是ARouter的可扩展性的表现,后续的扩展都会基于Service层来实现。

接下来分享一下ARouter的具体解决方案,也就是ARouter是如何解决上述问题的。

1.页面注册:注解&注解处理器


首先,对于页面自动注册的问题,ARouter是可以自动注册映射关系的,因为大型APP的页面往往很多,会存在几十甚至上百个页面,所以手动注册映射关系会非常麻烦,需要写很多重复冗余的代码,并且需要调用很多接口,而为了避免这样的麻烦,ARouter实现了页面的自动注册。而为了解决隐式intent的问题和将所有配置都存储在Manifest中这样集中式的问题,首先想到的就是分布式管理,可以将所有的配置都放在目标页面,这样就实现了“All In One”,就是一个页面中所有的配置都要聚合在该页面中,这样就解决上面的问题。不同的页面由不同的配置负责,这样修改也变得非常容易,而不需要将配置散落在整个APP四处。

其实配置相当于一个注解,所以ARouter采用的方案就是在每个目标页面上使用注解来标注一些参数,比方上图中的Path标注就是其路径,图中也可以看到对于注解的声明。使用注解时会遇到的第一个问题就是需要找到处理注解注解的时机,如果在运行期处理注解则会大量地运用反射,而这在软件开发中是非常不合适的,因为反射本身就存在性能问题,如果大量地使用反射会严重影响APP的用户体验,而又因为路由框架是非常基础的框架,所以大量使用反射也会使得跳转流程的用户体验非常差。所以ARouter最终使用的方式是在编译期处理被注解的类,而可以做到在运行中尽可能不使用反射。其实这一部分就是注解处理器,注解处理器其实是作用在JVM上的,可以通过插入一部分代码来处理被注解标注的类。

页面注册的整个流程如下图所示:首先通过注解处理器扫出被标注的类文件;然后按照不同种类的源文件进行分类,这是因为ARouter是一个框架,其能够提供的功能非常多,所以不仅仅提供了跳转功能,它也能够实现模块之间的解耦,除此之外ARouter还能够提供很多的功能,像刚才提到的拦截器可以实现自动注册,其实ARouter中的所有组件都是自动注册的;在按照不同种类的源文件进行分类完成之后,就能够按照固定的命名格式生成映射文件,这部分完成之后就意味着编译期的部分已经结束了;而最后一步的初始化其实是发生在运行期的,在运行期只需要通过固定的包名来加载映射文件就可以了,因为生成是由开发者自己完成的,所以会了解其中的规则,就可以在使用的时候利用相应的规则反向地提取出来。这就是页面自动注册的整个流程。

下图是ARouter在编译期生成的类文件,命名规则就是工程名++Group++模块名。可以看出这里面包含了Group、Interceptor以及Route,所以会有很多种不同的映射文件,对于这部分而言,大家可以在GitHub上自行下载Demo,运行一下看看在Build目录下生成的一些映射文件。

2.加载:分组管理,按需加载


接下来要分享的就是加载,刚才已经解决了注册的问题,这时候就到了运行期,而在运行期就需要将映射关系加载进来。而加载的时候就会遇到另一个问题,因为需要面对长久的APP的设计,所以不可能一次性把所有的页面都加载进来,当APP有一百或者几百个页面的时候,一次性将所有页面都加载到内存中本身对于内存的损耗是非常可怕的,同时对于性能的损耗也是不可忽视的。所以ARouter中提出了分组的概念,ARouter允许某一个模块下有多个分组,所有的分组最终会被一个root节点管理。如上图中所示,假设有4个模块,每个模块下面都有一个root结点,每个root结点都会管理整个模块中的group节点,每个group结点则包含了该分组下的所有页面,也就是说可以按照一定的业务规则或者命名规范把一部分页面聚合成一个分组,每个分组其实就相当于路径中的第一段,而每个模块中都会有一个拦截器节点就是Interceptor结点,除此之外每个模块还会有控制拦截反转的provider结点。

下图表现的就是刚才提到的按需加载。ARouter在初始化的时候只会一次性地加载所有的root结点,而不会加载任何一个Group结点,这样就会极大地降低初始化时加载结点的数量。因为每个模块中可能有N个分组,每个分组中可能有N个页面,如果一次性地将所有的页面全部加载进来,那么整个复杂度可能不只是O(N^2),但是每个模块都只加载其根节点,从算法的角度考虑可能就是复杂度为O(N)的方案,也就是有多少个模块就只需要加载多少个结点。下图中的三个圈中体现的就是ARouter初始化时加载的状况。那么什么时候加载分组结点呢?其实就是当某一个分组下的某一个页面第一次被访问的时候,整个分组的全部页面都会被加载进去,这就是ARouter的按需加载。其实在整个APP运行的周期中,并不是所有的页面都需要被访问到,可能只有20%的页面能够被访问到,所以这时候使用按需加载的策略就显得非常重要了,这样就会减轻很大的内存压力。

3.拦截器

分享完分组管理和按需加载之后,接下来分享一下关于拦截器的内容。原生的路由方案中存在的问题就是其无法在页面跳转的过程中插入一些自定义逻辑,而拦截器就是ARouter中提出的针对AOP思想的实现。

那么ARouter是如何实现拦截器的呢?其实ARouter对于拦截器的实现方式与刚才提到的路径注册方式是一样的,只是使用了不同的注解而已。如上图中所显示的,存在拦截器1至5,但是这5个拦截器并不是会都生效。在上图中可以看出从A页面到B页面的跳转流程中只有三个拦截器生效了,首先跳转到第一个拦截器,如果跳转的条件符合那么只需要在拦截器进行一些自定义的操作,等拦截器处理完成之后会放行给下一个拦截器,以此类推当经过了所有的拦截器之后才会结束整个跳转的流程,如果每个拦截器都放过的话才能够跳转到最终的页面。这里因为是自动注册的,所以可以将不同功能的拦截器放在不同功能的模块中,只有模块被打包到整个项目中,因为自动注册机制所以拦截器就会生效,如果不将这些拦截器放到模块并打包到项目中,那就不会生效,这样就不用去做很多注册与反注册的工作。如图所示的拦截器2就是没有被打包进来的,所以就不会生效,如果修改打包参数,将拦截器2打包到APP中就会生效,这部分就是对于拦截器的实现。

直接讲拦截器可能不容易让大家理解,那么就用这样形象的比喻来解释一下,拦截器就是像是一个汉堡,汉堡中夹心的无论是生菜、牛肉还是芝士都像拦截器一样,当在做汉堡时就相当于在做APK,打包了哪些模块就相当于在汉堡中放了哪些层,在吃的时候就会把这一层都咬掉,但是汉堡的每一层都有可能是芝士、牛肉或者铁片,当遇到某一层是铁片的时候就无法咬下去了,也就是被拦截住了。同样的拦截器就是需要当条件符合的时候才能让跳转流程继续执行,同样像汉堡一样,如果使用了太多的拦截器最终会导致汉堡变成了“巨无霸”,所有的拦截器会在任意两次跳转之间生效,声明了大量的拦截器会影响整个跳转流程的性能,拦截器的更详细内容会在第三部分的最佳实践中继续为大家介绍。

4.InstantRun兼容

接下来分享一下ARouter如何实现对于InstantRun的兼容。市面上的框架一般对于这一部分的兼容都是缺失的,对于InstantRun的兼容从技术上看并不是非常难以实现的,在实现时只需仔细阅读InstantRun的源码就可以了。在实现对于InstantRun的兼容时是存在如下图所示的四种情况的,当AndroidSDK版本大于21的时候,会存在SplitAPK的特性支持的,会允许将一个APK切分成多个小APK,当然其实这并不是APK的切分,而实际上是Dex的切分,也就每个依赖都会打包成小的Dex放在APP+包名的目录下的,这与传统情况下是不同的。

所以只需要参照这张表格并根据AndroidSDK和GradlePlugin的版本就可以解决了。如果Android版本超过21并且Gradle插件的版本超过2.3.0,这时候就会支持SplitAPK,从中可以获取所有Dex的位置,进而实现映射关系的加载。除此之外的三种情况都不支持SplitAPK的,这种情况下就需要看一下InstantRun的源码,就会发现在源码中原本应该存放业务代码的Dex的地方替换成了InstantRun的SDK的Dex,而是将业务代码打包在一个ZIP中,此时只需要通过运行时的反射拿到InstantRun的SDK的一个类的Path,而在获取Path时是存在静态方法的getDexFileDirectory,只需要执行一下就可以知道当前版本将真实的Dex放在什么地方,通过对于这两种方式的兼容就可以实现对于InstantRun的兼容。

5.依赖注入的实现

接下来分享依赖注入的实现,这一部分是路由框架在进行大规模组件之间解耦时比较重要的一点。其实依赖注入就是对于控制反转思想的实现,这部分服务端使用的比较多,客户端可能使用不是非常多。ARouter对于依赖注入的实现主要分成如下图所示的两个部分。

首先编译期扫出需要自动装配的字段,之前对于自动装配也已经提到了,就是在Compiler中的处理器Autowire Processor,自动体现在将字段自动地进行赋值而不需要用户手动干预,在扫除自动转配的字段之后,需要把自动装配的字段注册在映射文件中,然后跳转的时候按照预先的配置从URL中提取参数,并按照类型放入Intent中,这样就解决了如何通过URL跳转到Native页面,并将URL中的参数传递进来。上图中绿色的部分则是在运行期的早期实现,这部分通过反射拿到ActivityThread类,调用它的currentActivityThread方法,拿到当前的ActivityThread实例,之后通过反射替换ActivityThread实例中的字段mInstrumentation,并覆写Instrumentation的newActivity方法,在Activity实例化的时候,通过反射把Intent预先存好的参数值写入到需要自动装配的字段中。这是早期的做法,这种做法有一个非常严重的问题就是会不够稳定,路由框架作为整个APP的基础如果不足够稳定,那么造成的影响是非常严重的。用户如果使用自动装配这样的功能的时候失败的话,问题就非常严重了,可能导致用户的代码出现NPE,出现这样的问题就不简单是用户体验的问题了,有可能导致APP崩溃。


所以目前的实现方式则换成了上图的方式,在编译期基本没有变化,但是在运行期进行了调整。在运行期会在目标页面进行初始化的时候调用ARouter.inject(this),将自身的实例传递进去。ARouter会查找到编译期为调用方生成的注入辅助类,而这里提到的注入辅助类就是比方在编译期是扫描到一个A页面需要进行自动装配,此时就会为A页面生成一个注入辅助类,在运行的时候调用注入辅助类的方法对于字段进行赋值,这其实就是模拟用户对于字段进行赋值,虽然看起来可能麻烦一些,但是可以保证注入的稳定性,而且最终体现的效果是相同的,用户不需要写重复冗余的代码,而且在实现时并不需要在每一个目标页面上都调用这一行代码,完全可以将这些代码放在基类中,而在实例化辅助类之后,调用其中的inject方法完成对于字段的赋值。

下图所示的代码就是在编译期生成的注入辅助类,这部分实际上就是模仿了用户的写法,通过一定的工具和规则生成这样的代码,免去用户手写重复和冗余的代码,在用户的角度来看也是自动注入,这一部分就是依赖注入的具体实现,大家也可以参考GitHub上的源码来研究具体实现。

Android路由框架ARouter原理相关推荐

  1. Android平台页面路由框架ARouter原理

    本次分享将主要围绕以下几个方面: 一.为什么需要路由框架 二.ARouter的技术方案 三.使用ARouter的最佳实践 四.未来开发计划 一.为什么需要路由框架 原生的路由方案存在的问题 首先谈一谈 ...

  2. Android 路由框架ARouter最佳实践

    一.功能介绍 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中 支持多模块工程使用 支持添加多个拦截器,自定义拦截顺序 支持依赖注入,可单独作为依赖注入框架使用 支持InstantRun 支 ...

  3. Android项目解耦--路由框架ARouter的使用

    Android项目解耦–路由框架ARouter源码解析 前言 随着业务量的增长,客户端必然随之越来越业务和功能模块耦合越来越生,开发人员代码维护成本越来越高. App一般都会走向组件化.插件化的道路, ...

  4. XRouter 一个轻量级的Android路由框架,基于ARouter上进行改良,优化Fragment的使用,可结合XPage使用

    XRouter 一个轻量级的Android路由框架,基于ARouter上进行改良,优化Fragment的使用,可结合XPage使用. 关于我 特征 由于是借鉴了ARouter,拥有ARouer所有特征 ...

  5. xpage 传参_一个轻量级的Android路由框架,基于ARouter上进行改良,优化Fragment的使用,可结合XPage使用。...

    XRouter 一个轻量级的Android路由框架,基于ARouter上进行改良,优化Fragment的使用,可结合XPage使用. 关于我 特征 由于是借鉴了ARouter,拥有ARouer所有特征 ...

  6. Android项目解耦--路由框架ARouter源码解析

    前言 上一篇文章Android项目解耦–路由框架ARouter的使用讲述了ARouter在项目中的使用,这边文章主要对ARouter的源码进行学习和分析. ARouter的结构 ARouter主要由三 ...

  7. 路由框架ARouter最全源码解析

    ARouter是2017年阿里巴巴开源的一款Android路由框架,官方定义: ARouter是Android平台中对页面,服务提供路由功能的中间件,提倡简单且够用 有下面几个优势: 1.直接解析UR ...

  8. (4.2.40)阿里开源路由框架ARouter的源码分析

    一需求背景 1 Android原生方案的不足 2 自定义路由框架的适用场景 3 对自定义路由框架的设想 二ARouter的概述 三ARouter的引入和使用 四源码分析 1 arouter-annot ...

  9. Android路由方案ARouter分析

    一.路由方案 原生的路由方案缺点: 显式:直接的类依赖,耦合严重 隐式:规则集中式管理,协作困难 Manifest扩展性较差 跳转过程无法控制 失败无法降级 ARouter的优势: 使用注解,实现了映 ...

最新文章

  1. 电商618背后的那些技术事儿
  2. 【node】------mongoose的基本使用------【巷子】
  3. MySQL的空值查询
  4. 北斗有 35 颗卫星,而 GPS 有 24 颗卫星,为什么二者数量不同?
  5. tf.assign() 更新ref通过给它重新赋值
  6. dedeCMS解决问题:“用户资料尚未通过审核,因此空间禁止访问”?
  7. php中is null,php中empty(), is_null(), isset()函数区别
  8. springcloud入门实战进阶百度云,【MyBatis 5(1)
  9. validation problems were found problem cvc-complex-type.2.4a
  10. 现代ups电源及电路图集_2020山特UPS电源自动开机200KVA实力
  11. 推箱子游戏的java设计思路_用JAVA实现一个推箱子游戏
  12. NP、OSPF邻居邻接关系
  13. Oracle软件安装及手工建库
  14. WebApp列表:15个个性化礼物定制服务
  15. 掘进机数字孪生,掘进机远程智能控制
  16. FineBI产品简介
  17. 财经管理中的计算机应用内容,财大 财经管理中的计算机应用
  18. 【转】App应用内搜索:移动搜索的新入口
  19. 东北大学计算机保研清华难吗,学霸宿舍:他们全部保研清华、中科院、东大等名校,值得借鉴!...
  20. linux-centos8安装nginx

热门文章

  1. 梦幻手游服务器维护摆摊公示时间,梦幻西游手游4月13日维护公告 摆摊调整
  2. 荣耀路由x1 虚拟服务器,荣耀路由X1增强版怎么设置端口映射规则
  3. MySQL隔离性实现原理
  4. xP怎样取消计算机管理员,解决xp命令提示符提示已被系统管理员停用
  5. CF1491C Pekora and Trampoline
  6. 韩国的品牌创造之路(上)
  7. Excel如何新建指定名称工作表
  8. AutoCAD2015有时候会显示乱七八糟的线
  9. Win11怎么自定义设置开始菜单 打造创意的Windows11开始菜单的技巧
  10. 常见的前端开发面试题(附答案)