Arouter frame structure

The Arouter framework contains annotation definitions and annotation handlers. See # Define Annotations for a description and implementation example. Arouter itself could be an example.

Arouter – API initializes arouter

Static annotation processing, used by the Arouter framework, generates a uniform set of registered classes using the moduleName suffix to accommodate multiple modules. These registration classes are distributed within their respective Modules, and a management class is required to aggregate them together and provide a unified entry point for registration and invocation.

Initialization entry

To integrate the Arouter routing framework, call the following method during Application initialization to initialize the Arouter framework.

ARouter.init(sInstance);
Copy the code
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;
}
Copy the code

Dynamic scan route registration class

The routing table is initialized in logisticScenter. init(mContext, executor); In the finish. Note the following judgment:

ARouter. Debuggable () | | PackageUtils. IsNewVersion (context), under the condition of the debug or update the app version will update the routing tables, routing file list in SharedPreference save scan.

  • RegisterByPlugin is the com.alibaba. Arouter plugin flag that indicates whether the routing table has been registered at compile time.
  • Using ClassUtils scanning package (com. Alibaba. Android. Arouter. Routes) in all the class files – because all of the routing module created in this package path;
  • All the scanned files, according to the rules to generate instances, registered to the manager Warehouse;
/** * LogisticsCenter init, load all metas in memory. Demand initialization */
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    / / a cut
    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); }}}}Copy the code

Use the arouter-register plug-in

Arouter- Register is the implementation of the AutoRegister plug-in in the Arouter framework. The main purpose is to complete the initialization operation of the routing table during compilation and reduce the Arouter initialization time.

  • Arouter framework generated class package path: com. Alibaba. Android. Arouter
  • com.android.build.api.transform.TransForm

Routing file and initialization class scan

@Override
void transform(Context context, Collection<TransformInput> inputs , Collection<TransformInput> referencedInputs,
               TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {

    / / a cut
    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()
}

Copy the code
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) } } } } } } }Copy the code

Object file bytecode operation

Through the scan operation above: Get Arouter framework generated class path stored in RegisterTransform. RegisterList ScanSetting of object, Initialization and arouter – API classes in the LogisticsCenter file by RegisterTransform. FileContainsInitClass hold. After the scan is complete, call RegisterCodeGenerator. InsertInitCodeTo (ext), traversing RegisterTransform. RegisterList ScanSetting in object as input, Read and write to the file where the LogisticsCenter class resides.

// Create temporary file optJar, read data from source file jarFile, write data to temporary optJar file, use optJar to overwrite 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()
}

// Look for the loadRouterMap method in logisticScenter. class
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 to loadRouterMap: Retister (className)
// The register function is defined in LogisticsCenter to generate an instance of the given class name and register it with 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)/ / the name of the class
                // 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)
    }
}
Copy the code

Arouter-register and arouter-API initialize arouter

Arouter- Register is an implementation of AutoRegister, which can be analyzed using the # AutoRegister framework.

  • Arouter- API performs initialization operations by scanning class files at run time.
  • At the end of the compile phase, arouter-register scans jar files and.class files for the route registration classes, Logisticscenter. class#register(className) is inserted into the logisticscenter. class#loadRouterMap() method so that the loadRouterMap can register routes directly. Saves runtime scanning time.
  • Arouter-register works on top of arouter-API, with both register(className) and loadRouterMap() provided by the arouter-API library.

Records of methods used in plug-ins

JarFile

  • File.entries () -> Enumeration: Collection of.class files packed in Jar files
  • Enumeration.nextelement () -> JarEntry: the.class file packaged in the Jar file

Such access

All reading and writing to.class files using ClassVisiter, ClassReader, and ClassWriter are 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
    }
}
Copy the code

Methods to access

Class methods are read and written using MethodVisitor, where code can be inserted.

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)
    }
}
Copy the code

ASM library

Arouter- Register plug-in, is based on plug-in technology and ASM framework implementation. The ASM framework integration in com. Android. View the build: gradle, has cut under the table.

Tool reference in android.tools.build

\ - com android. Tools. Build: gradle: 2.1.3 \ - com android. View the 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 (*)Copy the code