Android 中使用Javassist
来源:互联网 发布:搜狗输入法云计算进程 编辑:程序博客网 时间:2024/05/20 17:59
Javassist
Javassist 是一个执行字节码操作的库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。
Javassist 可以绕过编译,直接操作字节码,从而实现代码注入,所以使用 Javassist 的时机就是在构建工具 Gradle 将源文件编译成 .class 文件之后,在将 .class 打包成 dex 文件之前。
Gradle
Android Studio 项目是使用 Gradle 构建的,构建工具 Gradle 可以看做是一个脚本,包含一系列的Task,依次执行这些 Task 后,项目就打包成功了。
而 Task 有一个重要的概念,那就是 inputs 和outputs 。
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 插件,不清楚的可以看前面的 自定义 Gradle 插件 ,这里不多介绍。
在前面说了 Javassist 工作的时机,这个时机在 Gradle1.5 之前并不容易掌握的,从1.5.0-beta1开始,android的gradle插件引入了com.android.build.api.transform.Transform,可以点击 http://tools.android.com/tech-docs/new-build-system/transform-api 查看相关内容,我们刚好就可以利用这个 API 来使用 Javassist 。
流程
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<Project> { 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.busimport com.android.build.api.transform.*import com.google.common.collect.Setsimport javassist.ClassPoolimport org.apache.commons.io.FileUtilsimport org.gradle.api.Projectpublic class JavassistTransform extends Transform { Project project public JavassistTransform(Project project) { // 构造函数,我们将Project保存下来备用 this.project = project } @Override String getName() {// 设置我们自定义的Transform对应的Task名称 return "JavassistTrans" } @Override // 指定输入的类型,通过这里的设定,可以指定我们要处理的文件类型这样确保其他类型的文件不会传入 Set<QualifiedContent.ContentType> getInputTypes() { return Sets.immutableEnumSet(QualifiedContent.DefaultContentType.CLASSES) } @Override// 指定Transform的作用范围 Set<QualifiedContent.Scope> 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<TransformInput> inputs, Collection<TransformInput> 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 ,后面有参考链接,源码里面也有示例,这里就不介绍了。
源码参考 Demo :JavassistDemo
参考
通过自定义Gradle插件修改编译后的class文件
Android热补丁动态修复技术(三)—— 使用Javassist注入字节码,完成热补丁框架雏形(可使用)
安卓AOP实战:Javassist强撸EventBus
Javassist 使用指南(一)
Javassist 使用指南(二)
Javassist 使用指南(三)
- Android 中使用Javassist
- javassist使用中遇到的问题记录
- Android中使用Java开源库Javassist动态创建字节码的学习研究
- 使用Javassist在tomcat容器中实现动态Mock
- javassist使用实例
- javassist 介绍和使用
- javassist 的简单使用
- javassist 的使用
- java javassist使用实例
- javassist使用样例
- javassist使用样例
- javassist
- Javassist
- Javassist
- JAVAssist
- Javassist
- javassist
- 使用javassist动态注入代码
- 文本标签
- 简单总结图优化 三 MATLAB仿真
- 9.笔记JAVA框架学习——Bean自动装配
- 在Ubuntu上安装rabbitMq server
- 10.笔记JAVA框架学习——Bean之间关系
- Android 中使用Javassist
- CKfinder和CKeditor使用笔记
- JAVA基础复习十五-Collection集合子类-List集合的子类
- STM8串口打印调试信息
- Windows线程同步的四种方法
- body标签的属性练习
- 垃圾回收器如何工作
- Robot Framework-Mac版本安装
- Linux内核:IO设备的抽象管理方式