WMRouter使用和源码分析

前言

项目组件化过程当中使用了WMRouter,为了更好的理解并使用。花了一周的时间研究了一下WMRouter(v1.2.0版本)。下面从四个方面说下本身的理解,但愿能给你们提供帮助。html

  1. what it is? what can it do?
  2. how to use it ?
  3. how it works ?
  4. why design like that?

一,WMRouter是什么,能解决什么问题?

1.1,什么是路由框架?

路由框架,也就是路由的做用。路由是起什么做用呢?就像送快递,从小县城到省会,再从省会发到北京分拨中心,而后再从北京分拨中心发到回龙观,再从回龙观发到具体的小区。路由框架解决的就是如何从A页面跳转到B页面的问题,其会在A和B之间创建数个节点。经过这些节点依次进行转发,最终达到目的地。java

1.2,为何须要路由框架?

Android原生已经支持AndroidManifest去管理App跳转,为何要有路由库?android

  • 显示Intent:项目庞大之后,类依赖耦合太大,不适合组件化拆分
  • 隐式Intent:协做困难,调用时候不知道调什么参数。每一个注册了Scheme的Activity均可以直接打开,有安全风险
  • AndroidMainfest集中式管理比较臃肿
  • 没法动态修改路由,若是页面出错,没法动态降级
  • 没法动态拦截跳转,譬如未登陆的状况下,打开登陆页面,登陆成功后接着打开刚才想打开的页面
  • H五、Android、iOS地址不同,不利于统一跳转

1.3,WMRouter的特色。

WMRouter是一款Android路由框架,主要提供URI分发、ServiceLoader两大功能(后面会看到,URI分发功能也是用ServiceLoader实现的)git

URI分发功能可用于跨module的页面跳转、动态下发URI连接的跳转等场景,特色以下:github

  1. 跳转的页面支持配置scheme、host、path。
  2. 支持URI正则匹配。
  3. 支持页面Exported控制,特定页面不容许外部跳转
  4. 默认使用注解配置自动注册,也支持Java代码动态注册。
  5. 某些页面须要登陆等条件才能进入的时候,能够配置拦截器,可在跳转前执行同步/异步操做。
  6. 支持单次跳转特殊操做:Intent设置Extra/Flags、设置跳转动画、自定义StartActivity操做等
  7. 支持配置单次和全局跳转监听(能够实现降级策略,也能够自定义处理逻辑)
  8. 彻底组件化设计,核心组件都可扩展、按需组合,实现灵活强大的功能

WMRouter提供了ServiceLoader模块,相似Java中的 java.util.ServiceLoader,但功能更加完善。经过ServiceLoader能够在一个App的多个模块之间经过接口调用代码,实现模块解耦,便于实现组件化、模块间通讯,以及和依赖注入相似的功能等。其特色以下:正则表达式

  1. 使用注解自动配置
  2. 支持获取接口的全部实现,或根据Key获取特定实现
  3. 支持获取Class或获取实例
  4. 支持无参构造、Context构造,或自定义Factory、Provider构造
  5. 支持单例管理
  6. 支持方法调用

二,WMRouter怎么用?

2.1,URI分发功能基本使用

详细使用参见WMRouter设计与使用文档,这里就大概说下整体的流程。设计模式

第一步,添加依赖

  • 根目录的build.gradle配置插件
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        // Android Gradle插件
        classpath 'com.android.tools.build:gradle:3.2.1'
        // 添加WMRouter插件
        classpath "com.sankuai.waimai.router:plugin:1.x"
    }
}
复制代码
  • Application模块中的build.gradle:
apply plugin: 'com.android.application'
// 应用WMRouter插件
apply plugin: 'WMRouter'
复制代码
  • 添加对wmrouter的依赖。若是有基础依赖库,能够添加到基础依赖库。这样不用每一个module都添加。
compile 'com.sankuai.waimai.router:router:1.x'
复制代码
  • 在使用了注解的每一个模块中配置注解生成器。注意是每一个使用了注解的模块都要配置。
annotationProcessor 'com.sankuai.waimai.router:compiler:1.x'
复制代码

第二步,Proguard配置

  • WMRouter已经内置了Proguard配置。(详见源码router/proguard-rules.pro),而且在router的build.gradle中已经配置了consumerProguardFiles属性。因此使用AAR依赖时通常不须要重复配置。这一点挺好的,若是咱们本身写SDK的话,也建议这样作。
# 保留ServiceLoaderInit类,须要反射调用
-keep class com.sankuai.waimai.router.generated.ServiceLoaderInit { *; }
# 避免注解在shrink阶段就被移除,致使obfuscate阶段注解失效、实现类仍然被混淆
-keep @interface com.sankuai.waimai.router.annotation.RouterService
复制代码

第三步,初始化SDK

在Application.onCreate中初始化:最简单的方式初始化方式就两行代码。数组

// 建立RootHandler
DefaultRootUriHandler rootHandler = new DefaultRootUriHandler(context);
// 初始化,必须在主线程调用
Router.init(rootHandler);
复制代码

第四步,配置跳转activity

跳转的目标Activity,添加注解@RouterUri浏览器

@RouterUri(path = "/test/schemehost", scheme = "test", host = "test.demo.com")
public class AdvancedDemoActivity extends BaseActivity {
    ...
}
复制代码

第五步, 发起URI跳转

发起跳转有好几种方式,经常使用的有如下三种。其实最经常使用的是方式三。缓存

// 方式1,直接传context和URI
Router.startUri(context, "/account");

// 方式2,或构造一个UriRequest
Router.startUri(new UriRequest(context, "/account"));

// 方式3,使用DefaultUriRequest,最经常使用
new DefaultUriRequest(context, uri)//传入context和目标uri
        // startActivityForResult使用的RequestCode
        .activityRequestCode(100)
        // 设置跳转来源,默认为内部跳转,还能够是来自WebView、来自Push通知等。
        // 目标Activity可经过UriSourceTools区分跳转来源。
        .from(UriSourceTools.FROM_INTERNAL)
        // Intent加参数
        .putIntentExtra("test-int", 1)
        .putIntentExtra("test-string", "str")
        // 设置Activity跳转动画
        .overridePendingTransition(R.anim.enter_activity, R.anim.exit_activity)
        // 监听跳转完成事件
        .onComplete(new OnCompleteListener() {
            @Override
            public void onSuccess(@NonNull UriRequest request) {
                ToastUtils.showToast(request.getContext(), "跳转成功");
            }

            @Override
            public void onError(@NonNull UriRequest request, int resultCode) {

            }
        })
        // 这里的start实际也是调用了Router.startUri方法
        .start();
复制代码

2.2, URI分发功能的高级配置

上面5步只是最基本的URI分发功能使用,SDK还提供了不少设置,方便在实际项目中使用。另外,在使用过程当中,还有一些比较容易遗漏的点。下面进行详细说明。

2.2.1,依赖和混淆配置

  • 注意:若是项目配置的Android Gradle插件版本比WMRouter依赖的版本低,默认会覆盖为高版本(可经过./gradlew buildEnvironment命令查看classpath的依赖关系)。若是不但愿被覆盖,能够尝试把配置改为:

    classpath("com.sankuai.waimai.router:plugin:1.x") {
        exclude group: 'com.android.tools.build'
    }
    复制代码
  • 若是使用了@RouterService注解和ServiceLoader加载实例的功能,会反射调用构造方法,应根据实际状况配置Proguard,避免实现类中的构造方法被移除,示例以下。

    # 使用了RouterService注解的实现类,须要避免Proguard把构造方法、方法等成员移除(shrink)或混淆(obfuscate),致使没法反射调用。实现类的类名能够混淆。
    -keepclassmembers @com.sankuai.waimai.router.annotation.RouterService class * { *; }
    复制代码
    • 这里的实际状况指的是?到底什么状况下才必须在项目中配置呢?查看源码后发现,用到反射的地方有:DefaultFactory,ProviderPool,以及自定义的IFactory。 默认是使用DefaultFactory进初始化,最终是使用clazz.newInstance()进行实例化对象的。若是没有使用自定义工厂、@RouterProvider、或者非默认参数构造函数以外的其余构造函数,就不用添加上面的配置。可是我的感受,安全起见,最好一开始就将全部混淆都配置上,防止后面更改了逻辑以后遗漏了。
    // CustomFactory.java--自定义工厂
    
        IFactoryService service4 = Router.getService(IFactoryService.class, "/factory", new IFactory() {
            @NonNull
            @Override
            public <T> T create(@NonNull Class<T> clazz) throws Exception {
                return clazz.getConstructor(String.class).newInstance("CreateByCustomFactory");
            }
        });
    复制代码

2.2.2,经常使用的设置

  • com.sankuai.waimai.router.core.RootUriHandler#setGlobalOnCompleteListener:设置全局跳转完成的监听,能够在其中跳转失败时执行全局降级逻辑。
  • com.sankuai.waimai.router.common.DefaultRootUriHandler#lazyInit:提早初始化(我的感受,初始化指的是,扫描全部注解生成的注册代码、反射获取class,建立接口的实例、执行注册,从而生成路由表的过程)。该方法最好放到子线程执行,防止启动过慢。若是没有提早执行该方法,也会在路由分发的过程当中,执行注册。
  • 配置检查与Debugger配置。使用注解进行配置,注解每每分散在一个工程的不一样代码文件甚至不一样的工程中。若是没有很好的文档或代码约束,很容易出现多个页面配置了相同的URI或Service致使冲突的问题。 所以WMRouter在注解生成阶段、APK打包阶段,使用注解生成器和Gradle插件进行检查,检查到配置冲突或错误会抛异常,中断编译。WMRouter中的Debugger用于调试和Log输出,运行时也会对一些配置进行检查,若是出现配置用法错误或其余严重问题会调用Debugger.fatal()抛出。 Debugger建议配置使用DefaultLogger:
    • 测试环境下开启Debug模式,fatal错误会抛出异常及时暴漏问题;
    • 线上环境关闭Debug模式,发生问题不抛异常;能够经过覆写DefaultLogger上报Error和Fatal级别的问题。
DefaultRootUriHandler rootHandler = new DefaultRootUriHandler(context);

//设置全局跳转完成的监听,能够在其中跳转失败时执行全局降级逻辑。
//在DefaultRootUriHandler中默认配置的GlobalOnCompleteListener会在跳转失败时弹Toast提示用户
rootHandler.setGlobalOnCompleteListener();

// 自定义Logger
DefaultLogger logger = new DefaultLogger() {
    @Override
    protected void handleError(Throwable t) {
        super.handleError(t);
        // 此处上报Fatal级别的异常
    }
};
// 设置Logger
Debugger.setLogger(logger);
// Log开关,建议测试环境下开启,方便排查问题。
Debugger.setEnableLog(true);
// 调试开关,建议测试环境下开启。调试模式下,严重问题直接抛异常,及时暴漏出来。
Debugger.setEnableDebug(true);


Router.init(rootHandler);
 // 后台线程懒加载
    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground(Void[] objects) {
            Router.lazyInit();
            return null;
        }
    }.execute();

复制代码
  • 跳转来源与Exported控制
    • com.sankuai.waimai.router.common.DefaultUriRequest#from:设置跳转来源参数,包括内部跳转、外部跳转、来自WebView的跳转、来自Push通知的跳转等,也能够自定义跳转来源,具体实现参考UriSourceTools。
    • 跳转来源能够用于实现Exported控制(SDK中注解里面的属性)、埋点统计、特殊业务逻辑等。其中Exported控制相似Android中Activity原生的Exported属性,默认为false,表示不容许来自外部的跳转,从而避免一些安全问题或功能异常。外部跳转由UriProxyActivity统一接收,而后调用WMRouter跳转并设置from为UriSourceTools.FROM_EXTERNAL,以后UriHandler经过跳转来源和页面的Exported配置便可判断是否容许跳转。
    • 经过UriSourceTools.setDisableExportedControl能够开启或关闭Exported控制。
/** 无效来源 */
    public static final int FROM_INVALID = 0;
    /** 外部跳转 */
    public static final int FROM_EXTERNAL = FROM_INVALID + 1;
    /** 内部跳转*/
    public static final int FROM_INTERNAL = FROM_EXTERNAL + 1;
    /** 从WebView跳转 */
    public static final int FROM_WEBVIEW = FROM_INTERNAL + 1;
    /** 从Push跳转 */
    public static final int FROM_PUSH = FROM_WEBVIEW + 1;
复制代码

2.3,URI分发功能的注解

2.3.1,RouterUri注解

最经常使用,基本只用这个注解就能够知足URI分发需求。根据URI的scheme+host,寻找并分发给对应的PathHandler,以后PathHandler再根据path匹配RouterUri注解配置的节点。可用于Activity或UriHandler的非抽象子类(Activity也会被转化成UriHandler,在Activity中能够经过Intent.getData()获取到URI)

参数以下:

  • path:跳转URI要用的path,必填。path应该以"/"开头,支持配置多个path。
  • scheme、host:跳转URI的scheme和host,可选。
  • exported:是否容许外部跳转,可选,默认为false。
  • interceptors:要添加的Interceptor,可选,支持配置多个。

说明:

  1. WMRouter支持多scheme+host+path的跳转,也支持只有path的跳转。若是RouterUri中配置了scheme、host、path,则跳转时应使用scheme+host+path的完整路径;若是RouterUri中只配置了path,则跳转应直接使用path。

  2. 因为多数场景下每每只须要一个固定的scheme+host,不想在每一个RouterUri注解上都写一遍scheme、host,这种场景能够在初始化时用new DefaultRootUriHandler("scheme", "host")指定默认的scheme、host,RouterUri没有配置的字段会使用这个默认值。

举例

一、用户帐户页面只配置path;跳转前要先登陆,所以添加了一个LoginInterceptor。

@RouterUri(path = "/account", interceptors = LoginInterceptor.class)
public class UserAccountActivity extends Activity {

}
复制代码
Router.startUri(context, "/account");
复制代码

二、一个页面配置多个path。

@RouterUri(scheme = "demo_scheme", host = "demo_host", path = {"/path1""/path2"})
public class TestActivity extends Activity {

}
复制代码
Router.startUri(context, "demo_scheme://demo_host/path1");
Router.startUri(context, "demo_scheme://demo_host/path2");
复制代码

三、根据后台下发的ABTest策略,同一个连接跳转不一样的Activity。其中AbsActivityHandler是WMRouter提供的用于跳转Activity的UriHandler通用基类。

@RouterUri(path = "/home")
public class HomeABTestHandler extends AbsActivityHandler {

    @NonNull
    @Override
    protected Intent createIntent(@NonNull UriRequest request) {
        if (FakeABTestService.getHomeABStrategy().equals("A")) {
            return new Intent(request.getContext(), HomeActivityA.class);
        } else {
            return new Intent(request.getContext(), HomeActivityB.class);
        }
    }
}
复制代码
Router.startUri(context, "/home");
复制代码

2.3.2,RouterRegex注解

RouterRegex注解也能够用于Activity和UriHandler,经过正则进行URI匹配。

参数以下:

  • regex:正则表达式,必填。用于匹配完整的URI字符串。
  • priority:优先级,数字越大越先匹配,可选,默认为0。优先级相同时,不保证前后顺序。
  • exported:是否容许外部跳转,可选,默认为false。
  • interceptors:要添加的Interceptor,可选,支持配置多个。
举例

一、对于指定域名的http(s)连接,使用特定的WebViewActivity打开。

@RouterRegex(regex = "http(s)?://(.*\\.)?(meituan|sankuai|dianping)\\.(com|info|cn).*", priority = 2)
public class WebViewActivity extends BaseActivity {

}
复制代码

二、对于其余http(s)连接,使用系统浏览器打开。

@RouterRegex(regex = "http(s)?://.*", priority = 1)
public class SystemBrowserHandler extends UriHandler {

    @Override
    protected boolean shouldHandle(@NonNull UriRequest request) {
        return true;
    }

    @Override
    protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {
        try {
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_VIEW);
            intent.setData(request.getUri());
            request.getContext().startActivity(intent);
            callback.onComplete(UriResult.CODE_SUCCESS);
        } catch (Exception e) {
            callback.onComplete(UriResult.CODE_ERROR);
        }
    }
}
复制代码

2.3.3,RouterPage注解

RouterPage注解用于指定内部页面跳转,和RouterUri注解相比,RouterPage注解对应的scheme和host为固定的wm_router://page,不可配置,exported为false也不可配置。感受这个是因为历史缘由存在的一个注解。本质和RouterUri注解是同样的。咱们本身的项目不会用到这个。因此不详细介绍了。有兴趣的小伙伴能够自行查看WMRouter设计与使用文档

2.4,核心组件的扩展

2.4.1,自定义UriHandler

  • 上面2.3.1中的HomeABTestHandler就属于自定义UriHandler,只不过继承了AbsActivityHandler而不是UriHandler。实际上,AbsActivityHandler也是UriHandler的子类(后面分析源码的时候会讲到)。更多状况下,是直接继承UriHandler来实现自定义的需求。
  • 这是另外一个例子:注意,直接继承UriHandler的实现类,要重写shouldHandle方法和handleInternal方法。
/** 跳转到系统自带浏览器 */
@RouterRegex(regex = DemoConstant.HTTP_URL_REGEX)
public class SystemBrowserHandler extends UriHandler {

    @Override
    protected boolean shouldHandle(@NonNull UriRequest request) {
        return true;
    }

    @Override
    protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {
        try {
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_VIEW);
            intent.setData(request.getUri());
            request.getContext().startActivity(intent);
            callback.onComplete(UriResult.CODE_SUCCESS);
        } catch (Exception e) {
            callback.onComplete(UriResult.CODE_ERROR);
        }
    }
}

复制代码
  • 还须要注意:若是自定义的UriHandler上面有@RouterUri、@RouterRegex或者@RouterPage注解,注解会自动将自定义UriHandler注册到路由表。若是没有这三个注解,那么须要在Router.init以前,将自定义的UriHandler添加到RootUriHandler的实例中(DefaultRootUriHandler是最经常使用的RootUriHandler实例)。
// 建立RootHandler
DefaultRootUriHandler rootHandler = new DefaultRootUriHandler(context);
rootHandler.addChildHandler(new UriHandler() {
            @Override
            protected boolean shouldHandle(@NonNull UriRequest request) {
                return false;
            }
            @Override
            protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {

            }
        });
// 初始化,必须在主线程调用
Router.init(rootHandler);
复制代码

2.4.2,自定义UriInterceptor

  • UriInterceptor为拦截器,不作最终的URI跳转操做,但能够在最终的跳转前进行各类同步/异步操做,常见操做举例以下:

    • URI跳转拦截,禁止特定的URI跳转,直接返回403(例如禁止跳转非meituan域名的HTTP连接)
    • URI参数修改(例如在HTTP连接末尾添加query参数)
    • 各类中间处理(例如打开登陆页登陆、获取定位、发网络请求)
    • ……
  • 每一个UriHandler均可以添加若干UriInterceptor。在UriHandler基类中,handle()方法先调用抽象方法shouldHandle()判断是否要处理UriRequest,若是须要处理,则逐个执行Interceptor,最后再调用handleInternal()方法进行跳转操做。 举例来讲,跳转某些页面须要先登陆,能够实现一个LoginInterceptor以下。

public class LoginInterceptor implements UriInterceptor {

    @Override
    public void intercept(@NonNull UriRequest request, @NonNull final UriCallback callback) {
        final FakeAccountService accountService = FakeAccountService.getInstance();
        if (accountService.isLogin()) {
            // 已经登陆,不需处理,继续跳转流程
            callback.onNext();
        } else {
            // 没登陆,提示登陆并启动登陆页
            Toast.makeText(request.getContext(), "请先登陆~", Toast.LENGTH_SHORT).show();
            accountService.registerObserver(new FakeAccountService.Observer() {
                @Override
                public void onLoginSuccess() {
                    accountService.unregisterObserver(this);
                    // 登陆成功,继续跳转
                    callback.onNext();
                }

                @Override
                public void onLoginFailure() {
                    accountService.unregisterObserver(this);
                    // 登陆失败,终止流程,返回错误ResultCode
                    callback.onComplete(CustomUriResult.CODE_LOGIN_FAILURE);
                }
            });
            // 启动登陆页
            startActivity(request.getContext(), LoginActivity.class);
        }
    }
}
复制代码

须要注意的是:

  • 每一个UriHandler均可以添加若干UriInterceptor(经过com.sankuai.waimai.router.core.UriHandler#addInterceptor方法添加),若是添加多个拦截器。则会安装添加的顺序封装成一个拦截器链,依次执行。熟悉责任链模式的小伙伴应该很容易就能看明白。感兴趣的小伙伴能够查看源码,com.sankuai.waimai.router.core.UriHandler#handle和com.sankuai.waimai.router.core.ChainedInterceptor。 - 若是当前拦截器的intercept方法中执行了com.sankuai.waimai.router.core.UriCallback#onNext方法,就会接着执行下一个拦截器。 - 若是当前拦截器的intercept方法中执行了com.sankuai.waimai.router.core.UriCallback#onComplete方法,就会结束拦截器链的调用。开始执行UriHandler的handleInternal方法。 - 若是当拦截器链全部的拦截器都遍历完毕了。开始执行UriHandler的handleInternal方法。
  • 路由分发可能会通过好几层UriHandler(好比从DefaultRootUriHandler分发到UriAnnotationHandler,而后再从UriAnnotationHandler分发到PathHandler,再从PathHandler分发到ActivityHandler),而每个UriHandler的实例都有本身的一串拦截器链。由于SDK是经过RootUriHandlerstartUri开始分发的,因此,若是要添加全局的拦截器,就能够经过给RootUriHandler的子类实例对象,好比DefaultRootUriHandler对象添加拦截器的方式实现。
  • 拦截器的添加方式有两种,一种是写在上述三个注解里面(最终经过UriTargetTools类的parse方法添加到目标UriHandler的拦截器链中),一种是直接经过UriHandler的addInterceptor方法添加。
  • 拦截器只能添加到UriHandler的实例对象中(能够是activity或者其余UriHandler实现类),并不能添加到某个方法上,这有什么影响呢?好比商品详情页activity有个加购物车按钮,点击聊天按钮,会判断是否登录,若是已经登录了,直接执行加购物车代码逻辑。若是没有登录,会先跳转到登录页面,而后登录成功以后再继续执行加购物车代码逻辑。这个加购物车方法,是没有办法经过添加拦截器的方式解决的。除非是某个activity,必须登录才能进入,这种状况下才能够经过给这个activity添加登录拦截器。感兴趣的小伙伴能够参考github的issue:关于登陆拦截器的问题

2.4.3,自定义RootUriHandler

根据实际状况,能够自定义具备各类功能的UriHandler和UriInterceptor,前面已经提到,再也不赘述。通常使用DefaultRootHandler和DefaultUriRequest,以及少许自定义的UriHandler已经能够知足绝大多数需求。若是有更复杂的场景须要,WMRouter中的核心组件能够经过继承、组合等方式实现更灵活的定制。例如自定义RootUriHandler示例以下:

// 自定义RootUriHandler
public class CustomRootUriHandler extends RootUriHandler {
    // ...
    public CustomRootUriHandler() {
        // 添加Uri注解支持
        addHandler(new UriAnnotationHandler());
        // 添加一个自定义的HttpHandler
        addHandler(new CustomHttpHandler());
    }
}

// 自定义UriRequest
public class CustomUriRequest extends UriRequest {
    // ...
    public CustomUriRequest setCustomProperties(String s) {
        putField("custom_properties", s);
        return this;
    }
}

// 初始化
Router.init(new CustomRootUriHandler());

// 启动Uri
CustomUriRequest request = new CustomUriRequest(mContext, url)
    .setCustomProperties("xxx");
Router.startUri(request);
复制代码
  • 我的感受,自定义RootUriHandler通常没有必要,若是真的有特殊需求,建议看懂源码的执行逻辑以后才开始动手。要知道,RootUriHandler的startUri方法是整个router的开始路由分发的入口。DefaultRootUriHandler构造方法中添加了SDK默认支持的各类子节点(UriAnnotationHandler,RegexAnnotationHandler,PageAnnotationHandler,StartUriHandler)。自定义的RootUriHandler最好也要加上这些子节点,不然会影响SDK的基本功能。
public DefaultRootUriHandler(Context context, @Nullable String defaultScheme, @Nullable String defaultHost) {
        super(context);
        mPageAnnotationHandler = createPageAnnotationHandler();
        mUriAnnotationHandler = createUriAnnotationHandler(defaultScheme, defaultHost);
        mRegexAnnotationHandler = createRegexAnnotationHandler();

        // 按优先级排序,数字越大越先执行

        // 处理RouterPage注解定义的内部页面跳转,若是注解没定义,直接结束分发
        addChildHandler(mPageAnnotationHandler, 300);
        // 处理RouterUri注解定义的URI跳转,若是注解没定义,继续分发到后面的Handler
        addChildHandler(mUriAnnotationHandler, 200);
        // 处理RouterRegex注解定义的正则匹配
        addChildHandler(mRegexAnnotationHandler, 100);
        // 添加其余用户自定义Handler...

        // 都没有处理,则尝试使用默认的StartUriHandler直接启动Uri
        addChildHandler(new StartUriHandler(), -100);
        // 全局OnCompleteListener,用于输出跳转失败提示信息
        setGlobalOnCompleteListener(DefaultOnCompleteListener.INSTANCE);
    }
复制代码

2.4.4,自定义ActivityLauncher

经过查看源码发现,全部Activity类型的UriHandler(就是经过在Activity类名上面添加注解,从而经过UriTargetTools的toHandler方法,生成的UriHandler实例),路由分发的最后一步(跳转该activity),都是经过ActivityLauncher接口的startActivity方法执行的。而SDK提供了ActivityLauncher接口的默认实现类DefaultActivityLauncher。咱们能够在这里hook一些核心的方法,执行本身的跳转逻辑。好比下面的例子,跳转到Activity以前,判断intent中的context是否是Activity类型的,若是不是,那么加上Intent.FLAG_ACTIVITY_NEW_TASK

public class XinActivityLauncher extends DefaultActivityLauncher {
    //...省略代码
    @Override
    protected int startActivityByDefault(UriRequest request, Context context, Intent intent, Integer requestCode, boolean internal) {
        try {
            Bundle options = (Bundle)request.getField(Bundle.class, FIELD_START_ACTIVITY_OPTIONS);
            if (requestCode != null && context instanceof Activity) {
                ActivityCompat.startActivityForResult((Activity)context, intent, requestCode, options);
            } else {
                if (!(context instanceof Activity)) {
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }
                ActivityCompat.startActivity(context, intent, options);
            }

            this.doAnimation(request);
            if (internal) {
                request.putField(FIELD_STARTED_ACTIVITY, 1);
                Debugger.i(" internal activity started, request = %s", new Object[]{request});
            } else {
                request.putField(FIELD_STARTED_ACTIVITY, 2);
                Debugger.i(" external activity started, request = %s", new Object[]{request});
            }

            return 200;
        } catch (ActivityNotFoundException var7) {
            Debugger.w(var7);
            return 404;
        } catch (SecurityException var8) {
            Debugger.w(var8);
            return 403;
        }
    }
    //...省略代码
}
复制代码

2.5,ServiceLoader的使用

2.5.1,什么是ServiceLoader?有什么做用?

简单来讲,ServiceLoader的核心做用就是:根据接口(或抽象类)名,找到接口(或抽象类)的具体实例,若是一个接口对应多个实例,那么再根据不一样实例的key找到具体的接口实例。

在实现组件化的项目中,极可能多个业务module之间是没有依赖关系的可是确实有可能不一样的业务module之间仍是有业务逻辑的耦合的。好比:

  • 跨module跳转页面,经过WMRouter的URI分发功能解决;
  • 业务层,业务moduleA须要用到业务moduleB的某些代码逻辑。这个时候就轮到ServiceLoader发挥做用了。能够把这部分逻辑抽象成一个接口(抽象类)。将接口类下沉到基础module中,而后接口的实现放到业务moduleB,接口的实现类上面添加@RouterService注解,这样业务moduleA就可以经过WMRouter获取业务moduleB的接口实现了。

2.5.2,怎么使用?

ServiceLoader模块使用主要分三步:

  1. 定义Java接口或抽象类。若是须要跨module调用接口实现,要把接口下沉,确保不一样module都能获取接口抽象类。
  2. 实现接口或抽象类,而后添加@RotuerService注解(接口抽象类,key,是否单例)。
  3. 经过Router的一系列getService方法获取接口的实现类的Class或者实例对象。

RouterService注解

经过RouterService注解声明实现类所实现的接口(或继承的父类,例如Activity、Fragment、Object等,后文再也不重复说明),一个接口能够有多个实现类,一个类也能够同时实现多个接口。RouterService注解的参数以下:

  • interfaces:必选参数。声明实现的接口,可配置多个。
  • key:可选参数。同一接口的不一样实现类,经过惟一的key进行区分。
  • singleton:可选参数。声明实现类是否为单例,默认为false。

示例以下:

public interface IService {

}

@RouterService(interfaces = IService.class, key = 'key1')
public static class ServiceImpl1 implements IService {

}

@RouterService(interfaces = IService.class, key = 'key2', singleton = true)
public static class ServiceImpl2 implements IService {

}
复制代码

获取实现类的方式

能够直接获取实现类的Class,例如获取Activity的Class进行页面跳转。

  • 指定接口和Key,获取某个实现类的Class(要求注解声明时指定了Key)
Class<IService> clazz = Router.getServiceClass(IService.class, "key1");
复制代码
  • 指定接口,获取注解声明的全部实现类的Class
List<Class<IService>> classes = Router.getAllServiceClasses(IService.class);
复制代码

获取实现类的实例

ServiceLoader更常见的使用场景,是获取实现类的实例而不是Class。实现类的构造在ServiceLoader中最终由Factory实现,构造失败会返回null或空数组。

  • 无参数构造
// 使用无参构造函数
IService service = Router.getService(IService.class, "key1");
List<IService> list = Router.getAllServices(IService.class);
复制代码
  • Context参数构造
// 使用Context参数构造
IService service = Router.getService(IService.class, context);
List<IService> list = Router.getAllServices(IService.class, context);
复制代码
  • 自定义Factory经过反射构造

对于实现类有特殊构造函数的状况,能够经过Factory自行从class获取构造方法进行构造,示例以下:

// 使用自定义Factory
IFactory factory = new IFactory() {
    public Object create(Class clazz) {
        return clazz.getConstructor().newInstance();
    }
};
IService service = Router.getService(IService.class, factory);
List<IService> list = Router.getAllServices(IService.class, factory);
复制代码
  • 使用Provider提供实例

在声明实现类时,能够在类中定义一个返回值类型为该实现类且无参数的静态方法,并使用RouterProvider注解标注。当调用Router获取实例时,若是没有指定Factory,则优先调用Provider方法获取实例,找不到Provider再使用无参数构造。使用示例以下:

@RouterService(interfaces = IService.class, key = 'key', singleton = true)
public static class ServiceImpl implements IService {

    public static final ServiceImpl INSTANCE = new ServiceImpl();

    // 使用注解声明该方法是一个Provider
    @RouterProvider
    public static ServiceImpl provideInstance() {
        return INSTANCE;
    }
}

// 调用时不传Factory,优先找Provider,找不到再使用无参数构造
IService service = Router.getService(IService.class, "key");
List<IService> list = Router.getAllServices(IService.class);
复制代码

singleton参数说明

注解声明为singleton的单例实现类,在调用getService()/getAllServices()方式获取实例时,实例会由单例缓存池管理,WMRouter中不会重复构造,且线程安全。

注意:当经过ServiceLoader获取Class、直接调用等其余方式使用实现类时,应避免重复建立对象,不然会致使单例失效。能够结合Provider确保实例不会重复建立。

三,为了解决这些问题,WMRouter内部是如何实现的。分析源码

WMRouter的核心原理大概就是,经过注解标注路由信息,在编译期动态扫描路由信息,生成加载路由表信息的java类。并利用 gradle transform和asm生成加载所有路由信息的class文件。在app运行时,路由框架反射调用这个class文件,从而完成了路由表的装载。

3.1,总体流程

3.1.1,路由关系生成

编译时注解生成ServiceInit_*类,UriAnnotationInit_*类等辅助注册代码。

  • 首先,编译的时候,根据@RouterUri,@RouterRegex,@RouterPage,@RouterService注解,生成辅助代码。详细的文件见下图。具体的生成原理参见路由节点的动态生成

  • 这里须要注意,@RouterUri,@RouterRegex,@RouterPage这三个注解,会同时生成两个文件。以@RouterUri为例进行说明:

    • 文件1,UriAnnotationInit_**类,其init方法中的每一行,都是本module中使用@RouterUri注解的类的注册到UriAnnotationHandler的执行代码。所谓注册,其实质就是创建映射关系。须要注意UriAnnotationHandler这个注册过程,一旦执行,就会开启整个UriAnnotationHandler这个分支的全部注册过程。
    • 文件2,ServiceInit_**类,其init方法中,经过ServiceLoader.put()方法,创建了接口抽象类(IUriAnnotationInit.class),接口实现类(com.sankuai.waimai.router.generated.UriAnnotationInit_72565413b8384a4bebb02d352762d60d.class),接口实现类的key(com.sankuai.waimai.router.generated.UriAnnotationInit_72565413b8384a4bebb02d352762d60d),这三者之间的映射关系。
  • 还须要注意。编译过程只是生成了可以进行注册的代码。可是代码并无执行。只有等到开启提早加载(执行Router.lazyInit()),或者开启跳转(Router#startUri(com.sankuai.waimai.router.core.UriRequest))的时候,才开始注册。

public class UriAnnotationInit_72565413b8384a4bebb02d352762d60d implements IUriAnnotationInit {
  public void init(UriAnnotationHandler handler) {
    handler.register("", "", "/advanced_demo", "com.sankuai.waimai.router.demo.advanced.AdvancedDemoActivity", false);
  }
}

复制代码
public class ServiceInit_eb71854fbd69455ef4e0aa026c2e9881 {
  public static void init() {
    ServiceLoader.put(IUriAnnotationInit.class, "com.sankuai.waimai.router.generated.UriAnnotationInit_72565413b8384a4bebb02d352762d60d", com.sankuai.waimai.router.generated.UriAnnotationInit_72565413b8384a4bebb02d352762d60d.class, false);
  }
}

复制代码

gradle插件,生成ServiceLoaderInit

  • ServiceLoaderInit类,只有一个init方法。其中包含了整个项目全部module的ServiceInit_**辅助类的init执行代码。ServiceInit_**辅助类是上面经过注解生成的。
  • 不管使用哪一种方式开启路由分发过程,真正开始分发以前,都会首先执行ServiceLoaderInit类的init方法。为何呢?上面咱们说到,注解只是生成辅助代码,可是并无执行。只有执行了ServiceLoaderInit类的init方法,才会真正的执行路由表注册代码,创建真正的映射关系。另外须要注意,改方法执行后,只会创建第一层关系。只有等到开启提早加载(执行Router.lazyInit()),或者开启跳转(Router#startUri(com.sankuai.waimai.router.core.UriRequest))的时候,才开始执行UriAnnotationHandler等子节点分支的注册代码(initAnnotationConfig()),创建映射关系。

详细过程,参见 路由节点的加载

public class ServiceLoaderInit {
  public static void init() {
    ServiceInit_aea7f96d0419b507d9b0ef471913b2f5.init();
    ServiceInit_f3649d9f5ff15a62b844e64ca8434259.init();
    ServiceInit_eb71854fbd69455ef4e0aa026c2e9881.init();
    ServiceInit_b57118238b4f9112ddd862e55789c834.init();
    ServiceInit_f1e07218f6691f962a9f674eb5b4b8bd.init();
    ServiceInit_e694d982fb5d7a3a8c6b7085829e74a6.init();
    ServiceInit_ee5f6404731417fe1433da40fd3c9708.init();
    ServiceInit_9482ef47a8cf887ff1dc4bf705d5fc0a.init();
    ServiceInit_36ed390bf4b81a8381d45028b37cc645.init();
  }
}

复制代码

3.1.2,分发过程

  • 经过Router#startUri(com.sankuai.waimai.router.core.UriRequest)开启跳转
  • 其中调用了getRootHandler().startUri(request);其中getRootHandler()方法获取RootUriHandler的实例,默认是DefaultRootUriHandler对象。
  • DefaultRootUriHandler是RootUriHandler的子类。其startUri方法调用的是父类的。RootUriHandler#startUri方法。
  • 而后其中各类判断以后,最终调用UriHandler#handle方法开始分发。
/** * 处理URI。一般不须要覆写本方法。 * * @param request URI跳转请求 * @param callback 处理完成后的回调 */
    public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
        if (shouldHandle(request)) {
            Debugger.i("%s: handle request %s", this, request);
            if (mInterceptor != null && !request.isSkipInterceptors()) {
                mInterceptor.intercept(request, new UriCallback() {
                    @Override
                    public void onNext() {
                        handleInternal(request, callback);
                    }

                    @Override
                    public void onComplete(int result) {
                        callback.onComplete(result);
                    }
                });
            } else {
                handleInternal(request, callback);
            }
        } else {
            Debugger.i("%s: ignore request %s", this, request);
            callback.onNext();
        }
    }
复制代码
  • UriHandler#handle方法中,首先调用UriHandler#shouldHandle方法,判断是否应该处理。这里要注意,UriHandler#handle在各个UriHanlder的子类中都会调用(责任链模式)。
  • 分析handle方法的时候,必定要注意当前的UriHandler的具体实例是什么
    • 第一次执行UriHandler#handle方法,执行第一句代码shouldHandle。
    • 当前实例是DefaultRootUriHandler对象,其父类是RootUriHandler,其父类的父类是ChainedHandler。由于DefaultRootUriHandler和RootUriHandler都没有实现shouldHandle方法而ChainedHandler实现了。
    • 因此,第一次执行UriHandler#handle方法的第一句shouldHandle,执行的是ChainedHandler的shouldHandle方法。该方法会判断mHandlers是否为空,在DefaultRootUriHandler的构造函数里面,已经添加了UriAnnotationHandler,PageAnnotationHandler,RegexAnnotationHandler,StartUriHandler等UriHandler对象。因此,shouldHandle返回true,继续向下执行。
    • 而后判断拦截器,这块不影响主体流程,咱们先略过,直接看handleInternal(request, callback)这行代码。而后和shouldHandle方法同样,DefaultRootUriHandler实例的handleInternal方法,也是用的ChainedHandler的实现。其handleInternal方法中,调用了next方法。开始了对DefaultRootUriHandler的mHandlers集合的遍历。咱们查看next方法能够发现,在遍历过程当中,若是某个UriHandler执行过程当中,调用了onNext回调(表明本身不负责这个request),会继续执行next方法,交给下一个UriHandler执行。若是若是某个UriHandler执行过程当中,调用了onComplete回调,表明这个request是其负责的,就结束遍历。
    • 举个例子。上面说过了,在DefaultRootUriHandler的构造函数里面给mHandlers添加了各类UriHandler对象。好比ChainedHandler的next方法轮询过程当中,正在获取的UriHandler实例是UriAnnotationHandler,而后调用UriAnnotationHandler的handle方法。
    • UriAnnotationHandler的handle中,首先会调用LazyInitHelper的ensureInit方法。实际上调用的是UriAnnotationHandler的initAnnotationConfig()方法。在该方法中,调用com.sankuai.waimai.router.components.DefaultAnnotationLoader#load方法。最终调用的是ServiceLoader.load(clazz).getAll(),来获取全部UriAnnotationInit接口的实现(经过注解生成的),而后调用这些实现的init方法。完成了UriAnnotationHandler分支的路由关系的注册。详情可参看下面ServiceLoader的讲解。
    • 而后调用super.handle(),又回到了UriHandler的handle()中。最终调用了UriAnnotationHandler的shouldHandle和handleInternal方法。在handleInternal方法中,经过request找到PathHandler,而后交给PathHandler执行其handle方法。而后PathHandler的handleInternal方法中,又根据request找到其内部注册的UriHandler,若是能找到,就交给这个子UriHandler去执行,若是找不到,就本身执行。这样就完成了分发过程。是否是感受跟事件分发很像,只不过是这里分发的是一个包装了地址的request而已。
public DefaultRootUriHandler(Context context, @Nullable String defaultScheme, @Nullable String defaultHost) {
        super(context);
        mPageAnnotationHandler = createPageAnnotationHandler();
        mUriAnnotationHandler = createUriAnnotationHandler(defaultScheme, defaultHost);
        mRegexAnnotationHandler = createRegexAnnotationHandler();

        // 按优先级排序,数字越大越先执行

        // 处理RouterPage注解定义的内部页面跳转,若是注解没定义,直接结束分发
        addChildHandler(mPageAnnotationHandler, 300);
        // 处理RouterUri注解定义的URI跳转,若是注解没定义,继续分发到后面的Handler
        addChildHandler(mUriAnnotationHandler, 200);
        // 处理RouterRegex注解定义的正则匹配
        addChildHandler(mRegexAnnotationHandler, 100);
        // 添加其余用户自定义Handler...

        // 都没有处理,则尝试使用默认的StartUriHandler直接启动Uri
        addChildHandler(new StartUriHandler(), -100);
        // 全局OnCompleteListener,用于输出跳转失败提示信息
        setGlobalOnCompleteListener(DefaultOnCompleteListener.INSTANCE);
    }
     @Override
    public void handle(@NonNull UriRequest request, @NonNull UriCallback callback) {
        mInitHelper.ensureInit();
        super.handle(request, callback);
    }

复制代码
ChainedHandler{
     @Override
    protected boolean shouldHandle(@NonNull UriRequest request) {
        return !mHandlers.isEmpty();
    }
     @Override
    protected void handleInternal(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
        next(mHandlers.iterator(), request, callback);
    }

    private void next(@NonNull final Iterator<UriHandler> iterator, @NonNull final UriRequest request, @NonNull final UriCallback callback) {
        if (iterator.hasNext()) {
            UriHandler t = iterator.next();
            t.handle(request, new UriCallback() {
                @Override
                public void onNext() {
                    next(iterator, request, callback);
                }

                @Override
                public void onComplete(int resultCode) {
                    callback.onComplete(resultCode);
                }
            });
        } else {
            callback.onNext();
        }
    }
}
复制代码
public class UriAnnotationHandler extends UriHandler {
   /** * 经过scheme+host找对应的PathHandler,找到了才会处理 */
    private PathHandler getChild(@NonNull UriRequest request) {
        return mMap.get(request.schemeHost());
    }
    
       @Override
    protected boolean shouldHandle(@NonNull UriRequest request) {
        return getChild(request) != null;
    }

    @Override
    protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {
        PathHandler pathHandler = getChild(request);
        if (pathHandler != null) {
            pathHandler.handle(request, callback);
        } else {
            // 没找到的继续分发
            callback.onNext();
        }
    }
    
}
复制代码
public class DefaultAnnotationLoader implements AnnotationLoader {

    public static final AnnotationLoader INSTANCE = new DefaultAnnotationLoader();

    @Override
    public <T extends UriHandler> void load(T handler, Class<? extends AnnotationInit<T>> initClass) {
        List<? extends AnnotationInit<T>> services = Router.getAllServices(initClass);
        for (AnnotationInit<T> service : services) {
            service.init(handler);
        }
    }
}
复制代码
public class UriAnnotationInit_179aab35b2125f96c7b066a0a2eccf82 implements IUriAnnotationInit {
  public void init(UriAnnotationHandler handler) {
    handler.register("", "", "/service_loader", "com.sankuai.waimai.router.demo.lib2.advanced.ServiceLoaderActivity", false);
    handler.register("", "", "/lib2", "com.sankuai.waimai.router.demo.lib2.basic.DemoLibActivity2", false);
  }
}

复制代码

3.2,核心类

3.2.1,UriHandler及其子类

UriHandler就是咱们所说的节点。UriHandler用于处理URI跳转请求,能够嵌套从而逐层分发和处理请求。UriHandler是异步结构,接收到UriRequest后处理(例如跳转Activity等),若是处理完成,则调用callback.onComplete()并传入ResultCode;若是没有处理,则调用callback.onNext()继续分发。

UriHandler及其实现类的类图整理以下(简单起见,省略了部分实现):

  • UriHandler是抽象类,其核心在于handle()方法,另外,shouldHandle()handleInternal()方法是抽象方法,由子类去实现。shouldHandle()方法判断是否应该在当前UriHandler执行分发。handleInternal()是当前UriHandler执行分发的具体逻辑。
  • RootUriHandlerstartUri()方法,是全部分发跳转的入口。DefaultRootUriHandlerRootUriHandler的默认实现,其构造函数中添加了PageAnnotationHandler,UriAnnotationHandler,RegexAnnotationHandler,StartUriHandler的实例。而后分发跳转请求的时候,依次交给这四个UriHandler处理,若是某个UriHandler不可以处理该请求,则交给下一个执行。若是可以处理该请求,则交给该UriHandler的子节点继续分发。好比,若是UriAnnotationHandler可以处理该请求,会交给其子节点PathHandler处理,而后PathHandler交给ActivityHandler处理。
  • 只有PageAnnotationHandler,UriAnnotationHandler,RegexAnnotationHandler这三个子类复写了UriHandlerhandle()方法。其复写的目的是为了在真正开始分发以前,调用mInitHelper.ensureInit(),确保该分支的路由表已经生成。
  • PageAnnotationHandler写死了SCHEME和HOST,因此只处理全部格式为 wm_router://page/* 的URI,根据path匹配。
  • UriAnnotationHandler分发到PathHandler,PathHandler分发到ActivityClassNameHandlerActivityHandler是最经常使用的分发路径。
  • 若是PageAnnotationHandler,UriAnnotationHandler,RegexAnnotationHandler及其子节点,都没有处理某个路由请求(处理的意思是:执行了UriCallback的onComplete回调),则交给StartUriHandler处理。StartUriHandler会从路由请求UriRequest中获取uri等参数,直接经过intent跳转。

3.2.2,UriRequest及其子类

UriRequest中包含Context、URI和Fields,其中Fields为HashMap<String, Object>,能够经过Key存听任意数据。简单起见,UriRequest类同时承担了Response的功能,跳转请求的结果,也会被保存到Fields中。

  • Intent的Extra参数,Bundle类型
  • 用于startActivityForResult的RequestCode,int类型
  • 用于overridePendingTransition方法的页面切换动画资源,int[]类型
  • 本次跳转结果的监听器,OnCompleteListener类型

每次URI跳转请求会有一个ResultCode(相似HTTP请求的ResponseCode),表示跳转结果,也存放在Fields中。常见Code以下,用户也能够自定义Code,为了不冲突,自定义Code应使用负数值

  • 200:跳转成功
  • 301:重定向到其余URI,会再次跳转
  • 400:请求错误,一般是Context或URI为空
  • 403:禁止跳转,例如跳转白名单之外的HTTP连接、Activity的exported为false等
  • 404:找不到目标(Activity或UriHandler)
  • 500:发生错误

总结来讲,UriRequest用于实现一次URI跳转中全部组件之间的通讯功能。SDK默认提供了DefaultUriRequest,通常用其就能够完成平常需求。

3.2.3,AnnotationInit及其子类

AnnotationInit的做用是,提供统一的,调用生成的辅助类的init方法。方便注册路由表。DefaultAnnotationLoader的load方法,经过ServiceLoader获取这些AnnotationInit的全部实现,而后调用其init方法。

public class UriAnnotationInit_179aab35b2125f96c7b066a0a2eccf82 implements IUriAnnotationInit {
  public void init(UriAnnotationHandler handler) {
    handler.register("", "", "/service_loader", "com.sankuai.waimai.router.demo.lib2.advanced.ServiceLoaderActivity", false);
    handler.register("", "", "/lib2", "com.sankuai.waimai.router.demo.lib2.basic.DemoLibActivity2", false);
  }
}
复制代码
/** * 使用ServiceLoader加载注解配置 * * Created by jzj on 2018/4/28. */
public class DefaultAnnotationLoader implements AnnotationLoader {
    public static final AnnotationLoader INSTANCE = new DefaultAnnotationLoader();
    @Override
    public <T extends UriHandler> void load(T handler, Class<? extends AnnotationInit<T>> initClass) {
        List<? extends AnnotationInit<T>> services = Router.getAllServices(initClass);
        for (AnnotationInit<T> service : services) {
            service.init(handler);
        }
    }
}
复制代码

3.2.4,ServiceLoader

上面说过,ServiceLoader的原理是首先经过接口名获取ServiceLoader实例,而后再从ServiceLoader实例中,根据key找到对应的接口实现类。那么具体过程呢?

保存映射关系
  1. 调用put方法(见下图),参数有:接口的Class对象,接口实现类中的key,接口实现类的Class对象,以及接口实现类是否要求单例。在put方法中,调用SERVICES.put(interfaceClass, loader),存入接口Class对象和ServiceLoader实例的关系。而后调用loader.putImpl(key, implementClass, singleton)
  2. 在ServiceLoader实例的putImpl方法中,调用mMap.put(key, new ServiceImpl(key, implementClass, singleton)),在ServiceLoader实例中存入key和接口实例class对象的关系。注意这里建立了一个ServiceImpl对象。那么ServiceImpl是干什么的呢?
获取映射关系
  1. 经过getService等方法获取接口的实例。该方法就一行代码ServiceLoader.load(clazz).get(key)
  2. load方法中,首先调用sInitHelper.ensureInit()。经过反射,调用com.sankuai.waimai.router.generated.ServiceLoaderInit类的init方法(ServiceLoaderInit类是经过gradle插件生成的)。在init方法中调用各个ServiceInit_36ed390bf4b81a8381d45028b37cc645init方法(见下图)。那么ServiceLoaderInit类中的init方法中的这些ServiceInit_36ed390bf4b81a8381d45028b37cc645类是什么呢?这些类中的init方法里面又是什么呢?
  3. 而后继续执行load方法,经过SERVICES.get(interfaceClass)获取接口对应的ServiceLoader实例。

public class ServiceLoader<I> {
    //...省略部分代码
    //保存了接口类名和其对应的ServiceLoader实例的对应关系。
    private static final Map<Class, ServiceLoader> SERVICES = new HashMap<>();
    //保存了key和接口的实现类的对应关系。
    private HashMap<String, ServiceImpl> mMap = new HashMap<>();
    
    private static final LazyInitHelper sInitHelper = new LazyInitHelper("ServiceLoader") {
        @Override
        protected void doInit() {
            try {
                // 反射调用Init类,避免引用的类过多,致使main dex capacity exceeded问题
                Class.forName(Const.SERVICE_LOADER_INIT)
                        .getMethod(Const.INIT_METHOD)
                        .invoke(null);
                Debugger.i("[ServiceLoader] init class invoked");
            } catch (Exception e) {
                Debugger.fatal(e);
            }
        }
    };
    /** * 提供给InitClass使用的初始化接口 * * @param interfaceClass 接口类 * @param implementClass 实现类 */
    public static void put(Class interfaceClass, String key, Class implementClass, boolean singleton) {
        ServiceLoader loader = SERVICES.get(interfaceClass);
        if (loader == null) {
            loader = new ServiceLoader(interfaceClass);
            SERVICES.put(interfaceClass, loader);
        }
        loader.putImpl(key, implementClass, singleton);
    }
    private void putImpl(String key, Class implementClass, boolean singleton) {
        if (key != null && implementClass != null) {
            mMap.put(key, new ServiceImpl(key, implementClass, singleton));
        }
    }
    /** * 根据接口获取 {@link ServiceLoader} */
    @SuppressWarnings("unchecked")
    public static <T> ServiceLoader<T> load(Class<T> interfaceClass) {
        sInitHelper.ensureInit();
        if (interfaceClass == null) {
            Debugger.fatal(new NullPointerException("ServiceLoader.load的class参数不该为空"));
            return EmptyServiceLoader.INSTANCE;
        }
        ServiceLoader service = SERVICES.get(interfaceClass);
        if (service == null) {
            synchronized (SERVICES) {
                service = SERVICES.get(interfaceClass);
                if (service == null) {
                    service = new ServiceLoader(interfaceClass);
                    SERVICES.put(interfaceClass, service);
                }
            }
        }
        return service;
    }
    /** * 建立指定key的实现类实例,使用 {@link RouterProvider} 方法或无参数构造。对于声明了singleton的实现类,不会重复建立实例。 * * @return 找不到或获取、构造失败,则返回null */
    public static <I, T extends I> T getService(Class<I> clazz, String key) {
        return ServiceLoader.load(clazz).get(key);
    }
    /** * 建立指定key的实现类实例,使用 {@link RouterProvider} 方法或无参数构造。对于声明了singleton的实现类,不会重复建立实例。 * * @return 可能返回null */
    public <T extends I> T get(String key) {
        return createInstance(mMap.get(key), null);
    }
}
复制代码

3.3,用到的设计模式

3.3.1,外观模式

外观模式提供一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层接口,让子系统更容易使用。

com.sankuai.waimai.router.Router这个类就使用了外观模式。其 lazyInit(), startUri(UriRequest request), getService(Class<I> clazz, String key)等方法,都是经过调用SDK内部的其余子系统提供的功能实现的。

public class Router {
    /** * 此初始化方法的调用不是必须的。 * 使用时会按需初始化;但也能够提早调用并初始化,使用时会等待初始化完成。 * 本方法线程安全。 */
    public static void lazyInit() {
        ServiceLoader.lazyInit();
        getRootHandler().lazyInit();
    }
    public static void startUri(UriRequest request) {
        getRootHandler().startUri(request);
    }
     /** * 建立指定key的实现类实例,使用 {@link RouterProvider} 方法或无参数构造。对于声明了singleton的实现类,不会重复建立实例。 * @return 找不到或获取、构造失败,则返回null */
    public static <I, T extends I> T getService(Class<I> clazz, String key) {
        return ServiceLoader.load(clazz).get(key);
    }
    ...
}
复制代码

3.3.2,单例模式

单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点

单例模式常见的有 七种写法,WMRouter用到的有如下几种:

饿汉式

public class DefaultActivityLauncher implements ActivityLauncher {
    public static final DefaultActivityLauncher INSTANCE = new DefaultActivityLauncher();
}
复制代码

双重校验锁

LazyInitHelper的performInit()方法,用到了双重校验锁的思想。保证只初始化一遍。

public abstract class LazyInitHelper {
    private void performInit() {
        if (!mHasInit) {
            synchronized (this) {
                if (!mHasInit) {
                    mHasInit = true;
                    long ts = 0;
                    final boolean enableLog = Debugger.isEnableLog();
                    if (enableLog) {
                        ts = SystemClock.uptimeMillis();
                    }
                    try {
                        doInit();
                    } catch (Throwable t) {
                        Debugger.fatal(t);
                    }
                    if (enableLog) {
                        Debugger.i("%s init cost %s ms", mTag,
                                SystemClock.uptimeMillis() - ts);
                    }
                }
            }
        }
    }
}
复制代码

使用容器实现单例模式

/** * 单例缓存 * * Created by jzj on 2018/3/29. */
public class SingletonPool {

    private static final Map<Class, Object> CACHE = new HashMap<>();

    @SuppressWarnings("unchecked")
    public static <I, T extends I> T get(Class<I> clazz, IFactory factory) throws Exception {
        if (clazz == null) {
            return null;
        }
        if (factory == null) {
            factory = RouterComponents.getDefaultFactory();
        }
        Object instance = getInstance(clazz, factory);
        Debugger.i("[SingletonPool] get instance of class = %s, result = %s", clazz, instance);
        return (T) instance;
    }

    @NonNull
    private static Object getInstance(@NonNull Class clazz, @NonNull IFactory factory) throws Exception {
        Object t = CACHE.get(clazz);
        if (t != null) {
            return t;
        } else {
            synchronized (CACHE) {
                t = CACHE.get(clazz);
                if (t == null) {
                    Debugger.i("[SingletonPool] >>> create instance: %s", clazz);
                    t = factory.create(clazz);
                    //noinspection ConstantConditions
                    if (t != null) {
                        CACHE.put(clazz, t);
                    }
                }
            }
            return t;
        }
    }
}
复制代码

3.3.3,工厂方法模式

工厂方法模式(Factory Method Pattern),在实际开发过程当中咱们都习惯于直接使用 new 关键字用来建立一个对象,但是有时候对象的创造须要一系列的步骤:你可能须要计算或取得对象的初始设置;选择生成哪一个子对象实例;或在生成你须要的对象以前必须先生成一些辅助功能的对象,这个时候就须要了解该对象建立的细节,也就是说使用的地方与该对象的实现耦合在了一块儿,不利于扩展,为了解决这个问题就须要用到咱们的工厂方法模式,它适合那些建立复杂的对象的场景,工厂方法模式也是一个使用频率很高的设计模式

WMRouter中,IFactory是抽象工厂。工厂方法是create()方法,生产的产品是泛型T。ContextFactory是具体工厂,其create方法,经过获取包含context的构造函数,建立T的实例。EmptyArgsFactory是另一个具体工厂,其create方法,经过clazz.newInstance()无参构造函数建立实例。

/** * 从Class构造实例 */
public interface IFactory {
    @NonNull
    <T> T create(@NonNull Class<T> clazz) throws Exception;
}

复制代码
public class ContextFactory implements IFactory {
    private final Context mContext;
    public ContextFactory(Context context) {
        mContext = context;
    }
    @Override
    public <T> T create(@NonNull Class<T> clazz) throws Exception {
        return clazz.getConstructor(Context.class).newInstance(mContext);
    }
}
复制代码
public class EmptyArgsFactory implements IFactory {
    public static final EmptyArgsFactory INSTANCE = new EmptyArgsFactory();
    private EmptyArgsFactory() {
    }
    @Override
    public <T> T create(@NonNull Class<T> clazz) throws Exception {
        return clazz.newInstance();
    }
}
复制代码

经过工厂方法获取实例

public class ServiceLoader<I> {
    private <T extends I> T createInstance(@Nullable ServiceImpl impl, @Nullable IFactory factory) {
        //...省略部分代码
        Class<T> clazz = (Class<T>) impl.getImplementationClazz();
        //经过工厂方法获取实例
        T t = factory.create(clazz);
        Debugger.i("[ServiceLoader] create instance: %s, result = %s", clazz, t);
        return t;
    }
}
复制代码

3.3.4,责任链模式

责任链模式是一种对象的行为模式。在责任链模式里,不少对象由每个对象对其下家的引用而链接起来造成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪个对象最终处理这个请求,这使得系统能够在不影响客户端的状况下动态地从新组织和分配责任。

UML图中的succeesor表示的是责任链中的下一个Handler

WMRouter路由分发时序图

  • 上图中的ChainedHandler其实是DefaultRootUriHandler,由于DefaultRootUriHandlerChainedHandler的子类,并且没有实现handleInternal方法。因此调用的是ChainedHandler的handleInternal方法,最终调用的是next(Iterator<UriHandler> iterator,UriRequest request,UriCallback callback)方法。而该方法中,在某个节点的onNext回调里面又递归调用了其本身,这样就创建了链式关系。
  • WMRouter中,UriHandler的若干子类构成了一个责任链,UriInterceptor的若干子类构成了另外一个责任链。
public class ChainedHandler extends UriHandler {
    @Override
    protected void handleInternal(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
        next(mHandlers.iterator(), request, callback);
    }

    private void next(@NonNull final Iterator<UriHandler> iterator, @NonNull final UriRequest request, @NonNull final UriCallback callback) {
        if (iterator.hasNext()) {
            UriHandler t = iterator.next();
            t.handle(request, new UriCallback() {
                @Override
                public void onNext() {
                    next(iterator, request, callback);
                }

                @Override
                public void onComplete(int resultCode) {
                    callback.onComplete(resultCode);
                }
            });
        } else {
            callback.onNext();
        }
    }
}
复制代码

四,为何要这么设计?

写到这里,有点心虚,毕竟不是本身写的框架,也不知道当时别人开发的时候的思路。只能说大致上猜想一下。

  • 首先要结合本身的业务背景和需求,看看有哪些问题须要解决?
  • 查看如今有哪些比较成熟的轮子,能不能解决本身的问题,有没有什么坑?
    • github上的开源框架
    • 不少大厂,有本身的基础技术部门,有不少内部的工具框架。
  • 针对本身的特殊业务需求,需求的紧急和重要程度,以及人力和排期,怎么解决这些问题?
    • 资源紧张,需求等级不高,就直接用现有的轮子
    • 若是资源充足,并且现有轮子不可以很好知足需求,能够本身研究一下实现原理,根据本身的需求造一个新轮子

关于这个框架诞生的需求背景,感受美团外卖Android平台化架构演进实践WMRouter:美团外卖Android开源路由框架说的很详细,你们能够学习一下,在遇到问题的时候怎么选择解决方案,怎样设计架构。

关于通用的路由需求,感受这篇文章说的挺好,你们能够学习一下Android 组件化 —— 路由设计最佳实践

五,拓展学习

  • WMRouter,美团外卖团队出品,目前已有1.3K-star
  • ARouter,阿里,目前已有9.5K-star
  • Andromeda,爱奇艺,目前已有1.8K-star
  • ActivityRouter,我的项目,目前已有2.7K-star
  • Router,我的项目,目前已有1.3K-star

参考文章:

WMRouter:美团外卖Android开源路由框架

github.com/meituan/WMR…

WMRouter设计与使用文档

WMRouter源码分析(1)-基本结构分析

Android 组件化 —— 路由设计最佳实践

一文了解Android中路由(Router)的实现

美团外卖开源路由框架 WMRouter 源码分析

生成带混淆配置的aar库

写给 Android 开发者的混淆使用手册

Android混淆——了解这些就够了

细说反射,Java 和 Android 开发者必须跨越的坎

java/android 设计模式学习笔记目录

相关文章
相关标签/搜索