Arouter frame structure
There are annotation definitions and annotation processor related contents in arouter framework. Arouter itself can also be regarded as an example.
Arouter API initializes arouter
The Arouter framework uses static annotation processing. In order to adapt to multiple modules, it uses the moduleName suffix to generate a set of registration classes with unified rules. These registration classes are distributed within their respective modules. A management class is needed to aggregate them to provide a unified registration and call entry.
Initialization entry
The integration of the Arouter routing framework calls for initialization of the Arouter framework in the Application initialization process by calling the following method.
ARouter.init(sInstance);
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; }
Dynamic scan route registration class
The routing table is initialized in logisticscenter init(mContext, executor); Complete in. Note the following judgment:
ARouter.debuggable() || PackageUtils.isNewVersion(context). The routing table will be updated only after debugging or updating the app version. The scanned routing file list is saved in SharedPreference.
- registerByPlugin is com alibaba. The arouter plug-in flag indicates whether the routing table registration has been carried out in the compilation stage. Skip it directly;
- Use ClassUtils to scan all class files in the package(com.alibaba.android.arouter.routes) - because the routes of all modules are created in this package path;
- Generate instances of all scanned files according to the rules and register them in the manager Warehouse;
/** * LogisticsCenter init, load all metas in memory. Demand initialization */ public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { // With deletion loadRouterMap(); if (!registerByPlugin) { Set<String> routerMap; if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) { 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 { routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>())); } for (String className : routerMap) { if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) { ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) { ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) { ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex); } } } }
Using the arouter register plug-in
Arouter register is the implementation of AutoRegister plug-in in arouter framework. Its main purpose is to complete the initialization of routing table in the compilation stage and reduce the time-consuming of arouter initialization.
Routing file and initialization class scanning
@Override void transform(Context context, Collection<TransformInput> inputs , Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { // With deletion boolean leftSlash = File.separator == '/' if (!isIncremental){ outputProvider.deleteAll() } inputs.each { TransformInput input -> // scan all jars input.jarInputs.each { JarInput jarInput -> String destName = jarInput.name // rename jar files def hexName = DigestUtils.md5Hex(jarInput.file.absolutePath) if (destName.endsWith(".jar")) { destName = destName.substring(0, destName.length() - 4) } // input file File src = jarInput.file // output file File dest = outputProvider.getContentLocation(destName + "_" + hexName, jarInput.contentTypes, jarInput.scopes, Format.JAR) //scan jar file to find classes if (ScanUtil.shouldProcessPreDexJar(src.absolutePath)) { ScanUtil.scanJar(src, dest) } FileUtils.copyFile(src, dest) } // scan class files input.directoryInputs.each { DirectoryInput directoryInput -> File dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY) String root = directoryInput.file.absolutePath if (!root.endsWith(File.separator)) root += File.separator directoryInput.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 dest FileUtils.copyDirectory(directoryInput.file, dest) } } if (fileContainsInitClass) { registerList.each { ext -> if (!ext.classList.isEmpty()) { ext.classList.each { Logger.i(it) } RegisterCodeGenerator.insertInitCodeTo(ext) } } } } 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) { // com/alibaba/android/arouter/core/LogisticsCenter RegisterTransform.fileContainsInitClass = destFile } } file.close() } } /** * scan class file * @param class file */ 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) { //fix repeated inject init code when Multi-channel packaging if (!ext.classList.contains(name)) { ext.classList.add(name) } } } } } } }
Target file bytecode operation
Through the above scanning operation: get the path to the class generated by Arouter framework and store it in registertransform The ScanSetting object in registerlist and the file where the Arouter API initialization class LogisticsCenter is located are controlled by registertransform Filecontainsinitclass holds. After the scan is completed, call RegisterCodeGenerator.. Insertinitcodeto (EXT), traversing registertransform The ScanSetting object in registerlist is used as input to read and write the file where the LogisticsCenter class is located.
// Reading and writing process: create a temporary file optJar, read data from the source file jarFile, and transfer it to the temporary optJar file. After completion, use optJar to overwrite the source file jarFile. 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) { 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 } // Find COM / Alibaba / Android / arouter / core / logisticscenter Class, call this method private 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() } // In logisticscenter Class to find the loadRouterMap method 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) } @Override MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions) //generate code into this method if (name == ScanSetting.GENERATE_TO_METHOD_NAME) { mv = new RouteMethodVisitor(Opcodes.ASM5, mv) } return mv } } // Insert statement into loadRouterMap: register (classname) // The register function is defined in the LogisticsCenter. It is used to generate an instance of a given class name and register it in the Warehouse manager. class RouteMethodVisitor extends MethodVisitor { RouteMethodVisitor(int api, MethodVisitor mv) { super(api, mv) } @Override void visitInsn(int opcode) { //generate code before return if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) { extension.classList.each { name -> name = name.replaceAll("/", ".") mv.visitLdcInsn(name)//Class 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) } @Override void visitMaxs(int maxStack, int maxLocals) { super.visitMaxs(maxStack + 4, maxLocals) } }
Arouter register and arouter API initialize arouter
Arouter register is an implementation of AutoRegister. You can refer to it.
- During runtime, arouter API performs initialization operations by scanning class files.
- Arouter-register scans jar files and at the end of the compilation phase Class file, find the route registration class in logisticscenter Insert logisticscenter. In the class#loadRouterMap() method The class #register (classname) call statement enables loadRouterMap to directly complete route registration, saving the time of runtime scanning.
- Arouter register works on the basis of arouter API. Both register(className) and loadRouterMap() are provided by arouter API library.
Method records used in plug-ins
JarFile
- file. Entries() - > enumeration: packaged in jar file class file collection
- enumeration. Nexterelement() - > jarentry: packaged in jar file class file
Class access
Use classvisitor, ClassReader and ClassWriter pairs class files are read and written in this group of classes.
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() } 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() } static class MyClassVisitor extends ClassVisitor { ScanClassVisitor(int api, ClassVisitor cv) { super(api, cv) } @Override void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces) } @Override MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions) //generate code into this method if (name == ScanSetting.GENERATE_TO_METHOD_NAME) { mv = new RouteMethodVisitor(Opcodes.ASM5, mv) } return mv } }
Method access
Use MethodVisitor to read and write class methods and insert code.
class RouteMethodVisitor extends MethodVisitor { RouteMethodVisitor(int api, MethodVisitor mv) { super(api, mv) } @Override void visitInsn(int opcode) { super.visitInsn(opcode) } @Override void visitMaxs(int maxStack, int maxLocals) { super.visitMaxs(maxStack + 4, maxLocals) } }
ASM Library
Arouter register plug-in is implemented based on bytecode stake technology. ASM is a plug-in framework, which is integrated in com android. tools. Build: in gradle, the following table has been deleted.
- Byte code pile insertion technology
- ASM: a framework implementation of bytecode instrumentation
\--- com.android.tools.build:gradle:2.1.3 \--- com.android.tools.build:gradle-core:2.1.3 +--- com.android.tools.build:builder:2.1.3 | +--- org.ow2.asm:asm:5.0.3 | \--- org.ow2.asm:asm-tree:5.0.3 | \--- org.ow2.asm:asm:5.0.3 +--- org.ow2.asm:asm:5.0.3 +--- org.ow2.asm:asm-commons:5.0.3 | \--- org.ow2.asm:asm-tree:5.0.3 (*) +--- net.sf.proguard:proguard-gradle:5.2.1 | \--- net.sf.proguard:proguard-base:5.2.1 +--- org.jacoco:org.jacoco.core:0.7.6.201602180812 | \--- org.ow2.asm:asm-debug-all:5.0.4 \--- org.antlr:antlr:3.5.2 +--- org.antlr:antlr-runtime:3.5.2 \--- org.antlr:ST4:4.0.8 (*)
This article is transferred from https://juejin.cn/post/7044826883719954469 , in case of infringement, please contact to delete.