不深入源码,只讲大致逻辑,毕竟源码还是需要自己去研究的。

    image.png

    根据官方文档的介绍,总结一下这个框架的特性,知道特性后,再去根据每个特性去分析下原理,除去一些没特点的特性,总结了一下,有下面几个:

    1. 支持URI正则匹配
    2. 页面配置支持Java代码动态注册,或注解配置自动注册
    3. 支持配置全局和局部拦截器,可在跳转前执行同步/异步操作,例如定位、登录等
    4. 支持配置全局和局部降级策略
    5. ServiceLoader模块,实现组件化、模块间通信

    接下来,首先wmrouter是一个路由框架,那么最重要的功能当然是模块间跳转和通信,分析原理,重点自然是这两个,该框架也根据这两个功能衍生出两个特性,URI的分发过程和ServiceLoader模块,下面就讲这两点。

    从官方copy一份URI跳转的核心设计思路
    image.png

    看起来好像东西挺少的,简单说说,所有的跳转包括外部(浏览器发起的)跳转,都会被封装为UriRequest,每一个UriRequest都会经历一个RooterUriHandler(可以自己设计,wmrouter有提供默认),RooterUriHandler根据优先级处理不同的UriHandler,在整个过程中,拦截器和
    UriHandler挂钩,也就是在处理Handler,可以做一些拦截操作,具体什么操作下面会分析到。

    了解了大致流程,在开始具体分析前,需要先了解一下apt的作用,https://www.yuque.com/mikaelzero/cg3abf/yudshn 之前一篇博客有根据wmrouter单独设计了一个跳转功能,大体上能够理解apt在wmrouter中的作用。

    接下来看下具体的URI分发过程图,别看图复杂,逻辑很简单

    image.png

    第一步,生成UriRequest
    第二步,进入RootUriHandler处理过程
    第三步,处理不同的Handler

    RootUriHandler是使用者调用init函数进行初始化,一般使用默认的DefaultRootUriHandler

    1. DefaultRootUriHandler rootHandler = new DefaultRootUriHandler(context);
    2. Router.init(rootHandler);

    在DefaultRootUriHandler中添加了四个Handle,对应图上的四种Handler

    1. addChildHandler(mPageAnnotationHandler, 300);
    2. addChildHandler(mUriAnnotationHandler, 200);
    3. addChildHandler(mRegexAnnotationHandler, 100);
    4. addChildHandler(new StartUriHandler(), -100);

    最后一个参数优先级,那么对应图上刚好是第三步中从左到右进行处理

    image.png

    怎么分发?
    处理方法在UriHandle中,他是所有XXXHandle的老爹

    1. public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
    2. if (shouldHandle(request)) {
    3. if (mInterceptor != null && !request.isSkipInterceptors()) {
    4. ......
    5. } else {
    6. handleInternal(request, callback);
    7. }
    8. } else {
    9. callback.onNext();
    10. }
    11. }

    handleInternal由不同特性的Handle进行处理,但基本上都是对自身包含的Handle列表进行串行调用,like this

    1. @Override
    2. protected void handleInternal(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
    3. next(mHandlers.iterator(), request, callback);
    4. }
    5. private void next(@NonNull final Iterator<UriHandler> iterator, @NonNull final UriRequest request,
    6. @NonNull final UriCallback callback) {
    7. if (iterator.hasNext()) {
    8. UriHandler t = iterator.next();
    9. t.handle(request, new UriCallback() {
    10. @Override
    11. public void onNext() {
    12. next(iterator, request, callback);
    13. }
    14. @Override
    15. public void onComplete(int resultCode) {
    16. callback.onComplete(resultCode);
    17. }
    18. });
    19. } else {
    20. callback.onNext();
    21. }
    22. }

    其他不重要,知道他会对他的所有ChildHandler进行处理就可以了,这是主要的分发流程。

    现在从图中的左到右,分析下他们的工作流程,很简单

    PageAnnotationHandler对应的是WMRouter提供的RouterPage注解,如果你看过上面发的那篇关于apt的文章,你应该知道他大致的流程了。
    如果 scheme为 wm_router host为 page的uri,那么他会处理,否则给下一个Handler,框架会对RouterPage注解的类进行扫描后获取需要的信息,并注册到PageAnnotationHandler中,这样PageAnnotationHandler就知道有多少个子Handler需要去处理了 like this

    1. public class PageAnnotationInit_98a9894a5c616338294a4e09f7a7bc91 implements IPageAnnotationInit {
    2. public void init(PageAnnotationHandler handler) {
    3. handler.register("/test/handler", new TestPageAnnotation.TestHandler());
    4. handler.register("/test/interceptor", new TestPageAnnotation.TestInterceptorHandler(), new UriParamInterceptor());
    5. }
    6. }

    至于这个init方法怎么调用的,分析ServiceLoader再说。

    还有其他三个Handler,不需要分析了,相信聪明的你马上就知道原理和PageAnnotationHandler大体上是一致的
    UriAnnotationHandler对应的注解为 RouterUri注解
    RegexAnnotationHandler对应的注解为 RouterRegex注解
    StartUriHandler对应尝试直接用setData(Uri)隐式跳转启动Uri的Handler

    现在你知道了,关于注解的原理都是通过apt来进行代码生成,然后再去调用生成的代码就行了,问题来了,生成代码的类名不都是随机名字吗?怎么调用?

    image.png

    如果你看了上面的分析,并且去自己生成了下代码,你会发现,这些类的共性都是他们都会实现接口

    image.png

    ServiceLoader是根据SPI进行扩展,功能就是获取到实现了某个接口的所有实现类,大致说下原理,怎么调用函数?当然是要先实例化,wmrouter分出了单例的参数,如果是单例会在单例池中找,如果没有就会根据类的信息尝试在Provider(也是一个池,可以使用者自己使用RouterProvider注解来实例化)中找,如果没有就直接newInstance,这样类的实例拿到了,就可以直接调用用apt生成的类的函数

    那么类的信息怎么来?想要get,当然要先put了,怎么put,wmrouter用的是通过asm插件来生成一个类,叫ServiceLoaderInit,该插件会去扫描service包(在生成类的时候,会统一放在service包名下)下的ServiceInit_xxx1的类,然后进行调用,like this

    1. * public class ServiceLoaderInit {
    2. *
    3. * public static void init() {
    4. * ServiceInit_xxx1.init();
    5. * ServiceInit_xxx2.init();
    6. * }
    7. * }

    那ServiceLoaderInit这个类怎么调用?反射,因为ServiceLoaderInit是在gradle的任务中生成的,所以编译期获取不到

    1. Class.forName(Const.SERVICE_LOADER_INIT)
    2. .getMethod(Const.INIT_METHOD)
    3. .invoke(null);

    大致逻辑明白了后,去看wmtouer提供的比如函数调用什么的,逻辑都是类似的。