当前位置:网站首页>Arouter framework analysis

Arouter framework analysis

2022-06-23 21:22:00 User 9227784

Arouter Frame structure

Arouter There are annotation definitions and annotation processor related contents in the framework structure ,Arouter It can also be regarded as an example .

arouter-api Yes Arouter initialization

Arouter Static annotation processing used by the framework , To adapt to multiple modules , Use moduleName Suffixes generate a set of registration classes with uniform rules . These registration classes are distributed in their respective module Inside , You need a management class to aggregate them together , Provide unified registration and call entry .

Initialization entry

Integrate Arouter Routing framework , Need to be in Application The following method is called during initialization. Arouter The framework is initialized .

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 scanning route registration class

The routing table is initialized in LogisticsCenter.init(mContext, executor); Finish in . We need to pay attention to the following judgment :

ARouter.debuggable() || PackageUtils.isNewVersion(context), stay debug Or update app The routing table will only be updated under the condition of version , The list of scanned routing files is in SharedPreference Kept in .

  • registerByPlugin yes com.alibaba.arouter Plug in tags , Indicate whether the routing table has been registered in the compilation stage , Just skip ;
  • Use ClassUtils scanning package(com.alibaba.android.arouter.routes) All class files in - Because the routes of all modules are created in this packet path ;
  • Put all the scanned files , Generate instances according to rules , Register with manager Warehouse in ;
/**
 * 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);
            }
        }
    }
}

Use arouter-register plug-in unit

Arouter-register yes AutoRegister Plug in Arouter Implementation in framework , The main purpose is to complete the initialization of the routing table in the compilation stage , Reduce Arouter Initialization time .

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

Object file bytecode operation

Through the above scanning operation : Get Arouter The path of the framework generated class is stored in RegisterTransform.registerList Medium ScanSetting In the object , as well as arouter-api Initialization class LogisticsCenter Where the document is located RegisterTransform.fileContainsInitClass hold . After scanning , call RegisterCodeGenerator.insertInitCodeTo(ext) , Traverse RegisterTransform.registerList Medium ScanSetting Object as input , Yes LogisticsCenter Read and write operations of the file where the class is located .

//  Reading and writing process : Create temporary file optJar, From the source file jarFile Read data from , Transcribe to temporary optJar In file , Use... When finished optJar 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()
}

//  stay LogisticsCenter.class Search for  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
    }
}

//  towards  loadRouterMap  Insert statement :retister(className)
// register Function in  LogisticsCenter  In the definition of , Used to generate an instance of a given class name , Sign up to  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 initialization Arouter

Arouter-register yes AutoRegister An implementation of , You can refer to \# AutoRegister Frame analysis .

  • Arouter-api At run time , Initialize by scanning class files .
  • Arouter-register At the end of the compilation phase , scanning jar Document and .class The file found the route registration class , stay LogisticsCenter.class#loadRouterMap() Method LogisticsCenter.class#register(className) Call statement , bring loadRouterMap You can complete the route registration directly , Saves runtime scanning time .
  • Arouter-register Working in Arouter-api On the basis of ,register(className) and loadRouterMap() All by Arouter-api Library provides .

The method used in the plug-in records

JarFile

  • file.entries() -> Enumeration : Jar Packaged in a file .class File set
  • enumeration.nextElement() -> JarEntry : Jar Packaged in a file .class file

Such access

Use ClassVisiter、ClassReader、ClassWriter Yes .class Read and write operation of the file , All 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 Read and write class methods , You can 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 unit , It is based on bytecode stake insertion technology .ASM It is a pile inserted frame , The framework is integrated in com.android.tools.build:gradle Inside , The following table has been deleted .

\--- 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 (*)

Related courses

Android Basic series of tutorials :

Android basic course U- Summary _ Bili, Bili _bilibili

Android basic course UI- Layout _ Bili, Bili _bilibili

Android basic course UI- Control _ Bili, Bili _bilibili

Android basic course UI- Animation _ Bili, Bili _bilibili

Android basic course -activity Use _ Bili, Bili _bilibili

Android basic course -Fragment Usage method _ Bili, Bili _bilibili

Android basic course - Hot repair / The principle of thermal renewal technology _ Bili, Bili _bilibili

In this paper, from https://juejin.cn/post/7044826883719954469, If there is any infringement , Please contact to delete .

原网站

版权声明
本文为[User 9227784]所创,转载请带上原文链接,感谢
https://yzsam.com/2021/12/202112251051051953.html