内容简介:项目组件化过程中使用了路由框架,也就是路由的作用。路由是起什么作用呢?就像送快递,从小县城到省会,再从省会发到北京分拨中心,然后再从北京分拨中心发到回龙观,再从回龙观发到具体的小区。路由框架解决的就是如何从A页面跳转到B页面的问题,其会在A和B之间建立数个节点。通过这些节点依次进行转发,最终达到目的地。Android原生已经支持AndroidManifest去管理App跳转,为什么要有路由库?
项目组件化过程中使用了 WMRouter ,为了更好的理解并使用。花了一周的时间研究了一下WMRouter(v1.2.0版本)。下面从四个方面说下自己的理解,希望能给大家提供帮助。
- what it is? what can it do?
- how to use it ?
- how it works ?
- why design like that?
一,WMRouter是什么,能解决什么问题?
1.1,什么是路由框架?
路由框架,也就是路由的作用。路由是起什么作用呢?就像送快递,从小县城到省会,再从省会发到北京分拨中心,然后再从北京分拨中心发到回龙观,再从回龙观发到具体的小区。路由框架解决的就是如何从A页面跳转到B页面的问题,其会在A和B之间建立数个节点。通过这些节点依次进行转发,最终达到目的地。
1.2,为什么需要路由框架?
Android原生已经支持AndroidManifest去管理App跳转,为什么要有路由库?
- 显示Intent:项目庞大以后,类依赖耦合太大,不适合组件化拆分
- 隐式Intent:协作困难,调用时候不知道调什么参数。每个注册了Scheme的Activity都可以直接打开,有安全风险
- AndroidMainfest集中式管理比较臃肿
- 无法动态修改路由,如果页面出错,无法动态降级
- 无法动态拦截跳转,譬如未登录的情况下,打开登录页面,登录成功后接着打开刚才想打开的页面
- H5、Android、iOS地址不一样,不利于统一跳转
1.3,WMRouter的特点。
WMRouter是一款Android路由框架,主要提供URI分发、ServiceLoader两大功能(后面会看到,URI分发功能也是用ServiceLoader实现的)
URI分发功能可用于跨module的页面跳转、动态下发URI链接的跳转等场景,特点如下:
- 跳转的页面支持配置scheme、host、path。
- 支持URI正则匹配。
- 支持页面Exported控制,特定页面不允许外部跳转
- 默认使用注解配置自动注册,也支持 Java 代码动态注册。
- 某些页面需要登录等条件才能进入的时候,可以配置拦截器,可在跳转前执行同步/异步操作。
- 支持单次跳转特殊操作:Intent设置Extra/Flags、设置跳转动画、自定义StartActivity操作等
- 支持配置单次和全局跳转监听(可以实现降级策略,也可以自定义处理逻辑)
- 完全组件化设计,核心组件均可扩展、按需组合,实现灵活强大的功能
WMRouter提供了ServiceLoader模块,类似Java中的 java.util.ServiceLoader
,但功能更加完善。通过ServiceLoader可以在一个App的多个模块之间通过接口调用代码,实现模块解耦,便于实现组件化、模块间通信,以及和依赖注入类似的功能等。其特点如下:
- 使用注解自动配置
- 支持获取接口的所有实现,或根据Key获取特定实现
- 支持获取Class或获取实例
- 支持无参构造、Context构造,或自定义Factory、Provider构造
- 支持单例管理
- 支持方法调用
二,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,可选,支持配置多个。
说明:
-
WMRouter支持多scheme+host+path的跳转,也支持只有path的跳转。如果RouterUri中配置了scheme、host、path,则跳转时应使用scheme+host+path的完整路径;如果RouterUri中只配置了path,则跳转应直接使用path。
-
由于多数场景下往往只需要一个固定的scheme+host,不想在每个RouterUri注解上都写一遍scheme、host,这种场景可以在初始化时用
new DefaultRootUriHandler("scheme", "host")
指定默认的scheme、host,RouterUri没有配置的字段会使用这个默认值。
举例
1、用户账户页面只配置path;跳转前要先登录,因此添加了一个LoginInterceptor。
@RouterUri(path = "/account", interceptors = LoginInterceptor.class) public class UserAccountActivity extends Activity { } 复制代码
Router.startUri(context, "/account"); 复制代码
2、一个页面配置多个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"); 复制代码
3、根据后台下发的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,可选,支持配置多个。
举例
1、对于指定域名的http(s)链接,使用特定的WebViewActivity打开。
@RouterRegex(regex = "http(s)?://(.*\\.)?(meituan|sankuai|dianping)\\.(com|info|cn).*", priority = 2) public class WebViewActivity extends BaseActivity { } 复制代码
2、对于其他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是通过
RootUriHandler
的startUri
开始分发的,所以,如果要添加全局的拦截器,就可以通过给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模块使用主要分三步:
- 定义Java接口或抽象类。如果需要跨module调用接口实现,要把接口下沉,确保不同module都能获取接口抽象类。
- 实现接口或抽象类,然后添加@RotuerService注解(接口抽象类,key,是否单例)。
- 通过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
),这三者之间的映射关系。
- 文件1,
-
还需要注意。编译过程只是生成了能够进行注册的代码。但是代码并没有执行。只有等到开启提前加载(执行
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) 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 UriHandler#handle
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执行分发的具体逻辑。 -
RootUriHandler
的startUri()
方法,是所有分发跳转的入口。DefaultRootUriHandler
是RootUriHandler
的默认实现,其构造函数中添加了PageAnnotationHandler
,UriAnnotationHandler
,RegexAnnotationHandler
,StartUriHandler
的实例。然后分发跳转请求的时候,依次交给这四个UriHandler处理,如果某个UriHandler不能够处理该请求,则交给下一个执行。如果能够处理该请求,则交给该UriHandler的子节点继续分发。比如,如果UriAnnotationHandler能够处理该请求,会交给其子节点PathHandler处理,然后PathHandler交给ActivityHandler处理。 - 只有
PageAnnotationHandler
,UriAnnotationHandler
,RegexAnnotationHandler
这三个子类复写了UriHandler
的handle()
方法。其复写的目的是为了在真正开始分发之前,调用mInitHelper.ensureInit()
,确保该分支的路由表已经生成。 -
PageAnnotationHandler
写死了SCHEME和HOST,所以只处理所有格式为wm_router://page/*
的URI,根据path匹配。 -
UriAnnotationHandler
分发到PathHandler
,PathHandler
分发到ActivityClassNameHandler
或ActivityHandler
是最常用的分发路径。 - 如果
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找到对应的接口实现类。那么具体过程呢?
保存映射关系
- 调用
put
方法(见下图),参数有:接口的Class对象,接口实现类中的key,接口实现类的Class对象,以及接口实现类是否要求单例。在put方法中,调用SERVICES.put(interfaceClass, loader)
,存入接口Class对象和ServiceLoader实例的关系。然后调用loader.putImpl(key, implementClass, singleton)
。 - 在ServiceLoader实例的
putImpl
方法中,调用mMap.put(key, new ServiceImpl(key, implementClass, singleton))
,在ServiceLoader实例中存入key和接口实例class对象的关系。注意这里创建了一个ServiceImpl对象。那么ServiceImpl是干什么的呢?
获取映射关系
- 通过
getService
等方法获取接口的实例。该方法就一行代码ServiceLoader.load(clazz).get(key)
。 - 在
load
方法中,首先调用sInitHelper.ensureInit()
。通过反射,调用com.sankuai.waimai.router.generated.ServiceLoaderInit
类的init
方法(ServiceLoaderInit
类是通过gradle插件生成的)。在init
方法中调用各个ServiceInit_36ed390bf4b81a8381d45028b37cc645
的init
方法(见下图)。那么ServiceLoaderInit类中的init方法中的这些ServiceInit_36ed390bf4b81a8381d45028b37cc645
类是什么呢?这些类中的init方法里面又是什么呢? - 然后继续执行
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
,因为DefaultRootUriHandler
是ChainedHandler
的子类,而且没有实现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 组件化 —— 路由设计最佳实践
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
- 使用源码编译Hadoop
- GYHttpMock:使用及源码解析
- fishhook使用场景&源码分析
- 使用 Clion 阅读 Envoy 源码
- pinpoint 使用和相关源码解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机动画的算法基础
鲍虎军 金小刚 彭群生 / 浙江大学出版社 / 2000-12 / 60.00元
《计算机应用技术前沿丛书:计算机动画的算法基础》主要内容简介:20世纪是一个科技、经济空前发展的时代,从世纪初相对论、量子理论的创立到今天以信息产业为龙头的高科技产业成为经济发展的第一支柱,人类社会发生了根本性的变革。而在这场以科学技术为社会发展直接动因的变革中,意义最深远、影响最广泛的就是计算机及其相关技术的发展和应用。在过去的50年里,计算机已从最初的协助人类进行精密和复杂运算的单一功能的运算......一起来看看 《计算机动画的算法基础》 这本书的介绍吧!