ARouter source code analysis

I have read the source code several times, but I always forget it. Here is a record

Start with the annotation processor

BaseProcessor is an abstract class of the other three annotation processors, and subclasses implement the process method.
In the init method, it will be obtained from our module module Area filled in the gradle file_ Module_ Value corresponding to name. If the value of the processed moduleName is null, an exception will be thrown
if (StringUtils.isNotEmpty(moduleName)) {
            moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");

            logger.info("The user has configuration the module name, it was [" + moduleName + "]");
        } else {
            logger.error(NO_MODULE_NAME_TIPS);
            throw new RuntimeException("ARouter::Compiler >>> No module name, for more information, look at gradle log.");
  }

Then you initialize some tools

 mFiler = processingEnv.getFiler();
 types = processingEnv.getTypeUtils();
 elementUtils = processingEnv.getElementUtils();
 typeUtils = new TypeUtils(types, elementUtils);
 logger = new Logger(processingEnv.getMessager());
RouteProcessor is used to process the annotation Route
//In fact, this key is the group name we set. By default, it is the first word of path, not the routing set under each module,
//For example, if our two Route values under a module are set with group="group1" and group="group2" respectively, then there will be two values stored in the Map respectively
//The routing set under group1 and the routing set under group2 will also generate two class settings for us. The following code will parse
private Map<String, Set<RouteMeta>> groupMap = new HashMap<>(); // ModuleName and routeMeta There is something wrong with this official note
//This is actually an index table. Key is the group name and value is the generated class name in the above annotation. That is, the above routing set can be found through this key. Each module corresponds to a class.
private Map<String, String> rootMap = new TreeMap<>();

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (CollectionUtils.isNotEmpty(annotations)) {
//Scan the project to get all elements with Route annotation Set
<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class); try { logger.info(">>> Found routes, start... <<<"); this.parseRoutes(routeElements); } catch (Exception e) { logger.error(e); } return true; } return false; }

Parse the parseroutes (set<? Extensions element> routeelements) method in the above code

 

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);

Get the type type corresponding to the class Element. About type and Element References: https://www.jianshu.com/p/899063e8452e The

/*
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)
);

Use javaport to create map<string, class<? Extensions iroutegroup>> and map<string, routemetat> methods are parameter types

ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build();

That's it
Map<string, class<? Extensions iroutegroup>> complete parameters of routes and map<string, routemetat> atlas and map<string, routemetat> providers
/*
Build method : 'loadInto'
*/
MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(rootParamSpec);
Creation method: create ARouter$$Root$$moduleName class after loadintomethodofrootbuilder < -- > loadinto (map<string, class <? Extensions iroutegroup>> routes)

 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
//The Integer after the name and type (name, String type) of the attribute with Autowirde annotation (sex, boolean attribute) is an enumeration type Map<String, Integer> paramsType = new HashMap<>();
Map
<String, Autowired> injectConfig = new HashMap<>();
//This is used to handle Autowirde annotations in children injectParamCollector(element, paramsType, injectConfig);
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); } //The attribute with Autowird in the Activity or fragment will also be put into the route 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() + "]."); } //Verify the accuracy of the path and populate the routing table groupMap categories(routeMeta); }
private void categories(RouteMeta routeMete) {
//After verification
if (routeVerify(routeMete)) {
logger.info(">>> Start categories, group = " + routeMete.getGroup() + ", path = " + routeMete.getPath() + " <<<");
Set<RouteMeta> routeMetas = groupMap.get(routeMete.getGroup());
if (CollectionUtils.isEmpty(routeMetas)) {
Set<RouteMeta> routeMetaSet = new TreeSet<>(new Comparator<RouteMeta>() {
@Override
public int compare(RouteMeta r1, RouteMeta r2) {
try {
return r1.getPath().compareTo(r2.getPath());
} catch (NullPointerException npe) {
logger.error(npe.getMessage());
return 0;
}
}
});
//Populate routing table
routeMetaSet.add(routeMete);
//Map the routing table to the corresponding group
groupMap.put(routeMete.getGroup(), routeMetaSet);
} else {
//If a group already exists, it is directly added to the routing table
routeMetas.add(routeMete);
}
} else {
logger.warning(">>> Route meta verify error, group is " + routeMete.getGroup() + " <<<");
}
}
//Verify the accuracy of path
private boolean routeVerify(RouteMeta meta) {
String path = meta.getPath();
//If the path is empty or the path does not start with "/", the verification fails
if (StringUtils.isEmpty(path) || !path.startsWith("/")) { // The path must be start with '/' and not empty!
return false;
}
//Group uses the first word in the path as the group value by default
if (StringUtils.isEmpty(meta.getGroup())) { // Use default group(the first word in path)
try {
String defaultGroup = path.substring(1, path.indexOf("/", 1));
if (StringUtils.isEmpty(defaultGroup)) {
return false;
}
//Set group name for routing information
meta.setGroup(defaultGroup);
return true;
} catch (Exception e) {
logger.error("Failed to extract default group! " + e.getMessage());
return false;
}
}

return true;
}
After the above method groupMap It is assigned successfully. The following is the generation of the class.


MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(providerParamSpec);
Create loadintomethodofproviderbuilder < --- >loadinto (map<string, class <? Extensions iroutegroup > > providers) and create ARouter$$Providers$$moduleName class to use

  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);
                }

                // Several groups generate several classes ARouter$$Group$$groupName
                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 + "<<<");
//Fill in the rootmap. Key is the group name. value is the class name generated above rootMap.put(groupName, groupFileName); docSource.put(groupName, routeDocList); }

After this traversal, the system generates
public class ARouter$$Group$$Group name implement IRooteGroup{
public void loadInto(Map<String,RouteMata> atlas){
atlas.put("/order/OrderActivity", RouteMeta object)
atlas.put("/order/OrderDetailActivity", RouteMeta object)
     When this method is called externally, you will get the entire routing information
}
}

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();
}

// Create ARouter$$Providers$$module name. The Map key in the loadInto method of this class is the entire class name, and the value is RouteMeta
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 + " <<<");

// Create Arouter$$Route$$module name key is the group name value is the class corresponding to the Arouter$$Group$$group name
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);

After the above operations, the
public class ARouter$$Providers$$modulejava implements IProviderGroup {
@Override
public void loadInto(Map<String, RouteMeta> providers) {
providers.put("com.alibaba.android.arouter.demo.service.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/yourservicegroupname/hello", "yourservicegroupname", null, -1, -2147483648));
providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/yourservicegroupname/json", "yourservicegroupname", null, -1, -2147483648));
providers.put("com.alibaba.android.arouter.demo.module1.testservice.SingleService", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/yourservicegroupname/single", "yourservicegroupname", null, -1, -2147483648));
}
}

public class ARouter$$Root$$modulejava implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("m2", ARouter$$Group$$m2.class);
routes.put("module", ARouter$$Group$$module.class);
routes.put("test", ARouter$$Group$$test.class);
routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
}
}

So far, we can also find ARouter$$Root$$Module name and ARouter$$Providers$$The module name means that several classes will be created for several modules, but ARouter$$Group$$The group name is to create several classes for several groups
also ARouter$$Group$$Group name is also stored in this class provider Routing information, ARouter$$Providers$$The module name is also stored provider Information. Which means that we can both path lookup provider information
You can also search by the full class name provider Information.
 

At this point, the function of the Route annotation is analyzed. When waiting for the application to register, call the method of the class we created above and store the data in the warehouse

The Autowired annotation is relatively simple. It uses reflection to call our getintent Get method to assign values.

The Interceptor annotation is used to help us generate classes such as ARouter$$Interceptors$$modulejava
public class ARouter$$Interceptors$$modulejava implements IInterceptorGroup {
@Override
public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
//key is the priority value and the class of the interceptor we added
interceptors.put(7, Test1Interceptor.class);
interceptors.put(90, TestInterceptor90.class);
}
}
When we call arouter Init (application)
1. start the thread pool to scan the class object under the package to get that the collection of classes has a cache, which does not need to be scanned every time
2. resolve the class collection. If the class starts with ARouter$$Root, use class Forname gets the IRouterRoot object and calls the loadInto method to assign the Map in it to the Map in the data warehouse
Fill in: map<string, class<? Extensions irotegroup>> groupsindex
3. starting with ARoute$$Interceptors, call the loadInto method of IInterceptors to fill in the map<integer, class<? Extensions irotegroup>>interceptorsindex
4. call the loadInto method of IProvidersGroup to fill in the map<string, routemetat>providersindex in WareHouse if it starts with ARoute$$Providers
At this time, there must be a question: why didn't the loadInto method in the generated ARouter$$Group$$group name be called?
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);
}
}

After initialization, the following method is called
static void afterInit() {
// Trigger interceptor init, use byName.
interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}
protected Postcard build(String path) {
if (TextUtils.isEmpty(path)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
//It exposes an IProvider interface, PathReplaceService, which can preprocess things. If we implement this interface, we can do something in advance
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return build(path, extractGroup(path), true);
}
}

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService the interceptor of this system uses the thread pool to process our own interceptors

 

  

Posted by MBenefactor on Tue, 31 May 2022 18:30:48 +0530