请 checkout interceptor
分支进行查看。
拦截器主要是在真正执行跳转操作之前做一些逻辑判断。比如, 我们平时 startActivity 的时候,使用隐式跳转会有一个问题,就是无法确定目标 activity 是否存在,这个时候就需要下面的代码判断一下:
PackageManager packageManager = getPackageManager();
if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null){
startActivity(intent);
}else {
Toast.makeText(MainActivity.this,"找不到你想要的activity",Toast.LENGTH_SHORT).show();
}
但是这里有个问题,就是每次跳转前都要调用这一段代码。而使用拦截器就不一样了,虽然拦截器也是这样的一段逻辑,但是拦截器就相当于在跳转前设置了一个关卡,每个跳转请求都需要经过它,这样就与我们的跳转代码分离了,可以重用,有不少好处。那么该如何设计拦截器呢?由于拦截器可能不止一个,还要考虑优先级问题,这里我们采用责任链的设计方式。
这里其实就是参考的 OkHttp 的代码写的。责任链模式就不具体介绍了,贴个图吧,不了解的可以 Google。
根据这个模式,我们定义出如下接口:
com.aprz.brouter.api.interceptor.IRouteInterceptor
/**
* 拦截器接口
*/
public interface IRouteInterceptor {
/**
* 拦截器的优先级,数值越小优先级越高,为了规范,数字不应该小于 0
* 或者可以做一个拦截器的依赖图,后续有精力再搞
*/
int priority();
/**
* 拦截器的拦截方法,Chain 作为参数,可以使用它来决定是否拦截
*/
void intercept(@NonNull Chain chain, @NonNull Callback callback);
interface Chain {
/**
* 如果拦截器决定不拦截,调用这个方法
*/
void proceed(@NonNull Navigation navigation);
/**
* 如果拦截器决定拦截,调用这个方法
*/
void interrupt(@NonNull Throwable exception);
/**
* 返回路由对象
*/
Navigation navigate();
}
interface Callback {
/**
* 跳转成功
*/
void onSuccess(@NonNull Navigation navigation);
/**
* 跳转失败,被拦截器拦下了
*/
void onFail(@NonNull Throwable exception);
}
}
这里 IRouteInterceptor
与 Chain
都比较好理解,照着责任链的例子都可以写出来,那么 Callback
的作用是搞啥的呢?它的作用就是通知使用者,该次跳转是否成功。对于一个跳转请求,在整个责任链流程里面,callback
参数都是同一个对象。
Chain
这个接口是我们需要实现的,它将 IRouteInterceptor
的实现类都收集在一起,然后依次调用:
com.aprz.brouter.api.interceptor.RouteInterceptorChain
@Override
public void proceed(@NonNull Navigation navigation) {
handleInterceptors();
}
private void handleInterceptors() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (index >= interceptors.size()) {
// todo 抛出异常
return;
}
IRouteInterceptor interceptor = interceptors.get(index);
RouteInterceptorChain next = new RouteInterceptorChain(interceptors, index + 1, navigation, callback);
interceptor.intercept(next, callback);
}
});
}
这里,我将拦截器的流程分发到了主线程去执行,这里主要是考虑到拦截器里面需要弹窗等更新UI之类的操作才这样做的,当然,如果你的拦截器里面有比较繁重的逻辑,可以自己开线程处理,只要最后调用 com.aprz.brouter.api.interceptor.IRouteInterceptor.Chain#proceed
方法就行了。
拦截器的拦截逻辑需要在跳转之前,所以,我们直接在跳转前面,加上我们的逻辑就好了:
com.aprz.brouter.api.core.Navigation#navigate(android.content.Context, com.aprz.brouter.api.interceptor.IRouteInterceptor.Callback)
public void navigate(final Context context, final IRouteInterceptor.Callback callback) {
Navigation target = RouteStore.getNavigation(path);
List<IRouteInterceptor> interceptorList = InterceptorStore.getInterceptorList(target.path);
// 最后的拦截器,不然没有 callback.onSuccess 回调
interceptorList.add(new LastInterceptor());
Collections.sort(interceptorList, (o1, o2) -> o1.priority() - o2.priority());
IRouteInterceptor.Chain chain = new RouteInterceptorChain(
interceptorList,
0,
target,
new IRouteInterceptor.Callback() {
@Override
public void onSuccess(@NonNull Navigation navigation) {
// 执行真正的跳转逻辑
internalNavigate(context, navigation);
if (callback != null) {
callback.onSuccess(navigation);
}
}
@Override
public void onFail(@NonNull Throwable exception) {
if (callback != null) {
callback.onFail(exception);
}
}
});
chain.proceed(target);
}
public void internalNavigate(Context context, Navigation navigation) {
if (navigation.valid() && context != null) {
Intent intent = new Intent(context, targetActivityClass);
if (params != null) {
intent.putExtras(params);
}
if (!(context instanceof Activity)) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
}
}
这里,对比一下没有拦截器功能的代码,可以看到,实际上就是多了几行代码而已,这几行代码做了这些事情:
- 获取所有的拦截器
- 对拦截器进行排序
- 触发拦截器的责任链,依次执行拦截器里面的逻辑
我们看看一个拦截器例子,进一步了解拦截器,了解它可以做什么事情。
com.aprz.brouter.api.interceptor.LastInterceptor
/**
* 该拦截器应该在所有拦截器的最后添加
* 作用:
* 通知拦截回调,拦截链走完了,回调成功
*/
public class LastInterceptor implements IRouteInterceptor {
@Override
public int priority() {
return Integer.MAX_VALUE;
}
@Override
public void intercept(@NonNull Chain chain, @NonNull Callback callback) {
callback.onSuccess(chain.navigate());
}
}
这个拦截器是自带的拦截器,它的作用只是用来回调 callback
的方法的。所以它应该最后一个执行才对!!!可以看跳转逻辑,我们是最后一个添加的。
com.aprz.wallet.WalletParamsInterceptor
/**
* 该拦截器用来监测,跳转到 wallet 时,传递的参数是否合规
*/
@Interceptor(path = Constants.RoutePath.WALLET_ACTIVITY)
public class WalletParamsInterceptor implements IRouteInterceptor {
@Override
public int priority() {
return 100;
}
@Override
public void intercept(@NonNull Chain chain, @NonNull Callback callback) {
Navigation navigate = chain.navigate();
// 因为注解里面指定了path,不符合的不会走这个拦截器
Bundle params = navigate.getParams();
if (params != null && params.getLong("userId") > 0) {
chain.proceed(navigate);
} else {
chain.interrupt(new IllegalArgumentException("参数不正确,userId 不合法!!! "));
if (params == null) {
Toast.makeText(BRouter.context(), "没有传递参数不正确,不合法!!!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(BRouter.context(), "参数不正确,userId 不合法!!!" + params.getLong("userId"), Toast.LENGTH_SHORT).show();
}
}
}
}
这个,是开发者自定义的拦截器,注意这个类需要使用 Interceptor
来修饰,因为,只有用这个注解修饰,我们才能收集到这个类,然后将他添加到拦截器的集合中。另一个需要注意的地方,就是这个拦截器指定了 path,这就说明它不是一个全局的拦截器,它只拦截指定路径的跳转请求!!!
这个类的作用注释也写的很清楚了,不管是否拦截,一定要调用 proceed
或者 interrupt
方法的其中一个,否则跳转流程就走不下去了。
自定义的拦截器需要使用 Interceptor
来修饰,这是因为我们写了一个注解处理器,他会生成这样的代码:
com.aprz.brouter.interceptors.BRouter$$Interceptor$$app
public class BRouter$$Interceptor$$app implements IModuleInterceptor {
public Map<String, IRouteInterceptor> interceptors() {
Map<String, IRouteInterceptor> result = new HashMap<String, IRouteInterceptor>();
return result;
}
public List<IRouteInterceptor> globalInterceptors() {
List<IRouteInterceptor> result = new ArrayList<IRouteInterceptor>();
result.add(new NotFoundInterceptor());
return result;
}
}
这个类有两个方法,一个是收集全局拦截器,也就是注解里面没有指定 path 属性的。一个是收集非全局拦截器,也就是指定了 path 属性的。
有了这个类,我们使用反射的方式(也可以使用字节码方式,但是懒得实现了)实例化这个对象,就可以拿到该 module 下的所有拦截器了。
但是,这里还有问题,我们如何知道,工程有哪些 module 呢?而且,拦截器这样处理,还有类似的组件服务也是要这样处理,能不能将逻辑写的通用一点呢?
为了解决这两个问题,我又为每个 Module 生成了一个 BRouter$$Module$$xxx
类,这个类里面代码如下:
com.aprz.brouter.module.BRouter$$Module$$app
public final class BRouter$$Module$$app implements IModule {
private IModule module;
public BRouter$$Module$$app() {
this.module = new com.aprz.home.HomeModule();
}
@Override
public void onCreate(Application application) {
this.module.onCreate(application);
InterceptorHelper.addModuleInterceptor("app");
DegradeHelper.addModuleDegrade("app");
}
@Override
public void onDestroy() {
this.module.onDestroy();
}
}
这个类实际上是一个增强类,它在 com.aprz.home.HomeModule 的基础上(com.aprz.home.HomeModule 就是相当于组件的 Application 了),额外的做了一些事情。主要是收集 module 里面的拦截器等等。我们看看逻辑:
com.aprz.brouter.api.interceptor.InterceptorHelper
/**
* 这里添加每个 module 里面的自定义拦截器
*/
public static void addModuleInterceptor(String module) {
IModuleInterceptor moduleInterceptor = findModuleInterceptor(module);
List<IRouteInterceptor> globalInterceptors = moduleInterceptor.globalInterceptors();
for (IRouteInterceptor interceptor : globalInterceptors) {
InterceptorStore.putGlobalInterceptor(interceptor);
}
Map<String, IRouteInterceptor> interceptors = moduleInterceptor.interceptors();
for (Map.Entry<String, IRouteInterceptor> entry : interceptors.entrySet()) {
InterceptorStore.putInterceptor(entry.getKey(), entry.getValue());
}
}
@SuppressWarnings("unchecked")
@NonNull
private static IModuleInterceptor findModuleInterceptor(String module) {
try {
Class<IModuleInterceptor> interceptorClass =
(Class<IModuleInterceptor>) Class.forName("com.aprz.brouter.interceptors.BRouter$$Interceptor$$" + module);
return interceptorClass.newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
Log.e(TAG, "没有在 module 内部找到拦截器: " + module);
return new EmptyModuleInterceptor();
}
}
static class EmptyModuleInterceptor implements IModuleInterceptor {
@Override
public List<IRouteInterceptor> globalInterceptors() {
return new ArrayList<>(0);
}
@Override
public Map<String, IRouteInterceptor> interceptors() {
return new HashMap<>(0);
}
}
就是使用反射,拿到生成的类,然后生成实例,获取 module 里面的拦截器。有了这样的一份模板,那么以后,想要收集什么,都可以这样做。
最后,我们只需要为每个module注册一下即可:
com.aprz.brouter.App#onCreate
ModuleHelper.register("app");
ModuleHelper.register("wallet");
这样,每个module想收集啥,都可以使用反射来做了。register 里面,就调用了 module 的生命周期:
com.aprz.brouter.api.module.ModuleHelper#register
public static void register(String moduleName) {
if (modules.containsKey(moduleName)) {
Log.e(TAG, "发现了重复注册的module (默认替换为新的): " + moduleName);
}
IModule module = findModuleByName(moduleName);
// 执行 module 的 onCreate 方法
module.onCreate(BRouter.application());
modules.put(moduleName, module);
}
看了一些其他项目的代码,降级策略,都是在跳转失败的时候,做降级处理。我想了半天没想明白,什么情况下才会跳转失败呢?我们的app里面,没有做过降级处理,所以暂时无法理解是什么需求导致的。但是我还是开动我的脑筋,写了一个降级的例子。
com.aprz.brouter.interceptos.NotFoundInterceptor
/**
* 本来想将这个拦截器写在库里面的,但是发现怎么写都不爽,后来才想明白,这个拦截器应该给使用者实现才是对的
* 当没有匹配的 url 的时候,跳转到一个 404 页面
*/
@Interceptor
public class NotFoundInterceptor implements IRouteInterceptor {
private static final String TAG = "NotFoundInterceptor";
@Override
public int priority() {
return 10;
}
@Override
public void intercept(@NonNull Chain chain, @NonNull Callback callback) {
Log.e(TAG, "NotFoundInterceptor run on " + Thread.currentThread().getName());
Navigation navigate = chain.navigate();
if (navigate.invalid()) {
chain.interrupt(new RouteNotFoundException("没有找到匹配的路由地址:" + navigate.getPath()));
// 有降级策略,走降级
IRouteDegrade routeDegrade = DegradeHelper.getRouteDegrade(navigate);
if (routeDegrade != null) {
routeDegrade.handleDegrade(navigate);
}
// 没有降级策略,走默认页面
else {
// 不要搞成死循环了
BRouter.getInstance().path("app/not_found").navigate();
}
} else {
// 有降级策略,走降级
IRouteDegrade routeDegrade = DegradeHelper.getRouteDegrade(navigate);
if (routeDegrade != null) {
routeDegrade.handleDegrade(navigate);
}
// 没有降级策略,继续执行原来的跳转
else {
chain.proceed(navigate);
}
}
}
}
这个拦截器的逻辑比较简单,就是在 path 找不到对应页面的时候,跳转一个默认页面,或者跳到降级页面。这里可以看出来,降级算是拦截器的一个分支。
为了让这个例子更有意义,我在 path 有对应页面的时候,也会进行降级处理。例子大概是这样的,开发了一个新功能来替换老的功能,但是新的功能不一定稳定,所以有一个降级策略,就是监控新功能crash次数,当超过一定次数后,就开始降级处理:
com.aprz.wallet.WalletDegrade
@Degrade
public class WalletDegrade implements IRouteDegrade {
@Override
public boolean isMatch(@NonNull Navigation navigation) {
// 当新的 activity crash 次数超过限制了之后,需要降级
String targetPage = Constants.RoutePath.WALLET_ACTIVITY;
return CrashMonitor.needDegrade(targetPage)
&& targetPage.equals(navigation.getPath());
}
@Override
public void handleDegrade(@NonNull Navigation navigation) {
// 降级,跳转到老的页面
BRouter.getInstance()
.path(Constants.RoutePath.WALLET_OLD_ACTIVITY)
.navigate();
}
}