ARouter系列文章:

“总算懂了” 系列:组件化结构 ARouter 彻底解析(一)原理全解

“总算懂了” 系列:组件化结构 ARouter 彻底解析(二)APT—协助类生成

“总算懂了” 系列:组件化结构 ARouter 彻底解析(三)AGP/Transform—动态代码注入

在本系列前两篇中,介绍了ARouter的中心原理、ARouter中运用的APT技能,了解了协助类的效果和编译期生成协助类的原理。其间原理篇中说到了AGP办法加载路由——在编译时进行扫描并动态在LogisticsCenter#loadRouterMap()中刺进了代码。本篇就来介绍ARouter是怎样搜集协助类信息怎样动态注入代码,以及涉及到的AGPTrasformASM

一、Arouter中的中心技能

1.1 Gradle

先来了解Android开发必定要接触到的Gradle

  • 一个主动化构建结构
  • 在Android开发中,用来编译打包输出apk,还能够用来增加三方依靠

Gradle官网中的介绍: Gradle是一种开源构建主动化东西,满足灵敏,能够构建几乎任何类型的软件。Gradle对您要构建什么或怎样构建它几乎没有任何约束

也便是说,Gradle这个构建东西,能够被用来构建任何东西,详细怎样构建也是由运用者拟定。 在Android领域,AndroidStudio中的项目默认会引进Gradle,便是被用来构建Android运用包——apk文件。

那么Google是怎样拟定Apk构建流程的呢? 经过上面了解知道,AndroidStudio中只要Gradle还不够,还得告诉它怎样构建。咱们知道Apk打包流程包括很多环节:源码文件经过JavaCompiler转为class文件、class经过dex操作变为dex文件、dex和资源等打包成apk、签名 等等一系列步骤,这就需求Google运用Gradle把这些操作都串联起来。

“终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM—动态代码注入

在项目根目录的build.gradle文件中的这一句,就会引进「Google运用Gradle拟定的这一系列打包流程」,也便是Google自界说的各种Gradle 的使命。每个使命担任某个详细的作业,按照依靠联系排好履行顺序,先后履行,最终输出Apk文件。

在AndroidStudio的Terminal中,输入打包指令:./gradlew app:assembleDebug --console=plain,输出如下:

:app:preBuild UP-TO-DATE
...
:app:compileDebugJavaWithJavac
...
:app:transformClassesWithDexBuilderForDebug
...
:app:packageDebug
:app:assembleDebug

这些便是Google官方运用Gradle界说的相互有依靠联系的各种使命,这些使命履行完就完结了Apk的打包。

这些使命是怎样被创立的呢?在app模块和arouter-api模块的build.gradle文件中,榜首句别离是

//app的build.gradle
apply plugin: 'com.android.application'
//arouter-api的build.gradle
apply plugin: 'com.android.library'

意思是,app引进了名为com.android.application的gradle插件、arouter-api引进的是名为com.android.library的gradle插件。上面介绍的各种使命便是在这两种插件中创立的,引进了插件,也就引进了插件对应的使命。

界说了各种gradle使命,为啥还要界说gradle插件呢?一是能够对使命进行办理,例如排好使命的依靠和履行顺序;二是能够复用,模块引进某个插件,就具有了对应的才干。即gradle插件便是 将构建逻辑的可重用部分封装起来,能够运用到不同的项目和构建中。

gradle中,除了使命(Task)、插件(Plugin),还有一个概念——Project,共同组成了gradle的中心内容:

  • Project:Gradle工程,Project包括一个构建脚本,通常名为build.gradle。构建脚本为该 Project界说Task、依靠项、plugin和其他装备。一个Android工程以及每个Android module都是一个Project (都有一个build.gradle文件),一个build.gradle文件编译后便是一个Project方针
  • Task:Gradle使命,包括履行某些作业的逻辑,Task自身包括:
    • 动作(Actions):详细某中的操作,例如仿制文件或编译源代码
    • 输入(Inputs):操作运用的值、文件、目录
    • 输出(Outputs):操作修正或生成的文件、目录
  • Plugin:Gradle插件,用于创立和封装Task,介入编译构建过重要达到扩展功用目的。Plugin使得相同逻辑和装备能够被多个Project复用

再来看下Gradle的生命周期,有三个阶段:

  • 初始化:承认哪些Project将参加构建。即读取settings.gradle文件中的include的一切模块
  • 装备:承认参加构建的一切使命及其顺序。读取参加构建的Project的build.gradle文件,承认需求运转哪些Task以及承认Task间依靠联系和履行顺序(包括直接在build.gradle中界说的Task以及引进的Plugin中界说的Task)
  • 履行:运转在装备阶段承认的Task,这儿是真实履行一切的打包操作

以上对Gradle的基本认知非常重要,足以了解本篇内容。至于Gradle脚本编写、Task和Plugin详细用法这种固定知识点可自行到Gradle官网学习即可,这儿不再转移。

1.2 Android Gradle Plugin

AGP(Android Gradle Plugin),即 Android Gradle 插件,是Android开发中运用的Gradle插件。

上面说到的,在项目根目录的build.gradle文件中引进了com.android.tools.build:gradle:4.0.0,也就引进了 Google官方写好的两个Gradle插件:com.android.applicationcom.android.library,这两个插件内部会创立各种Task用于完结Apk编译打包流程。

来看一个形象的比方:Gradle编译打包流程和工厂经过主动化流水线来出产流程:

工厂(玩具工厂等) 开发东西(AndroidStudio等)
厂长 程序员
流水线 gradle
流水线中的一个机器 gradle中的一个插件
原材料 java/kotln文件、资源文件等
各作业台的操作:清洗、拼装、包装 打包流程:javac、APT、转dex、打包apk
流水线新加入一个机器:给原材料贴标签 gradle中新增一个APG:遍历dex并动态刺进代码

“终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM—动态代码注入

Gradle就像是工厂的流水线一样,担任界说流程和规矩,而详细的作业都是经过插件(及其包括的Task)完结的。假如想在流水线上增加一个操作,例如给原材料贴标签,那么就在最开头的方位增加一个贴标签的机器即可。对应的,想要在编译进程中动态刺进代码,咱们就需求新增一个AGP。

本来只要Google官方界说好的AGP,现在需求做的便是咱们自界说一个插件,而且插在编译进程中合适的方位上。这便是本篇中要介绍的要点内容——ARouter用来在编译阶段搜集协助类信息/动态注入代码的AGP是怎样完结的。

1.3 Transform

搜集协助类信息、动态注入代码,要在编译进程的什么机遇履行呢? 这个机遇要能够搜集到一切协助类(包括依靠的三方AAR中的)而且还能够向方针文件中刺进代码。

在官方界说的AGP中供给了这样一个机遇,如下打包流程图中箭头方位:

“终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM—动态代码注入

在class文件转为dex文件之前,这个节点能够拿到参加构建的一切class文件,包括本项目java文件/kotln文件经javac出产的class文件和依靠的第三方库中的class文件。

代码上怎样运用拿到这个机遇呢?Android官方的AGP中供给了一个API——Transform

“终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM—动态代码注入

咱们需求自界说Transform,在Transform的transform办法中就能够获取到一切的class文件。

  • transform办法被调用的机遇便是在class被打进dex文件之前
  • 需求在创立的gradle插件中注册自界说的Transform
  • 事实上每增加一个Transform,在Grade的装备阶段就会创立一个gradle Task

显然,在Transform的transform办法中,就能够拿到一切的class文件,咱们关心的是一切的协助类和LogisticsCenter类。

1.4 ASM

在Transform的中拿到一切class文件后,怎样辨认协助类和LogisticsCenter类呢?辨认后怎样在LogisticsCenter类中刺进代码呢? 如下图这样:

“终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM—动态代码注入

上图是class文件反编译后的样子,咱们能够看懂。 但假如运用文本软件直接打开class文件,基本便是看不懂的十六进制内容:

“终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM—动态代码注入

假如想要辨认出是哪个类、或是要向中心某个方位刺进一段代码,这是基本不可能了。咱们知道class文件又称为字节码文件,能够运用javap指令把class文件转为大约能看懂的字节码文件(或者直接运用AndroidStudio插件ASM Bytecode Viewer来检查):

“终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM—动态代码注入

右侧便是字节码指令以及Class文件版别号、常量池、拜访标识、变量表、指令代码行号表等信息(能够参考字节码,检查每个指令的意义),这儿咱们肉眼能够辨认类名和办法名。

在Transform的中拿到了class文件后,想要辨认类信息而且还能修正内容,这个在代码上怎样完结先呢?办法便是ASM。

ASM:字节码操作结构,是一种基于java字节码层面的代码剖析和修正东西,ASM的方针是生成、转化、剖析已编译的java class文件,可运用ASM东西读、写、转化JVM指令集。浅显点讲便是来处理javac编译之后的class文件。

ASM经过拜访者形式依次遍历class字节码中的各个部分(特点、特点、注解等),并不断回调到上层(这有点像SAX解析xml的进程),上层可在业务关心的某个拜访点,修正原有逻辑。之所以能够这么做,是因为java字节码是按照严厉的JVM标准生成的二进制字节流,ASM是照这个标准对java字节码的一次解说,将不流畅难懂的字节码背面对应的JVM指令一条条的转化成ASM API。其间中心类如下:

  • ClassReader:用于读取现已编译好的class文件,读取到内容(特点/办法等)后会调用ClassWriter对应的办法
  • ClassWriter:是ClassVisitor的子类,首要担任将ClassVisitor处理好后的class转化成byte数组,有了byte数组就能经过OutputStream写入文件,也就完结了对class文件的修正
  • ClassVisitor:class文件拜访者,可用于修正类型西,如修正类名、特点、办法,也可生成类的class文件。对于字节码文件中不同的区域有不同的Visitor:用于拜访办法的 MethodVisitor、用于拜访类特点的FieldVisitor、用于拜访注解的AnnotationVisitor
  • MethodVisitor:是ASM中最为重要的类,它是对帧栈中「操作数栈」进行操作的中心类,无论完结什么功用都离不开对操作数栈的操作

运用ASM辨认一个类,详细怎样操作呢?

/**
 * 扫描class
 * @param inputStream class文件的输入流(从transform中获取的)
 */
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 {
    ...
    //该办法首要供给对java版别,类权限拜访符、全限定名、泛型、父类、接口相关信息
    void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces)
        ...
    }
}

以上是读取/扫描 class文件的固定写法,要点是自定的ClassVisitor——ScanClassVisitor类,在它的visit()办法中能够获取到 类名、类完结的接口等等。经过比照 类名、接口等信息,就能承认是否是咱们要找的协助类了。

运用ASM在类办法刺进代码,详细怎样操作呢?

/**
 * 修正class办法
 * @param inputStream class文件的输入流(从transform中获取的)
 */
private byte[] handleClassMethod(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 {
    ...
    @Override
    MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions)
        //要刺进代码的方针办法method 被拜访到
        if (name == method) {
            mv = new MyMethodVisitor(Opcodes.ASM5, mv)
        }
        return mv
    }
}
class MyMethodVisitor extends MethodVisitor {
    ...
    //visitInsn,履行零操作指令,意味着操作码后无需跟任何字节的操作数,例如下面if种判断的return指令
    @Override
    void visitInsn(int opcode) {
        //保证在办法return之前刺进代码
        if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
            ...//此处是需求写自己的刺进代码的逻辑
        }
        super.visitInsn(opcode)//刺进代码之后再走return
    }
    ...
}

以上是在class文件办法中刺进代码的固定写法,要点是自定的MethodVisitor——MyMethodVisitor,visitInsn()办法中保证在办法return之前刺进代码。那么怎样写刺进代码的逻辑呢? 有三种办法:

  1. 假如你对字节码指令比较熟,能够直接调用MethodVisitor的API来写字节码指令
  2. 也能够运用javap指令检查要刺进的代码的字节码形式,然后运用ASM的API来写字节指令。
  3. 最简略的,运用AndroidStudio插件ASM Bytecode Viewer,能够协助咱们检查字节码,并直接生成ASM代码。这样 要刺进的代码 经过 AndroidStudio插件 就能够直接生成ASM API代码了,咱们把ASM代码仿制到MyMethodVisitor中即可(能够比照刺进代码前后的ASM代码,仿制新增的部分即可):

LogisticsCenter的loadRouterMap()检查字节码文件和对应的ASM字节码文件:

“终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM—动态代码注入

“终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM—动态代码注入

在办法return前刺进代码register("hufeiyang");,再来检查不同:

“终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM—动态代码注入

“终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM—动态代码注入

可见ASM字节码的差异便是这两句:

methodVisitor.visitLdcInsn("hufeiyang");
methodVisitor.visitMethodInsn(INVOKESTATIC, "com/alibaba/android/arouter/core/LogisticsCenter", "register", "(Ljava/lang/String;)V", false);

这两句便是register("hufeiyang");的ASM代码的翻译了,就能够在MyMethodVisitor的visitInsn()办法中运用了,也就完结了 在类办法刺进代码 这一操作了。

好了,到这儿就完结了一切的铺垫,介绍了Gradle、AGP、Transform、ASM,每个知识点都是以了解主,要能清楚的每个概念和效果。更深化地把握需求针对性的去学习,本篇是以了解和看懂ARouter为方针进行介绍的。

二、协助类的搜集

Arouter项目中,有一个Gradle插件模块,便是运用了上面那介绍的技能点来完结 协助类的搜集路由加载代码刺进

“终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM—动态代码注入

中心类效果如下:

  • PluginLaunch,自界说Gradle 插件,并注册自界说的Transform
  • RegisterTransform,自界说Transform,在class文件被打进dex文件之前拿到一切的class文件
  • ScanUtil,运用ASM对源码出产class文件和三方jar中的class文件进行扫描,找到一切协助类
  • RegisterCodeGenerator,找到类 LogisticsCenter,运用ASM完结路由加载代码的刺进

先来看协助类的搜集进程,也便是PluginLaunch、RegisterTransform、ScanUtil这三个类。

//ARouter主动注册插件
public class PluginLaunch implements Plugin<Project> {
    //apply()办法,对应着 "apply plugin:",即在gradle的装备阶段就会履行,把RegisterTransform这个task挂接到编译流程中
    @Override
    public void apply(Project project) {
        def isApp = project.plugins.hasPlugin(AppPlugin)
        //只要application模块才需求本插件去生成注册代码
        if (isApp) {
            Logger.make(project)
            Logger.i('Project enable arouter-register plugin')
            def android = project.extensions.getByType(AppExtension)
            def transformImpl = new RegisterTransform(project)
            //要扫描的接口:根协助类接口、拦截器协助类接口、服务协助类接口
            ArrayList<ScanSetting> list = new ArrayList<>(3)
            list.add(new ScanSetting('IRouteRoot'))
            list.add(new ScanSetting('IInterceptorGroup'))
            list.add(new ScanSetting('IProviderGroup'))
            RegisterTransform.registerList = list
            //注册transform
            android.registerTransform(transformImpl)
        }
    }
}

PluginLaunch,便是自界说的Gradle插件(榜首篇中有介绍过在运用ARouter结构的app模块中需求引进此插件),然后注册了自界说的RegisterTransform。RegisterTransform中的registerList保存着的是各个协助类的接口。

//1. 扫描class找到 完结了指定接口的class
//2. 生成[协助类注册代码]刺进到 LogisticsCenter
class RegisterTransform extends Transform {
    Project project
    //用于存各个协助类接口及对应的一切协助类
    static ArrayList<ScanSetting> registerList
    //包括LogisticsCenter.class的jar文件
    static File fileContainsInitClass;
    ...
    //transform办法,会在gradle的履行阶段履行(Transform自身会被封装在一个Task中-TransformTask)
    //履行阶段详细什么时候呢?--源码生成class文件之后,class被打进dex文件之前
    @Override
    void transform(Context context, Collection<TransformInput> inputs
                   , Collection<TransformInput> referencedInputs
                   , TransformOutputProvider outputProvider
                   , boolean isIncremental) throws IOException, TransformException, InterruptedException {
        ...
        inputs.each { TransformInput input ->
            //一、扫描悉数jar(三方库中的class文件)
            input.jarInputs.each { JarInput jarInput ->
                String destName = jarInput.name
                //重命名jar文件
                def hexName = DigestUtils.md5Hex(jarInput.file.absolutePath)
                if (destName.endsWith(".jar")) {
                    destName = destName.substring(0, destName.length() - 4)
                }
                File src = jarInput.file
                File dest = outputProvider.getContentLocation(destName + "_" + hexName, jarInput.contentTypes, jarInput.scopes, Format.JAR)
                if (ScanUtil.shouldProcessPreDexJar(src.absolutePath)) {
                    //-----扫描jar以找到方针class文件 begin----
                    ScanUtil.scanJar(src, dest)
                    //-----扫描jar以找到方针class文件 end ----
                }
                FileUtils.copyFile(src, dest)
            }
            //二、扫描悉数目录(源码生成的class文件)
            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("\\", "/")
                    }
                    //-----扫描目录以找到方针包途径的class文件 begin----
                    //类是在arouter APT生成类的包,'com/alibaba/android/arouter/routes/'
                    if(file.isFile() && ScanUtil.shouldProcessClass(path)){
                        ScanUtil.scanClass(file)
                    }
                    //-----扫描目录以找到方针包途径的class文件 end----
                }
                FileUtils.copyDirectory(directoryInput.file, dest)
            }
        }
        //扫描完毕,拿到生成的一切方针协助类(APT生成的IRouteRoot、IInterceptorGroup、IProviderGroup的完结类)
        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 {
                    //在 LogisticsCenter#loadRouterMap()中刺进代码
                    RegisterCodeGenerator.insertInitCodeTo(ext)
                }
            }
        }
    }
}

RegisterTransform是自界说的Transform,transform办法会在gradle的履行阶段履行——源码生成class文件之后、class被打进dex文件之前。详细逻辑便是 扫描悉数jar(三方库中的class文件)、扫描悉数目录(源码生成的class文件),拿到一切方针协助类,在 LogisticsCenter#loadRouterMap()中刺进代码。详细的扫描进程是运用ScanUtil类,刺进代码是RegisterCodeGenerator类。

class ScanUtil {
    //扫描jar
    static void scanJar(File jarFile, File destFile) {
        if (jarFile) {
            def file = new JarFile(jarFile)
            Enumeration enumeration = file.entries()
            while (enumeration.hasMoreElements()) { //遍历jar里面的一切class文件
                JarEntry jarEntry = (JarEntry) enumeration.nextElement()
                String entryName = jarEntry.getName()
                if (entryName.startsWith(ScanSetting.ROUTER_CLASS_PACKAGE_NAME)) {//找到包名是'com/alibaba/android/arouter/routes/'的class
                    InputStream inputStream = file.getInputStream(jarEntry)
                    scanClass(inputStream) //也是走scanClass办法
                    inputStream.close()
                } else if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {//LogisticsCenter,物流中心,是在jar中
                    // 记录包括 LogisticsCenter.class 的jar文件,扫描完结后会向其间刺进代码
                    RegisterTransform.fileContainsInitClass = destFile
                }
            }
            file.close()
        }
    }
...
    //扫描class文件
    static void scanClass(File file) {
        scanClass(new FileInputStream(file))
    }
    //扫描class,inputStream class文件的输入流(从transform中获取的)
    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 {
        ...
        //该办法首要供给对java版别,类权限拜访符、全限定名、泛型、父类、接口相关信息
        void visit(int version, int access, String name, String signature,String superName, String[] interfaces) {
            super.visit(version, access, name, signature, superName, interfaces)
            //按类完结的接口,别离存到 RegisterTransform.registerList对应ScanSetting#classList中
            RegisterTransform.registerList.each { ext ->
                if (ext.interfaceName && interfaces != null) {
                    interfaces.each { itName ->
                        if (itName == ext.interfaceName) {
                            if (!ext.classList.contains(name)) {
                                ext.classList.add(name)
                            }
                        }
                    }
                }
            }
        }
    }
}

无论是jar仍是目录,都是找到包名是com/alibaba/android/arouter/routes/的class文件,然后经过class文件的输入流,运用ASM读取class文件内容,再自界说ScanClassVisitor在visit()办法,按类完结的接口找到一切的协助类,存到 RegisterTransform.registerList对应的ScanSetting.classList中。到这儿,一切的协助类都现已找到了。

三、动态代码注入

最终来看看代码刺进的进程:

class RegisterCodeGenerator {
    ...
    static void insertInitCodeTo(ScanSetting registerSetting) {
        if (registerSetting != null && !registerSetting.classList.isEmpty()) {
            RegisterCodeGenerator processor = new RegisterCodeGenerator(registerSetting)
            //包括LogisticsCenter.class的jar文件
            File file = RegisterTransform.fileContainsInitClass
            if (file.getName().endsWith('.jar'))
                processor.insertInitCodeIntoJarFile(file)
        }
    }
    //刺进代码到jar
    private File insertInitCodeIntoJarFile(File jarFile) {
        if (jarFile) {
            ...
            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)
                //找到类 LogisticsCenter
                if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {
                    //拿到LogisticsCenter.class文件的输入流,开始插代码!!!
                    def bytes = referHackWhenInit(inputStream)
                    jarOutputStream.write(bytes)
                } else {
                    jarOutputStream.write(IOUtils.toByteArray(inputStream))
                }
                ...
            }
            ...
            optJar.renameTo(jarFile)
        }
        return jarFile
    }
    //运用ASM刺进代码
    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()
    }
    class MyClassVisitor extends ClassVisitor {
        ...
        @Override
        MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions)
            //找到要刺进代码的办法-loadRouterMap
            if (name == ScanSetting.GENERATE_TO_METHOD_NAME) {
                mv = new RouteMethodVisitor(Opcodes.ASM5, mv)
            }
            return mv
        }
    }
    class RouteMethodVisitor extends MethodVisitor {
    ..
        @Override
        void visitInsn(int opcode) {
            //在return之前刺进代码
            if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
                extension.classList.each { name ->
                    name = name.replaceAll("/", ".")//把协助类途径中的"/"换成"."
                    mv.visitLdcInsn(name)//协助类类名
                    //在LogisticsCenter.loadRouterMap()中增加代码:register(name),name便是 根协助类/拦截器协助类/服务协助类 类名
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC
                            , ScanSetting.GENERATE_TO_CLASS_NAME
                            , ScanSetting.REGISTER_METHOD_NAME
                            , "(Ljava/lang/String;)V"
                            , false)
                }
            }
            super.visitInsn(opcode)
        }
        ...
    }
}

首先是拿到jar中LogisticsCenter.class文件的输入流,接着运用ASM自界说MyClassVisitor在visitMethod()办法中找到要刺进代码的办法-loadRouterMap()。然后在自界说RouteMethodVisitor的visitInsn()办法中,保证在return之前刺进代码。遍历一切协助类,把协助类途径中的”/”换成”.” 。最终刺进代码的办法便是前面介绍的运用AndroidStudio插件检查ASM代码的办法。

到这儿,源码也便是整理完了。

四、总结

本篇内容较多,前半部分都是在介绍铺垫知识,只要彻底了解了才干把握ARouter结构的中心。

其间Gradle、AGP是Android开发中必定运用到的技能点,期望经过对ARouter的学习能更加深刻的了解Android编译构建的进程,以及运用Gradle插件能完结哪些作业。

Transform是官方Gradle 插件中供给的API,它能获取和处理被打进dex文件之前的class文件。不过Transform在最新的Gradle插件版别中被抛弃了,不过这不影响咱们咱们对它的学习。

ASM是字节码操作结构,结合AndroidStudio中的ASM代码检查插件,使得咱们能够快捷的修正class文件内容。

在以上内容彻底把握后,你会发现很多著名结构的完结基本都是这些技能点的运用。而且,假如在平时开发中遇到相似的问题,也能够经过这些技能进行解决。例如在我的组件化文章《“总算懂了” 系列:Android组件化,全面把握!》中,说到的AppLifeCycle插件,运用的也是这些技能。所以一个好的结构,深化学习后不仅知道了它的原理,也能把握涉及的到一切技能点,这对技能视界和深度都是一个很大的提高。

到这儿这一系列也要完毕了,中心隔了好久才完结这最终一篇,好在仍是有始有终。

你的 点赞、评论,是对我的巨大鼓舞!

欢迎关注我的大众号胡飞洋,文章更新可榜首时间收到;

假如有问题或者想进群,号内有加我微信的入口,我拉你进技能讨论群。在技能学习、个人成长的道路上,咱们一同行进!