概要

使用ARouter,我们只需要在必要的地方加上注解,然后在application中init Arouter就可以直接通过代码进行路由跳转了。
为什么我们可以不用写任何注册的代码,就直接跳转到相关路由呢?答案肯定是——ARouter帮我们做了。

@Route(path = "/test/activity2")
public class Test2Activity extends AppCompatActivity {——中间代码省略——
}
                ARouter.init(getApplication());
                ARouter.getInstance().build("/test/activity2").navigation();

本文主要分析下ARouter的自动注册的实现。

实现流程

笔者准备先列出实现的流程,再具体讲解相关代码。
流程如下:

  1. 通过使用注解解释器生成辅助类。
  2. 通过使用动态编译(Transform+Plugin)的方式,调用注解解释器生成的辅助类,来讲注解中的内容注册。

注解解释器生成辅助类,是在编译时做的。
而gradle plugin是在编译完成生成class之后,在虚拟机运行之前做的。

ARouter.init()

首先看下开发者们最熟悉的初始化方法。

    public static void init(Application application) {if (!hasInit) {logger = _ARouter.logger;_ARouter.logger.info(Consts.TAG, "ARouter init start.");hasInit = _ARouter.init(application);if (hasInit) {_ARouter.afterInit();}_ARouter.logger.info(Consts.TAG, "ARouter init over.");}}

随后进入_ARouter.init()

    protected static synchronized boolean init(Application application) {mContext = application;LogisticsCenter.init(mContext, executor);logger.info(Consts.TAG, "ARouter init success!");hasInit = true;mHandler = new Handler(Looper.getMainLooper());return true;}

随后进入LogisticsCenter.init()

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {mContext = context;executor = tpe;try {long startInit = System.currentTimeMillis();//billy.qi modified at 2017-12-06//load by plugin firstloadRouterMap();if (registerByPlugin) {logger.info(TAG, "Load router map by arouter-auto-register plugin.");} else {Set<String> routerMap;// 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.routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);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 {logger.info(TAG, "Load router map from cache.");routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));}logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");startInit = System.currentTimeMillis();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);}}}logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");if (Warehouse.groupsIndex.size() == 0) {logger.error(TAG, "No mapping files were found, check your configuration please!");}if (ARouter.debuggable()) {logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));}} catch (Exception e) {throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");}}

代码很长,但是有效代码其实很少,由于loadRouterMap()中肯定会把registerByPlugin设置为true,因此后面else中的内容不会执行到。
所以最关键的代码就只有loadRouterMap()这个方法而已。

    /*** arouter-auto-register plugin will generate code inside this method* call this method to register all Routers, Interceptors and Providers* @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>* @since 2017-12-06*/private static void loadRouterMap() {registerByPlugin = false;//auto generate register code by gradle plugin: arouter-auto-register// looks like below:// registerRouteRoot(new ARouter..Root..modulejava());// registerRouteRoot(new ARouter..Root..modulekotlin());}

官方注释也写的很明白了。这个方法就是注册路由的方法,会通过插件来向里面插入代码。

arouter-gradle-plugin

虽然注解解释器从逻辑上看是执行在前,但是我们还是根据从init源码层层深入了看。
先分析plugin,最后再看注解解释器。

arouter源码非常清晰,插件的module就是“arouter-gradle-plugin”。

先看下PluginLaunch中实现:

public class PluginLaunch implements Plugin<Project> {@Overridepublic void apply(Project project) {def isApp = project.plugins.hasPlugin(AppPlugin)//only application module needs this plugin to generate register codeif (isApp) {Logger.make(project)Logger.i('Project enable arouter-register plugin')def android = project.extensions.getByType(AppExtension)def transformImpl = new RegisterTransform(project)//init arouter-auto-register settingsArrayList<ScanSetting> list = new ArrayList<>(3)list.add(new ScanSetting('IRouteRoot'))list.add(new ScanSetting('IInterceptorGroup'))list.add(new ScanSetting('IProviderGroup'))RegisterTransform.registerList = list//register this pluginandroid.registerTransform(transformImpl)}}}

Plugin中主要通过RegisterTransform.registerList来限制了注解解释器操作的类,这个在后面的ScanUtils中会用到。
然后通过注册transformImpl来触发动态编译。

接下来看下Transform中transform的实现:

@Overridevoid transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {Logger.i('Start scan register info in jar file.')long startTime = System.currentTimeMillis()boolean leftSlash = File.separator == '/'inputs.each { TransformInput input ->// scan all jarsinput.jarInputs.each { JarInput jarInput ->String destName = jarInput.name// rename jar filesdef hexName = DigestUtils.md5Hex(jarInput.file.absolutePath)if (destName.endsWith(".jar")) {destName = destName.substring(0, destName.length() - 4)}// input fileFile src = jarInput.file// output fileFile dest = outputProvider.getContentLocation(destName + "_" + hexName, jarInput.contentTypes, jarInput.scopes, Format.JAR)//scan jar file to find classesif (ScanUtil.shouldProcessPreDexJar(src.absolutePath)) {ScanUtil.scanJar(src, dest)}FileUtils.copyFile(src, dest)}// scan class filesinput.directoryInputs.each { DirectoryInput directoryInput ->File dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)String root = directoryInput.file.absolutePathif (!root.endsWith(File.separator))root += File.separatordirectoryInput.file.eachFileRecurse { File file ->def path = file.absolutePath.replace(root, '')if (!leftSlash) {path = path.replaceAll("\\\\", "/")}if(file.isFile() && ScanUtil.shouldProcessClass(path)){ScanUtil.scanClass(file)}}// copy to destFileUtils.copyDirectory(directoryInput.file, dest)}}Logger.i('Scan finish, current cost time ' + (System.currentTimeMillis() - startTime) + "ms")if (fileContainsInitClass) {registerList.each { ext ->Logger.i('Insert register code to file ' + fileContainsInitClass.absolutePath)if (ext.classList.isEmpty()) {Logger.e("No class implements found for interface:" + ext.interfaceName)} else {ext.classList.each {Logger.i(it)}RegisterCodeGenerator.insertInitCodeTo(ext)}}}Logger.i("Generate code finish, current cost time: " + (System.currentTimeMillis() - startTime) + "ms")}

主要逻辑整理一下:

  1. 先通过ScanUtil来扫描每一个类
  2. 再通过RegisterCodeGenerator来插入代码

所以我们按顺序先看下ScanUtil的实现。
class ScanUtil {

static void scanJar(File jarFile, File destFile) {if (jarFile) {def file = new JarFile(jarFile)Enumeration enumeration = file.entries()while (enumeration.hasMoreElements()) {JarEntry jarEntry = (JarEntry) enumeration.nextElement()String entryName = jarEntry.getName()if (entryName.startsWith(ScanSetting.ROUTER_CLASS_PACKAGE_NAME)) {InputStream inputStream = file.getInputStream(jarEntry)scanClass(inputStream)inputStream.close()} else if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {// mark this jar file contains LogisticsCenter.class// After the scan is complete, we will generate register code into this fileRegisterTransform.fileContainsInitClass = destFile}}file.close()}
}static boolean shouldProcessPreDexJar(String path) {return !path.contains("com.android.support") && !path.contains("/android/m2repository")
}static boolean shouldProcessClass(String entryName) {return entryName != null && entryName.startsWith(ScanSetting.ROUTER_CLASS_PACKAGE_NAME)
}static void scanClass(File file) {scanClass(new FileInputStream(file))
}static void scanClass(InputStream inputStream) {ClassReader cr = new ClassReader(inputStream)ClassWriter cw = new ClassWriter(cr, 0)ScanClassVisitor cv = new ScanClassVisitor(Opcodes.ASM5, cw)cr.accept(cv, ClassReader.EXPAND_FRAMES)inputStream.close()
}static class ScanClassVisitor extends ClassVisitor {ScanClassVisitor(int api, ClassVisitor cv) {super(api, cv)}void visit(int version, int access, String name, String signature,String superName, String[] interfaces) {super.visit(version, access, name, signature, superName, interfaces)RegisterTransform.registerList.each { ext ->if (ext.interfaceName && interfaces != null) {interfaces.each { itName ->if (itName == ext.interfaceName) {ext.classList.add(name)}}}}}
}

}
关键代码其实也很少,不管是class还是jar,最终都是会走到static void scanClass(InputStream inputStream)这个方法。
这里会根据前面在Plugin中设置的RegisterTransform.registerList的类来过滤处理的类,需要处理的类都会add到 ScanSetting.classList这个ArrayList中。

前面提到ScanUtil会扫描内容,然后由RegisterCodeGenerator来插入代码,接下来看下RegisterCodeGenerator的实现。

class RegisterCodeGenerator {ScanSetting extensionprivate RegisterCodeGenerator(ScanSetting extension) {this.extension = extension}static void insertInitCodeTo(ScanSetting registerSetting) {if (registerSetting != null && !registerSetting.classList.isEmpty()) {RegisterCodeGenerator processor = new RegisterCodeGenerator(registerSetting)File file = RegisterTransform.fileContainsInitClassif (file.getName().endsWith('.jar'))processor.insertInitCodeIntoJarFile(file)}}/*** generate code into jar file* @param jarFile the jar file which contains LogisticsCenter.class* @return*/private File insertInitCodeIntoJarFile(File jarFile) {if (jarFile) {def optJar = new File(jarFile.getParent(), jarFile.name + ".opt")if (optJar.exists())optJar.delete()def file = new JarFile(jarFile)Enumeration enumeration = file.entries()JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(optJar))while (enumeration.hasMoreElements()) {JarEntry jarEntry = (JarEntry) enumeration.nextElement()String entryName = jarEntry.getName()ZipEntry zipEntry = new ZipEntry(entryName)InputStream inputStream = file.getInputStream(jarEntry)jarOutputStream.putNextEntry(zipEntry)if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {Logger.i('Insert init code to class >> ' + entryName)def bytes = referHackWhenInit(inputStream)jarOutputStream.write(bytes)} else {jarOutputStream.write(IOUtils.toByteArray(inputStream))}inputStream.close()jarOutputStream.closeEntry()}jarOutputStream.close()file.close()if (jarFile.exists()) {jarFile.delete()}optJar.renameTo(jarFile)}return jarFile}//refer hack class when object initprivate byte[] referHackWhenInit(InputStream inputStream) {ClassReader cr = new ClassReader(inputStream)ClassWriter cw = new ClassWriter(cr, 0)ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5, cw)cr.accept(cv, ClassReader.EXPAND_FRAMES)return cw.toByteArray()}class MyClassVisitor extends ClassVisitor {MyClassVisitor(int api, ClassVisitor cv) {super(api, cv)}void visit(int version, int access, String name, String signature,String superName, String[] interfaces) {super.visit(version, access, name, signature, superName, interfaces)}@OverrideMethodVisitor visitMethod(int access, String name, String desc,String signature, String[] exceptions) {MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions)//generate code into this methodif (name == ScanSetting.GENERATE_TO_METHOD_NAME) {mv = new RouteMethodVisitor(Opcodes.ASM5, mv)}return mv}}class RouteMethodVisitor extends MethodVisitor {RouteMethodVisitor(int api, MethodVisitor mv) {super(api, mv)}@Overridevoid visitInsn(int opcode) {//generate code before returnif ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {extension.classList.each { name ->name = name.replaceAll("/", ".")mv.visitLdcInsn(name)//类名// generate invoke register method into LogisticsCenter.loadRouterMap()mv.visitMethodInsn(Opcodes.INVOKESTATIC, ScanSetting.GENERATE_TO_CLASS_NAME, ScanSetting.REGISTER_METHOD_NAME, "(Ljava/lang/String;)V", false)}}super.visitInsn(opcode)}@Overridevoid visitMaxs(int maxStack, int maxLocals) {super.visitMaxs(maxStack + 4, maxLocals)}}
}

插入的实现主要有两步,首先是用ASM插入代码,然后用JarOutputStream覆写掉原来的文件。
主要插入的操作在RouteMethodVisitor中,extension这个变量是一个ScanSetting对象,在ScanUtil中也就是用它来保存过滤后的类。
动态生成的代码会生成在LogisticsCenter的loadRouterMap这个方法中。
在这里通过遍历过滤后的类,来动态生成多行代码,生成后的代码大概是这样:

com.alibaba.android.arouter.core.LogisticsCenter.register("com.xxx.MainActivity")
com.alibaba.android.arouter.core.LogisticsCenter.register("com.xxx.SubActivity")

接下来我们再看下LogisticsCenter.register这个方法的源码。

    private static void register(String className) {if (!TextUtils.isEmpty(className)) {try {Class<?> clazz = Class.forName(className);Object obj = clazz.getConstructor().newInstance();if (obj instanceof IRouteRoot) {registerRouteRoot((IRouteRoot) obj);} else if (obj instanceof IProviderGroup) {registerProvider((IProviderGroup) obj);} else if (obj instanceof IInterceptorGroup) {registerInterceptor((IInterceptorGroup) obj);} else {logger.info(TAG, "register failed, class name: " + className+ " should implements one of IRouteRoot/IProviderGroup/IInterceptorGroup.");}} catch (Exception e) {logger.error(TAG,"register class error:" + className);}}}

这里我们只是分析流程,因此不需要看每个注解的细节,我们只看registerRouteRoot这个方法就可以。

    private static void registerRouteRoot(IRouteRoot routeRoot) {markRegisteredByPlugin();if (routeRoot != null) {routeRoot.loadInto(Warehouse.groupsIndex);}}
class Warehouse {// Cache route and metasstatic Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();——代码省略——
}

最终会通过loadInfo方法将要注册的内容存储到Warehouse.groupsIndex这个变量中。

由于IRouteRoot只是一个interface,具体的实现类是由注解解释器来动态实现的。

arouter-compiler

arouter-compiler中实现了多个注解解释器,由于我们只分析自动注册的流程,因此只看路由相关的就可以。
先看下注解解析的关键方法。

    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {if (CollectionUtils.isNotEmpty(annotations)) {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;}

可以看到,过滤出Route注解后,都交由parseRoutes这个方法处理了。

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 ARouterTypeElement 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;if (types.isSubtype(tm, type_Activity)) {                 // Activitylogger.info(">>> Found activity route: " + tm.toString() + " <<<");// Get all fields annotation by @AutowiredMap<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);}}routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);routeMeta.setInjectConfig(injectConfig);} else if (types.isSubtype(tm, iProvider)) {         // IProviderlogger.info(">>> Found provider route: " + tm.toString() + " <<<");routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null);} else if (types.isSubtype(tm, type_Service)) {           // Servicelogger.info(">>> Found service route: " + tm.toString() + " <<<");routeMeta = new RouteMeta(route, element, RouteType.parse(SERVICE), null);} else if (types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {logger.info(">>> Found fragment route: " + tm.toString() + " <<<");routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), null);} else {throw new RuntimeException("ARouter::Compiler >>> Found unsupported class type, type = [" + types.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 bodySet<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 classList<? 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 providerloadIntoMethodOfProviderBuilder.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 providerloadIntoMethodOfProviderBuilder.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 paramsTypeStringBuilder 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 groupsString 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 docif (generateDoc) {docWriter.append(JSON.toJSONString(docSource, SerializerFeature.PrettyFormat));docWriter.flush();docWriter.close();}// Write provider into diskString 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 + " <<<");}}

处理代码有点多,主要是因为同时处理了“routes”、“atlas”和“providers”三种情况。
其主要逻辑其实很简单,就是根据注解的内容生成新的辅助类,此处直接贴上arouter demo中生成的辅助类,读者根据代码走一遍逻辑就能明白了。

public class ARouter$$Group$$test implements IRouteGroup {@Overridepublic void loadInto(Map<String, RouteMeta> atlas) {atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", null, -1, -2147483648));atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));}
}

总结

最后,根据运行的流程来做个自动注册的总结:

  1. 在编译阶段。注解解释器会自动生成辅助类,这些辅助类会继承IRouteGroup、IProvider、IRouteRoot这些interface。
  2. 在编译结束,到虚拟机执行之前。会根据这些interface,将自动生成的这些辅助类过滤出来,然后在loadRouterMap.loadRouterMap() 中调用loadRouterMap.register()来将这些辅助类中的内容自动注册。

ARouter init 源码解析(自动注册的实现)相关推荐

  1. MVC源码解析 - 配置注册 / 动态注册 HttpModule

    本来这一篇, 是要继续 Pipeline 的, 但是在 Pipeline之前, 我看到了InitModules()方法, 所以决定, 在中间穿插一篇进来. 这一篇来讲一下 IHttpModule 的加 ...

  2. MyBatis3源码解析(7)TypeHandler注册与获取

    简介 在上篇文章中,我们介绍了TypeHandler的简单使用和解析了TypeHandler的处理核心,这篇文章中我们接着看到TypeHandler是如注册和获取使用的 源码解析 TypeHandle ...

  3. SpringBoot入门-源码解析(雷神)

    一.Spring Boot入门 视频学习资料(雷神): https://www.bilibili.com/video/BV19K4y1L7MT?p=1 github: https://github.c ...

  4. EventBus源码解析

    前面一篇文章讲解了EventBus的使用,但是作为开发人员,不能只停留在仅仅会用的层面上,我们还需要弄清楚它的内部实现原理.所以本篇博文将分析EventBus的源码,看看究竟它是如何实现"发 ...

  5. Dubbo 实现原理与源码解析系列 —— 精品合集

    摘要: 原创出处 http://www.iocoder.cn/Dubbo/good-collection/ 「芋道源码」欢迎转载,保留摘要,谢谢! 1.[芋艿]精尽 Dubbo 原理与源码专栏 2.[ ...

  6. ARouter 源码解析(1.5.2 版本)

    文章目录 1.简介 2.ARouter 配置与基本用法 2.1 依赖引入与配置 2.2 基本用法 3.ARouter 编译时原理分析 4.ARouter 源码解析 4.1 ARouter 源码主要代码 ...

  7. 路由框架ARouter最全源码解析

    ARouter是2017年阿里巴巴开源的一款Android路由框架,官方定义: ARouter是Android平台中对页面,服务提供路由功能的中间件,提倡简单且够用 有下面几个优势: 1.直接解析UR ...

  8. ARouter 源码解析(零) 基本使用

    ARouter 源码解析(零) 基本使用 零.要解决的问题 在app的开发中,页面之间的相互跳转是最基本常用的功能.在Android中的跳转一般通过显式intent和隐式intent两种方式实现的,而 ...

  9. Android项目解耦--路由框架ARouter源码解析

    前言 上一篇文章Android项目解耦–路由框架ARouter的使用讲述了ARouter在项目中的使用,这边文章主要对ARouter的源码进行学习和分析. ARouter的结构 ARouter主要由三 ...

最新文章

  1. maven添加oracle jdbc依赖
  2. python处理数据的优势-Python处理excel的优点
  3. 怎么将ppt转为pdf?
  4. RSA签名算法 - Java加密与安全
  5. iphone小圆点在哪儿设置_iPhone终于自带长截屏了?苹果手机这些截图方式,你用过几种?...
  6. Python2.x(3.x)安装及Ulipad的安装和使用
  7. nexrcloud 自动上传_Nextcloud默认文件的自定义
  8. IDEA 中 project窗口,不显示项目工程目录,解决方法
  9. 2015年全部企业校园招聘情况+薪资水平!
  10. 设置vs工程中的宏参数
  11. 新一代区块链手机“甲骨文”即将上市
  12. 计算机秋招必备!杭州互联网大厂企业整理清单!
  13. python期末试题汇总
  14. 计算机组装师分为哪几步,电脑组装主要需要学习哪几个方面?难学吗?
  15. IDC机房有哪些设备?如何组建中小企业IDC机房?
  16. C语言基础 初识c语言
  17. 数据库无法打开的原因及解决办法
  18. 2021OpenInfra年度报告摘要:OpenInfra在中国
  19. 物联网RFID体系架构
  20. 单机dnf正在连接服务器,win7系统dnf正在连接服务器的解决方法

热门文章

  1. nats需要消息服务器吗,浅谈NATS消息系统
  2. 基于Java+SpringMVC+MySql+Layui+H5网站后台管理系统
  3. 论网站后台管理系统的重要性
  4. 最近的感悟的几个学习小技巧
  5. iOS prepare for resign
  6. React 三端框架 IceE 快速使用
  7. 解决office3件套软件,下载插件---应用商店无法打开的问题
  8. 兰台内外杂志兰台内外杂志社兰台内外编辑部2023年第3期目录
  9. SQl复杂一点的查询
  10. arcgis api for js之设置地图范围extent