路由表的生成需要使用APT工具以及Square公司开源的javapoet类库,目的是根据我们定义的Router
注解让机器帮我们“写代码”,生成一个Map类型的路由表,其中key根据Router
注解的信息生成对应的正则表达式,value是这个注解对应的类的信息集合。首先定义一个RouterProcessor
,继承自AbstractProcessor
,
public class RouterProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 初始化相关环境信息
mFiler = processingEnv.getFiler();
elementUtil = processingEnv.getElementUtils();
typeUtil = processingEnv.getTypeUtils();
Log.setLogger(processingEnv.getMessager());
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> supportAnnotationTypes = new HashSet<>();
// 获取需要处理的注解类型,目前只处理Router注解
supportAnnotationTypes.add(Router.class.getCanonicalName());
return supportAnnotationTypes;
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 收集与Router相关的所有类信息,解析并生成路由表
Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Router.class);
try {
return parseRoutes(routeElements);
} catch (Exception e) {
Log.e(e.getMessage(), e);
return false;
}
}
}
上述的三个方法属于AbstractProcessor的方法,public abstract boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
是抽象方法,需要子类实现。
private boolean parseRoutes(Set<? extends Element> routeElements) throws IOException {
if (null == routeElements || routeElements.size() == 0) {
return false;
}
// 获取Activity类的类型,后面用于判断是否是其子类
TypeElement typeActivity = elementUtil.getTypeElement(ACTIVITY);
// 获取路由Builder类的标准类名
ClassName routeBuilderCn = ClassName.get(RouteBuilder.class);
// 构建Map<String, Route>集合
String routerConstClassName = RouterProvider.ROUTER_CONST_NAME;
TypeSpec.Builder typeSpec = TypeSpec.classBuilder(routerConstClassName).addJavadoc(WARNING_TIPS).addModifiers(PUBLIC);
/**
* Map<String, Route>
*/
ParameterizedTypeName inputMapTypeName =
ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(String.class),
ClassName.get(Route.class));
ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeName, ROUTER_MAP_NAME).build();
MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(groupParamSpec);
// 将路由信息放入Map<String, Route>集合中
for (Element element : routeElements) {
TypeMirror tm = element.asType();
Router route = element.getAnnotation(Router.class);
// 获取当前Activity的标准类名
if (typeUtil.isSubtype(tm, typeActivity.asType())) {
ClassName activityCn = ClassName.get((TypeElement) element);
String key = "key" + element.getSimpleName().toString();
String routeString = RouteBuilder.assembleRouteUri(route.scheme(), route.host(), route.value());
if (null == routeString) {
//String keyValue = RouteBuilder.generateUriFromClazz(Activity.class);
loadIntoMethodOfGroupBuilder.addStatement("String $N= $T.generateUriFromClazz($T.class)", key,
routeBuilderCn, activityCn);
} else {
//String keyValue = "(" + route.value() + ")|(" + RouteBuilder.generateUriFromClazz(Activity.class) + ")";
loadIntoMethodOfGroupBuilder.addStatement(
"String $N=$S + $S + $S+$T.generateUriFromClazz($T.class)+$S", key, "(", routeString, ")|(",
routeBuilderCn, activityCn, ")");
}
/**
* routerMap.put(url, RouteBuilder.build(String url, int priority, Class<?> destination));
*/
loadIntoMethodOfGroupBuilder.addStatement("$N.put($N, $T.build($N, $N, $T.class))", ROUTER_MAP_NAME,
key, routeBuilderCn, key, String.valueOf(route.priority()), activityCn);
typeSpec.addField(generateRouteConsts(element));
}
}
// Generate RouterConst.java
JavaFile.builder(RouterProvider.OUTPUT_DIRECTORY, typeSpec.build()).build().writeTo(mFiler);
// Generate RouterGenerator
JavaFile.builder(RouterProvider.OUTPUT_DIRECTORY, TypeSpec.classBuilder(RouterProvider.ROUTER_GENERATOR_NAME)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(RouterProvider.class))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfGroupBuilder.build())
.build()).build().writeTo(mFiler);
return true;
}
最终生成的路由表如下:
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY KAOLA PROCESSOR. */
public class RouterGenerator implements RouterProvider {
@Override
public void loadRouter(Map<String, Route> routerMap) {
String keyActivityDetailActivity="(" + "(https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/activity/spring/\\w+" + ")|("+RouteBuilder.generateUriFromClazz(ActivityDetailActivity.class)+")";
routerMap.put(keyActivityDetailActivity, RouteBuilder.build(keyActivityDetailActivity, 0, ActivityDetailActivity.class));
String keyLabelDetailActivity="(" + "(https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/album/tag/share\\.html" + ")|("+RouteBuilder.generateUriFromClazz(LabelDetailActivity.class)+")";
routerMap.put(keyLabelDetailActivity, RouteBuilder.build(keyLabelDetailActivity, 0, LabelDetailActivity.class));
String keyMyQuestionAndAnswerActivity="(" + "(https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/app/myQuestion.html" + ")|("+RouteBuilder.generateUriFromClazz(MyQuestionAndAnswerActivity.class)+")";
routerMap.put(keyMyQuestionAndAnswerActivity, RouteBuilder.build(keyMyQuestionAndAnswerActivity, 0, MyQuestionAndAnswerActivity.class));
……
}
其中,RouteBuilder.generateUriFromClazz(Class)
的实现如下,目的是生成一条默认的与标准类名相关的native跳转规则。
public static final String SCHEME_NATIVE = "native://";
public static String generateUriFromClazz(Class<?> destination) {
String rawUri = SCHEME_NATIVE + destination.getCanonicalName();
return rawUri.replaceAll("\\.", "\\\\.");
}
可以看到,路由集合的key是一条正则表达式,包括了url拦截规则以及自定义的包含标准类名的native跳转规则。例如,keyMyQuestionAndAnswerActivity
最终生成的key是
((https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/app/
myQuestion.html)|(native://com.kaola.modules.answer.myAnswer.
MyQuestionAndAnswerActivity)
这样,调用者不仅可以通过默认的拦截规则 (https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/app/myQuestion.html)
跳转到对应的页面,也可以通过 (native://com.kaola.modules.answer.myAnswer.MyQuestionAndAnswerActivity)
。 这样的好处是模块间的跳转也可以使用,不需要依赖引用类。而native跳转会专门生成一个类RouterConst
来记录,如下:
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY KAOLA PROCESSOR. */
public class RouterConst {
public static final String ROUTE_TO_ActivityDetailActivity = "native://com.kaola.modules.activity.ActivityDetailActivity";
public static final String ROUTE_TO_LabelDetailActivity = "native://com.kaola.modules.albums.label.LabelDetailActivity";
public static final String ROUTE_TO_MyQuestionAndAnswerActivity = "native://com.kaola.modules.answer.myAnswer.MyQuestionAndAnswerActivity";
public static final String ROUTE_TO_CertificatedNameActivity = "native://com.kaola.modules.auth.activity.CertificatedNameActivity";
public static final String ROUTE_TO_CPSCertificationActivity = "native://com.kaola.modules.auth.activity.CPSCertificationActivity";
public static final String ROUTE_TO_BrandDetailActivity = "native://com.kaola.modules.brands.branddetail.ui.BrandDetailActivity";
public static final String ROUTE_TO_CartContainerActivity = "native://com.kaola.modules.cart.CartContainerActivity";
public static final String ROUTE_TO_SingleCommentShowActivity = "native://com.kaola.modules.comment.detail.SingleCommentShowActivity";
public static final String ROUTE_TO_CouponGoodsActivity = "native://com.kaola.modules.coupon.activity.CouponGoodsActivity";
public static final String ROUTE_TO_CustomerAssistantActivity = "native://com.kaola.modules.customer.CustomerAssistantActivity";
……
}
路由初始化在Application的过程中以同步的方式进行。通过获取RouterGenerator
的类直接生成实例,并将路由信息保存在sRouterMap
变量中。
public static void init() {
try {
sRouterMap = new HashMap<>();
((RouterProvider) (Class.forName(ROUTER_CLASS_NAME).getConstructor().newInstance())).loadRouter(sRouterMap);
} catch (Exception e) {
e.printStackTrace();
}
}
给定一个url以及上下文环境,即可使用路由。调用方式如下:
KaolaRouter.with(context).url(url).start();
页面路由分为路由请求生成,路由查找以及路由结果执行这几个步骤。路由请求目前较为简单,仅是封装了一个RouterRequest接口
public interface RouterRequest {
Uri getUriRequest();
}
路由的查找过程相对复杂,除了遍历路由初始化以后导入内存的路由表,还需要判断各种各样的前置条件。具体的条件判断代码中有相关注释。
@Override
public RouterResponse findResponse(RouterRequest request) {
if (null == sRouterMap) {
return null;
//throw new IllegalStateException(
// String.format("Router has not been initialized, please call %s.init() first.",
// KaolaRouter.class.getSimpleName()));
}
if (mRouterRequestWrapper.getDestinationClass() != null) {
RouterResponse response = RouterResponseFactory.buildRouterResponse(null, mRouterRequestWrapper);
reportFoundRequestCallback(request, response);
return response;
}
Uri uri = request.getUriRequest();
String requestUrl = uri.toString();
if (!TextUtils.isEmpty(requestUrl)) {
for (Map.Entry<String, Route> entry : sRouterMap.entrySet()) {
if (RouterUtils.matchUrl(requestUrl, entry.getKey())) {
Route routerModel = entry.getValue();
if (null != routerModel) {
RouterResponse response =
RouterResponseFactory.buildRouterResponse(routerModel, mRouterRequestWrapper);
reportFoundRequestCallback(request, response);
return response;
}
}
}
}
return null;
}
@Override
public RouterResult start() {
// 判断Context引用是否还存在
WeakReference<Context> objectWeakReference = mContextWeakReference;
if (null == objectWeakReference) {
reportRouterResultError(null, null, RouterError.ROUTER_CONTEXT_REFERENCE_NULL, null);
return getRouterResult(false, mRouterRequestWrapper, null);
}
Context context = objectWeakReference.get();
if (context == null) {
reportRouterResultError(null, null, RouterError.ROUTER_CONTEXT_NULL, null);
return getRouterResult(false, mRouterRequestWrapper, null);
}
// 判断路由请求是否有效
if (!checkRequest(context)) {
return getRouterResult(false, mRouterRequestWrapper, null);
}
// 遍历查找路路由结果
RouterResponse response = findResponse(mRouterRequestWrapper);
// 判断路由结果,执行路由结果为空时的拦截
if (null == response) {
boolean handledByCallback = reportLostRequestCallback(mRouterRequestWrapper);
if (!handledByCallback) {
reportRouterResultError(context, null, RouterError.ROUTER_RESPONSE_NULL,
mRouterRequestWrapper.getRouterRequest());
}
return getRouterResult(handledByCallback, mRouterRequestWrapper, null);
}
// 获取路由结果执行的接口
ResponseInvoker responseInvoker = getResponseInvoker(context, response);
if (responseInvoker == null) {
return getRouterResult(false, mRouterRequestWrapper, response);
}
Intent intent;
try {
intent = RouterUtils.generateResponseIntent(context, response, mRouterRequestWrapper);
} catch (Exception e) {
reportRouterResultError(context, null, RouterError.ROUTER_GENERATE_INTENT_ERROR, e);
return getRouterResult(false, mRouterRequestWrapper, response);
}
// 生成相应的Intent
if (null == intent) {
reportRouterResultError(context, null, RouterError.ROUTER_GENERATE_INTENT_NULL, response);
return getRouterResult(false, mRouterRequestWrapper, response);
}
// 获取路由结果回调接口,如果为空,则使用默认提供的实现
RouterResultCallback routerResultCallback = getRouterResultCallback();
// 由使用者处理
if (routerResultCallback.beforeRoute(context, intent)) {
return getRouterResult(true, mRouterRequestWrapper, response);
}
try {
responseInvoker.invoke(context, intent, mRouterRequestWrapper.getRequestCode(),
mRouterRequestWrapper.getOnActivityResultListener());
routerResultCallback.doRoute(context, intent, null);
return getRouterResult(true, mRouterRequestWrapper, response);
} catch (Exception e) {
reportRouterResultError(context, intent, RouterError.ROUTER_INVOKER_ERROR, e);
return getRouterResult(false, mRouterRequestWrapper, response);
}
}
最终会调用ResponseInvoker.invoke()
方法执行路由。
考拉路由框架与其他路由框架相比,目前功能较简单,目的也仅是支持页面跳转。为了达到对开发者友好、使用简单的目的,本文在设计路由框架的过程中使用了一些简单的设计模式,使得整个系统的可扩展性较强,也能够充分的满足考拉的业务需求。
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者黎星授权发布。