当前位置: 首页 > 工具软件 > DroidAssist > 使用案例 >

滴滴动态插桩DroidAssist源码详解

呼延景同
2023-12-01

先来了解一下这个DroidAssist的能帮我们做什么?都有哪些功能呢?

  • 替换:把指定位置代码替换为指定代码
  • 插入:在指定位置的前后插入指定代码
  • 环绕:在指定位置环绕插入指定代码
  • 增强
    • TryCatch 对指定代码添加 try catch 代码
    • Timing 对指定代码添加耗时统计代码

恩,功能确实挺强大的,咱们可以用它来实现动态的更改方法里的字节码,可以插入指定代码,比如打印信息、耗时操作等,也可以实现留存率的统计功能。当然这些面向AOP编程功能AspectJ也能实现,但是你必须得在代码中声明注解,有侵入性,好处就是学习成本低。怎么在编译时期动态的改变class字节码,常用的技术点就是ASM、javassist,ASM高效学习相对成本高,javassit相对学习成本低,DroidAssist就是采用了javassit来改变字节码。

另一个问题在什么时候改变字节码,android的构建插件com.android.tools.build:gradle在1.5之后除了TransForm的api允许你在class生成dex之前操作字节码,当然它的执行比混淆早(如果加了混淆)。这又涉及到了gradle的插件化开发部明白的请先看我以前写的两篇博客SVG-Android(gradle插件生成器)源码详解https://blog.csdn.net/xiatiandefeiyu/article/details/79020527熟悉一下。

所以说DroidAssist技术主要涉及到Gradle插件、TransForm api和javassit技术,如果我们自己实现的话,我们得制定一个规则例如插入一句代码,在哪插入这就需要用的人来确定,这也就是所谓的规则,把这些规则交给TransForm解析,然后利用javassit修改需要修改的class。利用这些技术我们可以做什么?比如一个问题eventbus想比大家都比较熟悉吧,组件的某个方法想被调用,添加注解,然后根据发送的对象调用,有没有这么一个问题,你写的时间长了,代码越来越多你还会记得哪个组件和哪一个组件交互吗?肯定不会记得找起来是不是很费劲,那么能不能在编译期的时候将所有的交互组件全部注册当文件里,以后查询文件就ok了吗,这又是一种用途。

好了,说了这么多废话现在进入主题,先来看看插件的入口类:

  void apply(Project project) {
        //创建配置文件属性droidAssistOptions
        project.extensions.create("droidAssistOptions", DroidAssistExtension)
        /**
         * 判断是不是主程序,主程序就遍历所有的项目class,lib则只处理lib
         */
        if (project.plugins.hasPlugin(AppPlugin.class)) {
            AppExtension extension = project.extensions.getByType(AppExtension)
            extension.registerTransform(
                    new DroidAssistTransform(project, true))
        }
        if (project.plugins.hasPlugin(LibraryPlugin.class)) {
            LibraryExtension extension = project.extensions.getByType(LibraryExtension)
            extension.registerTransform(
                    new DroidAssistTransform(project, false))
        }
    }

这段代码的意思就是将dsl配置的属性变成对象,并且注册Transform以便修改字节码,如果当前引用插件的项目为apply plugin: 'com.android.application'则配置的DroidAssistTransform的属性标记为true,对DroidAssist用法不熟的请参考DroidAssist的github

继续来看DroidAssistTransform的类的onTransform方法(class的处理都是在这个方法里处理的)

 void onTransform(
            Context gradleContext,
            Collection<TransformInput> inputs,
            Collection<TransformInput> referencedInputs,
            TransformOutputProvider outputProvider,
            boolean isIncremental)
            throws IOException, TransformException, InterruptedException {

        Logger.info("Transform start, " +
                "enable:${gradleExtension.enable}, " +
                "incremental:${isIncremental}")

        //  这个属性为false的时候直接copy输入文件到输出位置,交给下一个Transform处理
        if (!gradleExtension.enable) {
            outputProvider.deleteAll()
            def dirStream = inputs
                    .parallelStream()
                    .flatMap { it.directoryInputs.parallelStream() }
                    .filter { it.file.exists() }

            def jarStream = inputs
                    .parallelStream()
                    .flatMap { it.jarInputs.parallelStream() }
                    .filter { it.file.exists() }

            Stream.concat(dirStream, jarStream).forEach {
                def copy = it.file.isFile() ? "copyFile" : "copyDirectory"
                FileUtils."$copy"(
                        it.file,
                        GradleUtils.getTransformOutputLocation(outputProvider, it))
            }
            return
        }

        def start = System.currentTimeMillis()
        Logger.info("DroidAssist options: ${gradleExtension}")
        def timingLogger = new TimingLogger("Timing", "execute")

        //不是增量更新每次删除旧的输出文件
        if (!isIncremental) {
            outputProvider.deleteAll()
            timingLogger.addSplit("delete output")
        }

        def context =
                new DroidAssistContext(
                        gradleContext,
                        project,
                        gradleExtension,
                        referencedInputs)
        //将需要操作的类都加入到流中
        context.configure()
        timingLogger.addSplit("configure context")
    /**
   * 创建字节码操作执行者
     */
        def executor =
                new DroidAssistExecutor(
                        context,
                        outputProvider,
                        isIncremental)
        timingLogger.addSplit("create executor")

        //Execute all input classed with byte code operation transformers
        executor.execute(inputs)
        timingLogger.addSplit("execute inputs")

        timingLogger.dumpToLog()
        Logger.info("Transform end, " +
                "input classes count:${executor.classCount}, " +
                "affected classes:${executor.affectedCount}, " +
                "time use:${System.currentTimeMillis() - start} ms")
    }

这个方法的含义就是如果你不开启这个插件的话,会原封不动的将输入文件拷贝到输出文件夹中,交给下一个Transform处理,其实Transform就是用Task装饰了一下,最终执行的还是任务,假设A任务在B任务之前执行的话,A任务的输出就是B任务的输入,例如java被javac编译成class文件后交给混淆任务处理,就是这么一个执行流程,感兴趣的小伙伴可以下载gradle源码进行研究。android gradle2.3源码;isIncremental这个变量判断的是否实现增量更新,不是的话直接删除所有输出文件,然后创建DroidAssistContext这个类(作用是将所有要操作的类放入类池中,当然是先加载规则文件剔除掉不需要操作的class),最后是创建DroidAssistExecutor类实现字节码的操作,其他的代码都是和日志相关的,这里暂且不与关心。

ok,下面先来看看DroidAssistContext的onfigure方法的具体实现:


    def configure() {
        try {
//创建类的操作池
            createClassPool()
        } catch (Throwable e) {
            throw new DroidAssistException("Failed to create class pool", e)
        }
//加载配置文件并过滤掉不需要修改的class

        transformers = loadConfiguration()
    }

第一步是创建类的操作池

 def createClassPool() {
        classPool = new DroidAssistClassPool()
        //将android代码放入
        org.gradle.api.logging.Logger logger=project.logger;
        classPool.appendBootClasspath(project.android.bootClasspath)
        logger.quiet("path:"+project.android.bootClasspath);
        //拿出class文件流
        def dirStream = referencedInputs
                .parallelStream()
                .flatMap { it.directoryInputs.parallelStream() }
                .filter { it.file.exists() }
//拿出jar文件下的流
        def jarStream = referencedInputs
                .parallelStream()
                .flatMap { it.jarInputs.parallelStream() }
                .filter { it.file.exists() }
     //合并两个流,将他们全部放入操作池中
        Stream.concat(dirStream, jarStream).forEach {
            Logger.info("Append classpath: ${IOUtils.getPath(it.file)}")
            classPool.appendClassPath(it.file)
        }

这个方法的主要目的就是将操作的类放入池中,以备接下来操作字节使用,其中Stream的用法,小伙伴可以查看一下java8中的新特性的用法,javac任务将java转化成class分两种目录,一种就是普通目录的java文件,一种就是jar下的java文件,所以这里会把两个流结合都加入到DroidAssistClassPool池里面,其实DroidAssistClassPool就是Javassist的ClassPool

/**
 * 自定义class池
 */
    class DroidAssistClassPool extends ClassPool {
        DroidAssistClassPool() {
            super(true)
            childFirstLookup = true
        }

        void appendBootClasspath(Collection<File> paths) {
            paths.stream().parallel().forEach {
                appendClassPath(it)
                Logger.info "Append boot classpath: ${IOUtils.getPath(it)}"
            }
        }

        void appendClassPath(File path) {
            appendClassPath(path.absolutePath)
        }
    }

有关javassist的使用请看官网javassist官网

第二步/加载配置文件并过滤掉不需要修改的class

 

 def loadConfiguration() {
        def transformers = extension.configFiles
                .parallelStream()
                .flatMap {
            try {
                //解析xml文件
                def list = new DroidAssistConfiguration(project).parse(it)
                return list.stream().peek {
                    transformer ->
                        //添加包括哪些类
                        transformer.classFilterSpec.addIncludes(extension.includes)
                        //添加不包裹哪些类
                        transformer.classFilterSpec.addExcludes(extension.excludes)
                        transformer.setClassPool(classPool)
                        transformer.setAbortOnUndefinedClass(extension.abortOnUndefinedClass)
                        //开始检测
                        transformer.check()
                }
            } catch (Throwable e) {
                throw new DroidAssistException("Unable to load configuration," +
                        " unexpected exception occurs when parsing config file:$it, " +
                        "What went wrong:\n${e.message}", e)
            }
        }//parse each file
                .collect(Collectors.toList())

        Logger.info("Dump transformers:")
        transformers.each {
            Logger.info("transformer: $it")
        }
        return transformers
    }

这个方法的意图是:首先解析你的配置文件,然后根据规则创建处理专门改变class的transformer类,这里可以把它看做是策略模式的使用,例如是在方法后面加入代码还是前面还是替换方法中的哪一行代码,还是加入try catch代码段等等,采用的不同的策略,那么配置文件是什么鬼?

<?xml version="1.0" encoding="utf-8"?>
<DroidAssist>
    <!--全局配置-->
    <Global>
        ...
    </Global>

    <!--代码插入配置-->
    <Insert>
        ...
    </Insert>

    <!--代码环绕配置-->
    <Around>
        ...
    </Around>

    <!--代码替换配置-->
    <Replace>
        ...
    </Replace>

    <!--代码增强配置-->
    <Enhance>
        ...
    </Enhance>
</DroidAssist>

这就是它的配置规则,详细内容看官方DroidAssist官方

好了,继续看xml的解析,这个框架提供了

droidAssistOptions {

enable true

logLevel 3

config file{("droidassist.xml");("droidassist.xml")}

logDir file("${project.buildDir.absolutePath}/logs")

}

这种dsl的属性配置那么xml的解析就就是你配置的config file的值,来继续看怎么解析的

 List<Transformer> parse(File file) {
        Node configs = new XmlParser(true, true, true).parse(file)

        configs.Global.Filter.Include.each { globalIncludes.add(it.text()) }
        configs.Global.Filter.Exclude.each { globalExcludes.add(it.text()) }
      //添加不同的处理策略
        configs.Replace.MethodCall.each {
            node ->
                sourceTargetTransformerNodeHandler(METHOD, node) {
                    //代表第三个参数,最后一个参数的闭包可以这么写
                    return new MethodCallReplaceTransformer()
                }
        }
        //添加不同的处理策略
        configs.Replace.MethodExecution.each {
            node ->
                sourceTargetTransformerNodeHandler(METHOD, node) {
                    return new MethodExecutionReplaceTransformer()
                }
        }

        configs.Replace.ConstructorCall.each {
            node ->
                sourceTargetTransformerNodeHandler(CONSTRUCTOR, node) {
                    return new ConstructorCallReplaceTransformer()
                }
        }
        configs.Replace.ConstructorExecution.each {
            node ->
                sourceTargetTransformerNodeHandler(CONSTRUCTOR, node) {
                    return new ConstructorExecutionReplaceTransformer()
                }
        }
省略其他策略
。。。。}

先用XmlParser将文件解析为Node,关于groovy的api使用请参照官方groovy官方,然后遍历节点,根据节点的值进行不同Transformer的创建,这里我们只围绕方法前插入代码的方式研究,其他原理都是一样

 configs.Insert.BeforeMethodCall.each {
            node ->
                sourceTargetTransformerNodeHandler(METHOD, node) {
                    return new MethodCallInsertTransformer().setAsBefore(true)
                }
        }
def sourceTargetTransformerNodeHandler = {
        kind, node, transformerFeather ->
            SourceTargetTransformer transformer = transformerFeather.call()
            //标签属性是否匹配子类
            def extend = node.Source.@extend[0] ?: "true"
            //需要操作代码的位置
            transformer.setSource(node.Source.text().trim(), kind, Boolean.valueOf(extend))
            //需要部署的代码
            transformer.setTarget(node.Target.text().trim())

            if (transformer.getSource() == '') {
                throw new IllegalArgumentException("Empty source in node ${node}")
            }
            if (transformer.getTarget() == '') {
                throw new IllegalArgumentException("Empty target in node ${node}")
            }

            def includes = [], excludes = []
            //以下都是加入需要插入代码的class,和不需要插入代码的class也就是过滤class的规则
            node.Filter.Include.each { includes.add(it.text()) }
            node.Filter.Exclude.each { excludes.add(it.text()) }

            includes.addAll(globalIncludes)
            excludes.addAll(globalExcludes)

            transformer.classFilterSpec.addIncludes(includes)
            transformer.classFilterSpec.addExcludes(excludes)
            transformers.add(transformer)
    }

创建SourceTargetTransformer之后向类中加入需要操作的类和需要过滤的类,设置需要操作的代码段和需要部署的代码段,例如插入前面的话就是把Target代码插入到Source代码段前面。

准备工作已经做好了,那么接下来来看具体操作代码的类DroidAssistExecutor的execute方法

 void execute(Collection<TransformInput> inputs) {
        //合并两个类型class流
        def dirStream = inputs.stream()
                .flatMap { it.directoryInputs.stream() }

        def jarStream = inputs.stream()
                .flatMap { it.jarInputs.stream() }

        Stream.concat(dirStream, jarStream)
                .parallel()
         //将流转化为task执行
                .map { createTask(it) }
                .filter { it != null }
        //run执行子类的execute的方法
                .forEach { it.run() }
    }

 

这个方法实现比较简单就是合并两种输入的class流将它们转化为InputTask类,依次执行run方法

InputTask createTask(QualifiedContent content) {
        //创建两个输入任务
        def taskInput =
                new InputTask.TaskInput(
                        input: content,
                        dest: GradleUtils.getTransformOutputLocation(outputProvider, content),
                        incremental: incremental)
        if (content instanceof JarInput) {
            return new JarInputTask(context, buildContext, taskInput)
        }
        if (content instanceof DirectoryInput) {
            return new DirInputTask(context, buildContext, taskInput)
        }
        return null
    }

而任务一个是操作jar的任务,一个是操作普通文件下class的任务,现在来看一下DirInputTask的操作

void execute() {
        DirectoryInput input = taskInput.input
        def inputDir = input.file
        def executor = new WorkerExecutor(1)
        List<File> files = Lists.newArrayList()
/**
 * 如果增量更新的话只改变增量更新
 */

        if (taskInput.incremental) {
            //process changedFiles in incremental mode.
            //if file is removed, delete corresponding dest file.
            //if file is changed or added, add file to pending collections.
            input.changedFiles.each {
                file, status ->
                    def destFile = getDestFileMapping(file, inputDir, taskInput.dest)
                    context.project.logger.quiet("taskInput.dest:" + taskInput.dest.getAbsolutePath());
                    context.project.logger.quiet("file:" + file.getAbsolutePath());
                    context.project.logger.quiet("inputDir:" + inputDir.getAbsolutePath());
                    context.project.logger.quiet("destFile:" + destFile.getAbsolutePath());
                    if (status == Status.REMOVED) {
                        if (destFile != null) {
                            FileUtils.deleteQuietly(destFile)
                        }
                    }
                    if (status == Status.CHANGED || status == Status.ADDED) {
                        //添加进files
                        files << file
                        if (destFile != null) {
                            executor.execute {
                                FileUtils.copyFile(file, destFile)
                            }
                        }
                    }
            }
        } else {
            //process every class file in Non-incremental mode
            executor.execute {
                FileUtils.copyDirectory(inputDir, taskInput.dest)
            }

            def fileList = Files.walk(inputDir.toPath())
                    .parallel()
                    .map { it.toFile() }//Path to file
                    .filter { it.isFile() }
                    .filter { it.name.endsWith(DOT_CLASS) } //Filter class file
                    .collect(Collectors.toList())

            files.addAll(fileList)
        }
//过滤出class
        files.stream()
                .filter { it.isFile() }//Path to file
                .filter { it.name.endsWith(DOT_CLASS) }//Filter class file
                .forEach { executeClass(it, inputDir, temporaryDir) }
/**
 * 操作完输入给输出覆盖掉原来的
 */
        executor.execute {
            FileUtils.copyDirectory(temporaryDir, taskInput.dest)
        }

        executor.finish()
    }

方法很简单,就是判断如果是增量更新的话,判断哪些文件改变了,删除的删除,改变的将它全部copy到目的地然后放到files集合,过滤出class文件,执行 executor.execute方法改变字节码,最后将改变的字节码copy到输出目录覆盖原来的。接着来看executeClass

 void executeClass(File classFile, File inputDir, File tempDir) {
        //将/替换为.
        def className =
                FilenameUtils.
                        removeExtension(
                                inputDir.toPath()
                                        .relativize(classFile.toPath())
                                        .toString())
                        .replace(File.separator, '.')
        // 开始执行class操作
        executeClass(className, tempDir)
    }
/**
 * 
 * @param className
 * @param directory 临时目录
 * @return
 */
    boolean executeClass(String className, File directory) {
        buildContext.totalCounter.incrementAndGet()
        def inputClass = null
        def transformers = context.transformers
        //找到一个满足的就返回
        def classAllowed = transformers.any {
            it.classAllowed(className)
        }
        if (!classAllowed) {
            return false
        }
        org.gradle.api.logging.Logger logger=context.project.logger;

        logger.quiet("className:"+className);
        //全类名寻找类池内容
        inputClass = context.classPool.getOrNull(className)
        if (inputClass == null) {
            return false
        }

        transformers.each {
            try {
                it.performTransform(inputClass, className)
            } catch (NotFoundException e) {
                throw new DroidAssistNotFoundException(
                        "Transform failed for class: ${className}" +
                                " with not found exception: ${e.cause?.message}", e)
            } catch (CannotCompileException e) {
                throw new DroidAssistBadStatementException(
                        "Transform failed for class: ${className} " +
                                "with compile error: ${e.cause?.message}", e)
            } catch (Throwable e) {
                throw new DroidAssistException(
                        "Transform failed for class: ${className} " +
                                "with error: ${e.cause?.message}", e)
            }
        }
//类被修改的话则完成修改,将文件写入directory文件夹下
        if (inputClass.modified) {
            buildContext.affectedCounter.incrementAndGet()
            inputClass.writeFile(directory.absolutePath)
            return true
        }
        return false
    }

这个方法主要是遍历策略模式是否可以执行这个类,也就是include规则,有可以执行它的策略,就会遍历策略执行类的修改,最终将改变的class写入临时文件夹下。继续看performTransform方法

 public boolean performTransform(
            CtClass inputClass,
            String className)
            throws NotFoundException, CannotCompileException {

        inputClass.stopPruning(true);
        //让类可以重新操作
        if (inputClass.isFrozen()) {
            inputClass.defrost();
        }
        beforeTransform();
        return onTransform(inputClass, className);
    }

这个方法让ctclass这个类可以重新操作然后执行beforeTransform和onTransform,最终字节码修改是在onTransform中实现,这里还是以方法里插入代码为例,也就是MethodCallInsertTransformer策略类

 protected final boolean onTransform(
            CtClass inputClass,
            String inputClassName)
            throws NotFoundException, CannotCompileException {
        //expr
        if (TRANSFORM_EXPR.equals(getTransformType())) {
            return onTransformExpr(inputClass, inputClassName);
        }
        //exec
        if (TRANSFORM_EXEC.equals(getTransformType())) {
            return onTransformExec(inputClass, inputClassName);
        }
        return false;
    }

它会执行onTransformExpr方法

   //检查class是否在include或者不在einclude
        if (!filterClass(inputClass, inputClassName)) {
            return false;
        }

        final AtomicBoolean modified = new AtomicBoolean(false);
        Editor editor = new Editor() {

            @Override
            public void edit(MethodCall call) throws CannotCompileException {
                if (METHOD_CALL.equals(getExecuteType())) {
                    boolean disposed;
                    try {
                        disposed = execute(inputClass, inputClassName, call);
                    } catch (NotFoundException e) {
                        String msg = e.getMessage() + " for input class " + inputClassName;
                        throw new CannotCompileException(msg, e);
                    }
                    modified.set(modified.get() | disposed);
                }
            }

            @Override
            public void edit(FieldAccess fieldAccess) throws CannotCompileException {
                if (FIELD_ACCESS.equals(getExecuteType())) {
                    boolean disposed;
                    try {
                        disposed = execute(inputClass, inputClassName, fieldAccess);
                    } catch (NotFoundException e) {
                        String msg = e.getMessage() + " for input class " + inputClassName;
                        throw new CannotCompileException(msg, e);
                    }
                    modified.set(modified.get() | disposed);
                }
            }

            @Override
            public void edit(NewExpr newExpr) throws CannotCompileException {
                if (NEW_EXPR.equals(getExecuteType())) {
                    boolean disposed;
                    try {
                        disposed = execute(inputClass, inputClassName, newExpr);
                    } catch (NotFoundException e) {
                        String msg = e.getMessage() + " for input class " + inputClassName;
                        throw new CannotCompileException(msg, e);
                    }
                    modified.set(modified.get() | disposed);
                }
            }
        };
     //得到默认构造方法函数,不声明自己的构造函数的时候
        CtConstructor initializer = tryGetClassInitializer(inputClass);
        if (initializer != null) {
            if (instrument(initializer, editor)) {
                modified.set(true);
            }
        }
        //得到类的所有方法
        CtMethod[] declaredMethods = tryGetDeclaredMethods(inputClass);
        for (CtMethod method : declaredMethods) {
            if (instrument(method, editor)) {
                modified.set(true);
            }
        }
        //得到所有声明的构造方法
        CtConstructor[] declaredConstructors = tryGetDeclaredConstructors(inputClass);
        for (CtConstructor constructor : declaredConstructors) {
            if (instrument(constructor, editor)) {
                modified.set(true);
            }
        }
        return modified.get();

代码有点多,但就是实现了两个功能,再监测一次这个class是否符合当前策略的匹配规则,然后获得构造方法和所有的声明方法开始监测字节码,进行修改,也就是来到这个局部内部类的执行方法

   Editor editor = new Editor() {
//方法体中遇到方法调用会走这个
            @Override
            public void edit(MethodCall call) throws CannotCompileException {
                if (METHOD_CALL.equals(getExecuteType())) {
                    boolean disposed;
                    try {
                        disposed = execute(inputClass, inputClassName, call);
                    } catch (NotFoundException e) {
                        String msg = e.getMessage() + " for input class " + inputClassName;
                        throw new CannotCompileException(msg, e);
                    }
                    modified.set(modified.get() | disposed);
                }
            }
//遇到属性声明会走这个
            @Override
            public void edit(FieldAccess fieldAccess) throws CannotCompileException {
                if (FIELD_ACCESS.equals(getExecuteType())) {
                    boolean disposed;
                    try {
                        disposed = execute(inputClass, inputClassName, fieldAccess);
                    } catch (NotFoundException e) {
                        String msg = e.getMessage() + " for input class " + inputClassName;
                        throw new CannotCompileException(msg, e);
                    }
                    modified.set(modified.get() | disposed);
                }
            }
//遇到new对象会走这个
            @Override
            public void edit(NewExpr newExpr) throws CannotCompileException {
                if (NEW_EXPR.equals(getExecuteType())) {
                    boolean disposed;
                    try {
                        disposed = execute(inputClass, inputClassName, newExpr);
                    } catch (NotFoundException e) {
                        String msg = e.getMessage() + " for input class " + inputClassName;
                        throw new CannotCompileException(msg, e);
                    }
                    modified.set(modified.get() | disposed);
                }
            }
        };

最终执行的是execute方法

//如果是super的直接不予修改
        if (methodCall.isSuper()) {
            return false;
        }
        String insnClassName = methodCall.getClassName();
        String insnName = methodCall.getMethodName();
        String insnSignature = methodCall.getSignature();
     //是否能找到这个方法的调用
        CtClass insnClass = tryGetClass(insnClassName, inputClassName);
        if (insnClass == null) {
            return false;
        }
        //判断这个类是否是source处
        if (!isMatchSourceMethod(insnClass, insnName, insnSignature)) {
            return false;
        }

        String target = getTarget();
        int line = methodCall.getLineNumber();
        if (!target.endsWith(";")) target = target + ";";

        String before = isAsBefore() ? target : "";
        String after = isAsAfter() ? target : "";

        String proceed = isVoidSourceReturnType() ? "$proceed($$);" : "$_ =$proceed($$);";

        String statement = before + proceed + after;
   //将原来代码替换为附加代码
        String replacement = replaceInstrument(methodCall, statement);

        if (isAsBefore()) {
            Logger.warning(getPrettyName() + " insert before call by: " + replacement
                    + " at " + inputClassName + ".java" + ":" + line);
        }

        if (isAsAfter()) {
            Logger.warning(getPrettyName() + " insert after call by: " + replacement
                    + " at " + inputClassName + ".java" + ":" + line);
        }
        return true;
    }

这个方法主要也是做了三件事,检查这个methodCall的调用类是不是source中的类,是的话检测是在前插入代码还是后插入代码,后和前将原来代码放在中间替换原来的代码,这样就实现了代码的前或后插入。替换的代码如下:

 protected String replaceInstrument(
            MethodCall methodCall,
            String statement)
            throws CannotCompileException {
        String replacement = getReplaceStatement(methodCall, statement);
        try {
            String s = replacement.replaceAll("\n", "");
            methodCall.replace(s);
        } catch (CannotCompileException e) {
            Logger.error("Replace method call instrument error with statement: "
                    + statement + "\n", e);
            throw new DroidAssistBadStatementException(e);
        }
        return replacement;
    }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 类似资料: