猪小花1号

个人签名

282篇博客

考拉Android客户端路由总线设计 (下篇)

猪小花1号2018-08-28 12:05

生成路由表

路由表的生成需要使用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()方法执行路由。

待开发

  1. 职责链模式,参考OkHttp
  2. 集成Fragment
  3. 支持异步
  4. 路由缓存
  5. 路由智能优先级(调用过的,放最前面)
  6. 集成权限管理
  7. 考虑需要登录的情况,统一处理

总结

考拉路由框架与其他路由框架相比,目前功能较简单,目的也仅是支持页面跳转。为了达到对开发者友好、使用简单的目的,本文在设计路由框架的过程中使用了一些简单的设计模式,使得整个系统的可扩展性较强,也能够充分的满足考拉的业务需求。

参考

  1. https://github.com/alibaba/ARouter
  2. http://www.jianshu.com/p/79e9a54e85b2
  3. https://joyrun.github.io/2016/08/01/ActivityRouter/
  4. http://www.jianshu.com/p/8a3eeeaf01e8
  5. http://www.jianshu.com/p/f582c3893bed



网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者黎星授权发布。