美文网首页程序员
ARouter源码解析

ARouter源码解析

作者: A邱凌 | 来源:发表于2020-08-13 16:47 被阅读0次

    前言

    当我们项目变得庞大的时候 使用单模块 就会变得很臃肿 所以当我们使用组件化架构的时候 有一个需要解决的问题就是跨模块通信问题 我们可以使用阿里的ARouter或者美团的WMRouter来实现

    使用方法

    使用方法很简单 就是使用@Route注解 然后跳转使用ARouter单例来跳转 具体可以参考官网文档

    编译阶段

    我们可以使用AnnotationProcessor 来对注解进行解析和并且生成代码 所以我们看一下编译阶段会做哪些事情

    @Route注解为例 看一下RouteProcessor源码 下面这段可以跳过 不怎么想看解析注解的代码 大致原理就是 获取所有@Route注解
    然后通过javapoet生成具体的类(包名会固定为com.alibaba.android.arouter.routes)

        private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
            if (CollectionUtils.isNotEmpty(routeElements)) {
                // prepare the type an so on.
    
                logger.info(">>> Found routes, size is " + routeElements.size() + " <<<");
    
                rootMap.clear();
    
                TypeMirror type_Activity = elementUtils.getTypeElement(ACTIVITY).asType();
                TypeMirror type_Service = elementUtils.getTypeElement(SERVICE).asType();
                TypeMirror fragmentTm = elementUtils.getTypeElement(FRAGMENT).asType();
                TypeMirror fragmentTmV4 = elementUtils.getTypeElement(Consts.FRAGMENT_V4).asType();
    
                // Interface of ARouter
                TypeElement type_IRouteGroup = elementUtils.getTypeElement(IROUTE_GROUP);
                TypeElement type_IProviderGroup = elementUtils.getTypeElement(IPROVIDER_GROUP);
                ClassName routeMetaCn = ClassName.get(RouteMeta.class);
                ClassName routeTypeCn = ClassName.get(RouteType.class);
    
                /*
                   Build input type, format as :
                   ```Map<String, Class<? extends IRouteGroup>>```
                 */
                ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(
                        ClassName.get(Map.class),
                        ClassName.get(String.class),
                        ParameterizedTypeName.get(
                                ClassName.get(Class.class),
                                WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))
                        )
                );
    
                /*
                  ```Map<String, RouteMeta>```
                 */
                ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
                        ClassName.get(Map.class),
                        ClassName.get(String.class),
                        ClassName.get(RouteMeta.class)
                );
    
                /*
                  Build input param name.
                 */
                ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
                ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
                ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build();  // Ps. its param type same as groupParamSpec!
    
                /*
                  Build method : 'loadInto'
                 */
                MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                        .addAnnotation(Override.class)
                        .addModifiers(PUBLIC)
                        .addParameter(rootParamSpec);
    
                //  Follow a sequence, find out metas of group first, generate java file, then statistics them as root.
                for (Element element : routeElements) {
                    TypeMirror tm = element.asType();
                    Route route = element.getAnnotation(Route.class);
                    RouteMeta routeMeta;
    
                    // Activity or Fragment
                    if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
                        // Get all fields annotation by @Autowired
                        Map<String, Integer> paramsType = new HashMap<>();
                        Map<String, Autowired> injectConfig = new HashMap<>();
                        for (Element field : element.getEnclosedElements()) {
                            if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null && !types.isSubtype(field.asType(), iProvider)) {
                                // It must be field, then it has annotation, but it not be provider.
                                Autowired paramConfig = field.getAnnotation(Autowired.class);
                                String injectName = StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name();
                                paramsType.put(injectName, typeUtils.typeExchange(field));
                                injectConfig.put(injectName, paramConfig);
                            }
                        }
    
                        if (types.isSubtype(tm, type_Activity)) {
                            // Activity
                            logger.info(">>> Found activity route: " + tm.toString() + " <<<");
                            routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                        } else {
                            // Fragment
                            logger.info(">>> Found fragment route: " + tm.toString() + " <<<");
                            routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
                        }
    
                        routeMeta.setInjectConfig(injectConfig);
                    } else if (types.isSubtype(tm, iProvider)) {         // IProvider
                        logger.info(">>> Found provider route: " + tm.toString() + " <<<");
                        routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null);
                    } else if (types.isSubtype(tm, type_Service)) {           // Service
                        logger.info(">>> Found service route: " + tm.toString() + " <<<");
                        routeMeta = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
                    } else {
                        throw new RuntimeException("The @Route is marked on unsupported class, look at [" + tm.toString() + "].");
                    }
    
                    categories(routeMeta);
                }
    
                MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                        .addAnnotation(Override.class)
                        .addModifiers(PUBLIC)
                        .addParameter(providerParamSpec);
    
                Map<String, List<RouteDoc>> docSource = new HashMap<>();
    
                // Start generate java source, structure is divided into upper and lower levels, used for demand initialization.
                for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
                    String groupName = entry.getKey();
    
                    MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                            .addAnnotation(Override.class)
                            .addModifiers(PUBLIC)
                            .addParameter(groupParamSpec);
    
                    List<RouteDoc> routeDocList = new ArrayList<>();
    
                    // Build group method body
                    Set<RouteMeta> groupData = entry.getValue();
                    for (RouteMeta routeMeta : groupData) {
                        RouteDoc routeDoc = extractDocInfo(routeMeta);
    
                        ClassName className = ClassName.get((TypeElement) routeMeta.getRawType());
    
                        switch (routeMeta.getType()) {
                            case PROVIDER:  // Need cache provider's super class
                                List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();
                                for (TypeMirror tm : interfaces) {
                                    routeDoc.addPrototype(tm.toString());
    
                                    if (types.isSameType(tm, iProvider)) {   // Its implements iProvider interface himself.
                                        // This interface extend the IProvider, so it can be used for mark provider
                                        loadIntoMethodOfProviderBuilder.addStatement(
                                                "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                                (routeMeta.getRawType()).toString(),
                                                routeMetaCn,
                                                routeTypeCn,
                                                className,
                                                routeMeta.getPath(),
                                                routeMeta.getGroup());
                                    } else if (types.isSubtype(tm, iProvider)) {
                                        // This interface extend the IProvider, so it can be used for mark provider
                                        loadIntoMethodOfProviderBuilder.addStatement(
                                                "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                                tm.toString(),    // So stupid, will duplicate only save class name.
                                                routeMetaCn,
                                                routeTypeCn,
                                                className,
                                                routeMeta.getPath(),
                                                routeMeta.getGroup());
                                    }
                                }
                                break;
                            default:
                                break;
                        }
    
                        // Make map body for paramsType
                        StringBuilder mapBodyBuilder = new StringBuilder();
                        Map<String, Integer> paramsType = routeMeta.getParamsType();
                        Map<String, Autowired> injectConfigs = routeMeta.getInjectConfig();
                        if (MapUtils.isNotEmpty(paramsType)) {
                            List<RouteDoc.Param> paramList = new ArrayList<>();
    
                            for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
                                mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");
    
                                RouteDoc.Param param = new RouteDoc.Param();
                                Autowired injectConfig = injectConfigs.get(types.getKey());
                                param.setKey(types.getKey());
                                param.setType(TypeKind.values()[types.getValue()].name().toLowerCase());
                                param.setDescription(injectConfig.desc());
                                param.setRequired(injectConfig.required());
    
                                paramList.add(param);
                            }
    
                            routeDoc.setParams(paramList);
                        }
                        String mapBody = mapBodyBuilder.toString();
    
                        loadIntoMethodOfGroupBuilder.addStatement(
                                "atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                routeMeta.getPath(),
                                routeMetaCn,
                                routeTypeCn,
                                className,
                                routeMeta.getPath().toLowerCase(),
                                routeMeta.getGroup().toLowerCase());
    
                        routeDoc.setClassName(className.toString());
                        routeDocList.add(routeDoc);
                    }
    
                    // Generate groups
                    String groupFileName = NAME_OF_GROUP + groupName;
                    JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                            TypeSpec.classBuilder(groupFileName)
                                    .addJavadoc(WARNING_TIPS)
                                    .addSuperinterface(ClassName.get(type_IRouteGroup))
                                    .addModifiers(PUBLIC)
                                    .addMethod(loadIntoMethodOfGroupBuilder.build())
                                    .build()
                    ).build().writeTo(mFiler);
    
                    logger.info(">>> Generated group: " + groupName + "<<<");
                    rootMap.put(groupName, groupFileName);
                    docSource.put(groupName, routeDocList);
                }
    
                if (MapUtils.isNotEmpty(rootMap)) {
                    // Generate root meta by group name, it must be generated before root, then I can find out the class of group.
                    for (Map.Entry<String, String> entry : rootMap.entrySet()) {
                        loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));
                    }
                }
    
                // Output route doc
                if (generateDoc) {
                    docWriter.append(JSON.toJSONString(docSource, SerializerFeature.PrettyFormat));
                    docWriter.flush();
                    docWriter.close();
                }
    
                // Write provider into disk
                String providerMapFileName = NAME_OF_PROVIDER + SEPARATOR + moduleName;
                JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                        TypeSpec.classBuilder(providerMapFileName)
                                .addJavadoc(WARNING_TIPS)
                                .addSuperinterface(ClassName.get(type_IProviderGroup))
                                .addModifiers(PUBLIC)
                                .addMethod(loadIntoMethodOfProviderBuilder.build())
                                .build()
                ).build().writeTo(mFiler);
    
                logger.info(">>> Generated provider map, name is " + providerMapFileName + " <<<");
    
                // Write root meta into disk.
                String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
                JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                        TypeSpec.classBuilder(rootFileName)
                                .addJavadoc(WARNING_TIPS)
                                .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT)))
                                .addModifiers(PUBLIC)
                                .addMethod(loadIntoMethodOfRootBuilder.build())
                                .build()
                ).build().writeTo(mFiler);
    
                logger.info(">>> Generated root, name is " + rootFileName + " <<<");
            }
        }
    

    ARouter.init

    我们上面讲到了 生成的java类包名为com.alibaba.android.arouter.routes
    ARouter.init方法会调用LogisticsCenter.init方法
    看一下核心

    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
            try {
            
                    ......
                    
                loadRouterMap();
                if (registerByPlugin) {
                    //autoRegiterPlugin 后面会讲到
                    logger.info(TAG, "Load router map by arouter-auto-register plugin.");
                } else {
                    Set<String> routerMap;
                        //如果是debug模式 或者版本有更新 会重新获取所有的Route注解类 并且保存到SP
                    // It will rebuild router map every times when debuggable.
                    if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                        logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                        // These class was generated by arouter-compiler.
                        //根据我们上面说的包名 获取所有的Class类
                        routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                        //存在SP中
                        if (!routerMap.isEmpty()) {
                            context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                        }
    
                        PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
                    } else {
                        routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                    }
    
                   startInit = System.currentTimeMillis();
    
                        //这里会循环遍历所有的Class类 然后会通过反射 创建所有实例 并保存到map中 
                        //这里因为会反射创建所有实例 所以会比较耗时 做启动优化时 可以通过AutoRegister来优化
                    for (String className : routerMap) {
                        if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                            // This one of root elements, load root.
                            ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                        } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                            // Load interceptorMeta
                            ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                        } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                            // Load providerIndex
                            ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                        }
                    }
                }
            } catch (Exception e) {
                throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
            }
        }
    

    ARouter.init会将所有注解Class 实例化并存放在map中 方便调用

    navigation

    看一下调用链

    Postcard.navigation->
    ARouter.navigation->
    _ARouter.navigation->
    
    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
            ......
            try {
                  //填充postcard
                LogisticsCenter.completion(postcard);
            } catch (NoRouteFoundException ex) {
                if (null != callback) {
                    callback.onLost(postcard);
                } else {
                    // No callback for this invoke, then we use the global degrade service.
                    DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                    if (null != degradeService) {
                        degradeService.onLost(context, postcard);
                    }
                }
    
                return null;
            }
    
            if (null != callback) {
                callback.onFound(postcard);
            }
    
            if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
                //调用拦截器
                interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                    /**
                     * Continue process
                     *
                     * @param postcard route meta
                     */
                    @Override
                    public void onContinue(Postcard postcard) {
                        _navigation(context, postcard, requestCode, callback);
                    }
    
                    /**
                     * Interrupt process, pipeline will be destory when this method called.
                     *
                     * @param exception Reson of interrupt.
                     */
                    @Override
                    public void onInterrupt(Throwable exception) {
                        if (null != callback) {
                            callback.onInterrupt(postcard);
                        }
    
                        logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                    }
                });
            } else {
                  //继续处理
                return _navigation(context, postcard, requestCode, callback);
            }
    
            return null;
        }
        
    private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
            final Context currentContext = null == context ? mContext : context;
              //根据类型跳转
            switch (postcard.getType()) {
                case ACTIVITY:
                    // Build intent
                    final Intent intent = new Intent(currentContext, postcard.getDestination());
                    intent.putExtras(postcard.getExtras());
    
                    // Set flags.
                    int flags = postcard.getFlags();
                    if (-1 != flags) {
                        intent.setFlags(flags);
                    } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    }
    
                    // Set Actions
                    String action = postcard.getAction();
                    if (!TextUtils.isEmpty(action)) {
                        intent.setAction(action);
                    }
    
                    // Navigation in main looper.
                    runInMainThread(new Runnable() {
                        @Override
                        public void run() {
                            startActivity(requestCode, currentContext, intent, postcard, callback);
                        }
                    });
    
                    break;
                case PROVIDER:
                    return postcard.getProvider();
                case BOARDCAST:
                case CONTENT_PROVIDER:
                case FRAGMENT:
                    Class fragmentMeta = postcard.getDestination();
                    try {
                        Object instance = fragmentMeta.getConstructor().newInstance();
                        if (instance instanceof Fragment) {
                            ((Fragment) instance).setArguments(postcard.getExtras());
                        } else if (instance instanceof android.support.v4.app.Fragment) {
                            ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                        }
    
                        return instance;
                    } catch (Exception ex) {
                        logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                    }
                case METHOD:
                case SERVICE:
                default:
                    return null;
            }
    
            return null;
        }
    

    AutoRegister插件

    调用ARouter.init方法时 会遍历所有dex文件 并且找到被@Route注解的类 并通过反射生成 这是一个比较耗时的操作 之前在启动优化的过程中 发现ARouter启动占了一半的时间

    ARouter提供了AutoRegister插件 主要核心原理就是将ARouter.init方法的流程 在编译过程完成 通过Transform+ASM的方式 遍历所有dex文件 并且生成工厂类 这样就避免启动时遍历

    参考:

    自动注册插件
    AutoRegister:一种更高效的组件自动注册方案(android组件化开发)

    总结

    我们可以使用ARouter来方便的进行路由管理 核心原理就是扫描所有@Route注解的类 然后统一收集 跳转时取出 并且还有很多像拦截器这种功能

    拦截器功能我们得吐槽一下 就是所有的路由跳转 都会遍历每一个拦截器 浪费了一部分资源

    相关文章

      网友评论

        本文标题:ARouter源码解析

        本文链接:https://www.haomeiwen.com/subject/dxhidktx.html