URI跳转核心设计思路与接口
下图展示了WMRouter中URI跳转的核心设计思路。借鉴网络请求的机制,WMRouter中的每次URI跳转视为发起一个UriRequest;URI跳转请求被WMRouter逐层分发给一系列的UriHandler进行处理;每个UriHandler处理之前可以被UriInterceptor拦截,并插入一些特殊操作。
UriRequest
UriRequest中包含Context、URI和Fields,其中Fields为HashMap
存放到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的实现。
public interface UriCallback {
/**
* 处理完成,继续后续流程。
*/
void onNext();
/**
* 处理完成,终止分发流程。
*
* @param resultCode 结果
*/
void onComplete(int resultCode);
}
public class DemoUriHandler extends UriHandler {
public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
Uri uri = request.getUri();
// 处理HTTP链接
if ("http".equalsIgnoreCase(uri.getScheme())) {
try {
// 调用系统浏览器
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(uri);
request.getContext().startActivity(intent);
// 跳转成功
callback.onComplete(UriResult.CODE_SUCCESS);
} catch (Exception e) {
// 跳转失败
callback.onComplete(UriResult.CODE_ERROR);
}
} else {
// 非HTTP链接不处理,继续分发
callback.onNext();
}
}
// ...
}
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);
}
}
}
灵活性与易用性的平衡
由于WMRouter是一个开放式组件化框架,UriRequest可以存放任意数据,UriHandler、UriInterceptor可以完全自定义,不同的UriHandler可以任意组合,具有很大的灵活性。但过于灵活容易导致易用性的下降,即使对于最常规最简单的应用,也需要复杂的配置才能完成功能。
为了在两者之间平衡,WMRouter对包结构进行了划分,核心接口和实现类提供基础通用能力,尽可能保留最大的灵活性。可以在core包基础上进行自定义开发和配置,独立运行。
- core:提供核心接口和实现类,提供基础通用能力。
- utils:通用工具类。
- components:辅助功能组件。
在保证核心组件灵活性的基础上,WMRouter又封装了一系列通用实现类,并组合成一套默认实现,满足绝大多数使用场景。
- activity:Activity跳转相关。
- regex:正则匹配相关。
- common:UriHandler、UriInterceptor、UriRequest通用实现类。
WMRouter还提供了ServiceLoader模块。
- service:ServiceLoader模块。
- method:方法调用,提供了几个通用接口,基于ServiceLoader实现方法调用。