频道栏目
首页 > 程序开发 > 移动开发 > Android > 正文
Android开发中Javassist使用技巧
2017-11-08 11:50:42      个评论    来源:Deemons的博客  
收藏   我要投稿

Javassist

Javassist 是一个执行字节码操作的库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。

Javassist 可以绕过编译,直接操作字节码,从而实现代码注入,所以使用 Javassist 的时机就是在构建工具 Gradle 将源文件编译成 .class 文件之后,在将 .class 打包成 dex 文件之前。

Gradle

Android Studio 项目是使用 Gradle 构建的,构建工具 Gradle 可以看做是一个脚本,包含一系列的Task,依次执行这些 Task 后,项目就打包成功了。

而 Task 有一个重要的概念,那就是 inputsoutputs
Task 通过 inputs 拿到一些东西,处理完毕之后就输出 outputs ,而下一个 Task 的 inputs 则是上一个 Task 的outputs。

例如:一个 Task 的作用是将 java 编译成 class,这个 Task 的 inputs 就是 java 文件的保存目录,outputs 这是编译后的 class 的输出目录,它的下一个 Task 的 inputs 就会是编译后的 class 的保存目录了。

 

Gradle 由一个个 Task 组成,而这些 Task 都是由 Plugin 来定义的。

比如:

apply plugin : 'com.android.application' 这个 插件定义了将 Module 编译成 application 的一系列 Task。

apply plugin : 'com.android.library' 这个 插件定义了将 Module 编译成 library 的一系列 Task。

不同的 Plugin 提供了不同的 Task 来实际不同的功能。

可以简单的理解为: Gradle只是一个框架,真正起作用的是plugin。而plugin的主要作用是往Gradle脚本中添加Task

 

我们需要在整个 Gradle 工作的过程中,找到合适的时机来插入自定义的 Plugin,然后在 Plugin 中使用 Javassist 对字节进行操作 ,所以使用 Javassit 的前提是掌握自定义 Gradle 插件,。

在前面说了 Javassist 工作的时机,这个时机在 Gradle1.5 之前并不容易掌握的,从1.5.0-beta1开始,android的gradle插件引入了com.android.build.api.transform.Transform,可以点击 https://tools.android.com/tech-docs/new-build-system/transform-api 查看相关内容,我们刚好就可以利用这个 API 来使用 Javassist 。

transform<喎"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPiZuYnNwOzwvcD4NCjxoMyBpZD0="流程">流程

1.在 插件的module 中添加依赖:

apply plugin: 'groovy'

dependencies {
    compile gradleApi() //gradle sdk
    compile localGroovy() //groovy sdk

    compile 'com.android.tools.build:gradle:2.2.0'
    compile 'org.javassist:javassist:3.20.0-GA'
}
repositories {
    jcenter()
}

2.在自定义的插件中调用 自定义Transform

public class JavassistPlugin implements Plugin {

    void apply(Project project) {
        def log = project.logger
        log.error "========================";
        log.error "Javassist开始修改Class!";
        log.error "========================";
        project.android.registerTransform(new JavassistTransform(project))
    }
}

3.自定义 Transform

package com.deemons.bus

import com.android.build.api.transform.*
import com.google.common.collect.Sets
import javassist.ClassPool
import org.apache.commons.io.FileUtils
import org.gradle.api.Project

public class JavassistTransform extends Transform {
    Project project

    public JavassistTransform(Project project) {    // 构造函数,我们将Project保存下来备用
        this.project = project
    }

    @Override
    String getName() {// 设置我们自定义的Transform对应的Task名称
        return "JavassistTrans"
    }


    @Override
    // 指定输入的类型,通过这里的设定,可以指定我们要处理的文件类型这样确保其他类型的文件不会传入
    Set getInputTypes() {
        return Sets.immutableEnumSet(QualifiedContent.DefaultContentType.CLASSES)
    }


    @Override
// 指定Transform的作用范围
    Set getScopes() {
        return Sets.immutableEnumSet(QualifiedContent.Scope.PROJECT, QualifiedContent.Scope.PROJECT_LOCAL_DEPS,
                QualifiedContent.Scope.SUB_PROJECTS, QualifiedContent.Scope.SUB_PROJECTS_LOCAL_DEPS,
                QualifiedContent.Scope.EXTERNAL_LIBRARIES)
    }

    @Override
    boolean isIncremental() {
        return false
    }

    @Override
    void transform(Context context, Collection inputs,
                   Collection referencedInputs,
                   TransformOutputProvider outputProvider, boolean isIncremental)
            throws IOException, TransformException, InterruptedException {
        def startTime = System.currentTimeMillis();
        // Transform的inputs有两种类型,一种是目录,一种是jar包,要分开遍历
        inputs.each { TransformInput input ->
            try {
                //对 jar包 类型的inputs 进行遍历
                input.jarInputs.each {
                    //这里处理自定义的逻辑
                    MyInject.injectDir(it.file.getAbsolutePath(), "com", project)
                    // 重命名输出文件(同目录copyFile会冲突)
                    String outputFileName = it.name.replace(".jar", "") + '-' + it.file.path.hashCode()
                    def output = outputProvider.getContentLocation(outputFileName, it.contentTypes, it.scopes, Format.JAR)
                    FileUtils.copyFile(it.file, output)
                }
            } catch (Exception e) {
                project.logger.err e.getMessage()
            }
            //对类型为“文件夹”的input进行遍历
            input.directoryInputs.each { DirectoryInput directoryInput ->
                //文件夹里面包含的是我们手写的类以及R.class、BuildConfig.class以及R$XXX.class等
                //这里处理自定义的逻辑
                MyInject.injectDir(directoryInput.file.absolutePath, "com", project)
                // 获取output目录
                def dest = outputProvider.getContentLocation(directoryInput.name,
                        directoryInput.contentTypes, directoryInput.scopes,
                        Format.DIRECTORY)

                // 将input的目录复制到output指定目录
                FileUtils.copyDirectory(directoryInput.file, dest)
            }
        }
        ClassPool.getDefault().clearImportedPackages();
        project.logger.error("JavassistTransform cast :" + (System.currentTimeMillis() - startTime) / 1000 + " secs");
    }
}

以上其实都算是模板了,具体怎样使用 javassist ,源码里面有示例,这里就不介绍了。

点击复制链接 与好友分享!回本站首页
上一篇:Android开源框架Gradle 插件自定义教程
下一篇:android setImage nullPointerException错误
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站