URI跳转核心设计思路与接口

下图展示了WMRouter中URI跳转的核心设计思路。借鉴网络请求的机制,WMRouter中的每次URI跳转视为发起一个UriRequest;URI跳转请求被WMRouter逐层分发给一系列的UriHandler进行处理;每个UriHandler处理之前可以被UriInterceptor拦截,并插入一些特殊操作。

URI跳转核心设计思路与接口 - 图1

UriRequest

UriRequest中包含Context、URI和Fields,其中Fields为HashMap,可以通过Key存放任意数据。简单起见,UriRequest类同时承担了Response的功能,跳转请求的结果,也会被保存到Fields中。
存放到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跳转中所有组件之间的通信功能。

UriHandler

UriHandler用于处理URI跳转请求,可以嵌套从而逐层分发和处理请求。UriHandler是异步结构,接收到UriRequest后处理(例如跳转Activity等),如果处理完成,则调用callback.onComplete()并传入ResultCode;如果没有处理,则调用callback.onNext()继续分发。

下面的示例代码展示了一个只处理HTTP链接的UriHandler的实现。

  1. public interface UriCallback {
  2. /**
  3. * 处理完成,继续后续流程。
  4. */
  5. void onNext();
  6. /**
  7. * 处理完成,终止分发流程。
  8. *
  9. * @param resultCode 结果
  10. */
  11. void onComplete(int resultCode);
  12. }
  13. public class DemoUriHandler extends UriHandler {
  14. public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
  15. Uri uri = request.getUri();
  16. // 处理HTTP链接
  17. if ("http".equalsIgnoreCase(uri.getScheme())) {
  18. try {
  19. // 调用系统浏览器
  20. Intent intent = new Intent();
  21. intent.setAction(Intent.ACTION_VIEW);
  22. intent.setData(uri);
  23. request.getContext().startActivity(intent);
  24. // 跳转成功
  25. callback.onComplete(UriResult.CODE_SUCCESS);
  26. } catch (Exception e) {
  27. // 跳转失败
  28. callback.onComplete(UriResult.CODE_ERROR);
  29. }
  30. } else {
  31. // 非HTTP链接不处理,继续分发
  32. callback.onNext();
  33. }
  34. }
  35. // ...
  36. }

UriInterceptor

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

  • URI跳转拦截,禁止特定的URI跳转,直接返回403(例如禁止跳转非meituan域名的HTTP链接)
  • URI参数修改(例如在HTTP链接末尾添加query参数)
  • 各种中间处理(例如打开登录页登录、获取定位、发网络请求)
  • ……

每个UriHandler都可以添加若干UriInterceptor。在UriHandler基类中,handle()方法先调用抽象方法shouldHandle()判断是否要处理UriRequest,如果需要处理,则逐个执行Interceptor,最后再调用handleInternal()方法进行跳转操作。

举例来说,跳转某些页面需要先登录,可以实现一个LoginInterceptor如下。

  1. public class LoginInterceptor implements UriInterceptor {
  2. @Override
  3. public void intercept(@NonNull UriRequest request, @NonNull final UriCallback callback) {
  4. final FakeAccountService accountService = FakeAccountService.getInstance();
  5. if (accountService.isLogin()) {
  6. // 已经登录,不需处理,继续跳转流程
  7. callback.onNext();
  8. } else {
  9. // 没登录,提示登录并启动登录页
  10. Toast.makeText(request.getContext(), "请先登录~", Toast.LENGTH_SHORT).show();
  11. accountService.registerObserver(new FakeAccountService.Observer() {
  12. @Override
  13. public void onLoginSuccess() {
  14. accountService.unregisterObserver(this);
  15. // 登录成功,继续跳转
  16. callback.onNext();
  17. }
  18. @Override
  19. public void onLoginFailure() {
  20. accountService.unregisterObserver(this);
  21. // 登录失败,终止流程,返回错误ResultCode
  22. callback.onComplete(CustomUriResult.CODE_LOGIN_FAILURE);
  23. }
  24. });
  25. // 启动登录页
  26. startActivity(request.getContext(), LoginActivity.class);
  27. }
  28. }
  29. }

灵活性与易用性的平衡

由于WMRouter是一个开放式组件化框架,UriRequest可以存放任意数据,UriHandler、UriInterceptor可以完全自定义,不同的UriHandler可以任意组合,具有很大的灵活性。但过于灵活容易导致易用性的下降,即使对于最常规最简单的应用,也需要复杂的配置才能完成功能。

为了在两者之间平衡,WMRouter对包结构进行了划分,核心接口和实现类提供基础通用能力,尽可能保留最大的灵活性。可以在core包基础上进行自定义开发和配置,独立运行。

  • core:提供核心接口和实现类,提供基础通用能力。
  • utils:通用工具类。
  • components:辅助功能组件。

在保证核心组件灵活性的基础上,WMRouter又封装了一系列通用实现类,并组合成一套默认实现,满足绝大多数使用场景。

  • activity:Activity跳转相关。
  • regex:正则匹配相关。
  • common:UriHandler、UriInterceptor、UriRequest通用实现类。

WMRouter还提供了ServiceLoader模块。

  • service:ServiceLoader模块。
  • method:方法调用,提供了几个通用接口,基于ServiceLoader实现方法调用。