Android(安卓)AOP 之 javassist 示例
javassist Demo
这里主要介绍一些javassist在Android中的基本使用方法,以及一个简单的实例; 在做这个Demo时,也从网络上获取过相关知识,只是大部分都是copy的,没有很大的参考价值,而且坑也比较多,这里主要就是记录采坑记吧!
一、准备工作:
1、新建一个android项目,然后添加一个LibraryModule,我们的插件就在这个module中开发了
2、在LibraryModule的gradle文件中改成如下代码
apply plugin: 'groovy'apply plugin: 'maven'//group = 'com.github.alfredxl' //这里是你的github地址,如果使用jitpack发布该插件,这里需要填上你自己的github地址dependencies { compile gradleApi() compile localGroovy() compile 'com.android.tools.build:gradle:3.1.4' compile 'org.javassist:javassist:3.20.0-GA' // 这里可以添加你如果用到的第三方包}repositories { google() jcenter()}//下面的配置是为了发布到本地,发布到本地主要是测试方便uploadArchives { repositories.mavenDeployer { repository(url: uri('../localGradlePlugin'))//打包存放的位置,这里存放在Module的同级位置 pom.groupId = 'com.github.alfredxl'//包名 pom.artifactId = 'testjavassist'//在需要引用插件时用到 pom.version = '1.0.0'//插件版本 }}
3、删除Module下多余的文件和文件夹,保留如下截图的文件结构:
其中图中画红圈的地方的命名将是后面讲到的plugin的名称,后面将会详细讲到,我们打开这个文件,里面会直接链接到你开发的插件类:
implementation-class=com.bi.MyPlugin
在本例中,插件代码类就是MyPlugin
二、开发知识:
1、定义类实现Plugin接口:
class MyPlugin implements Plugin { @Override void apply(Project project) { def android = project.extensions.findByType(AppExtension.class) android.registerTransform(new MyJavassistTransform(project)) }}
这里定义了MyPlugin类实现Plugin接口,然后注册Transform,在transform中你就可以做你想做的事情了
这里还要提到的就是关于Project,Project大家有必要熟悉下,可以查看官网
2、定义类继承Transform:
public class MyJavassistTransform extends Transform { private Project project; public MyJavassistTransform(Project project) { this.project = project; } @Override public String getName() { return "MyJavassistTransform"; //在Tasks中的名称 } @Override public Set getInputTypes() { return TransformManager.CONTENT_CLASS; } @Override public Set<? super QualifiedContent.Scope> getScopes() { return TransformManager.SCOPE_FULL_PROJECT; } @Override public boolean isIncremental() { return false; } @Override public void transform(TransformInvocation transformInvocation) throws IOException { System.out.println("MyJavassistTransform_start..."); Collection inputs = transformInvocation.getInputs(); TransformOutputProvider outputProvider = transformInvocation.getOutputProvider(); // 删除上次编译目录 outputProvider.deleteAll(); try { ClassPool mClassPool = new ClassPool(ClassPool.getDefault()); // 添加android.jar目录 mClassPool.appendClassPath(AndroidJarPath.getPath(project)); Map dirMap = new HashMap<>(); Map jarMap = new HashMap<>(); for (TransformInput input : inputs) { for (DirectoryInput directoryInput : input.getDirectoryInputs()) { // 获取output目录 File dest = outputProvider.getContentLocation(directoryInput.getName(), directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY); dirMap.put(directoryInput.getFile().getAbsolutePath(), dest.getAbsolutePath()); mClassPool.appendClassPath(directoryInput.getFile().getAbsolutePath()); } for (JarInput jarInput : input.getJarInputs()) { // 重命名输出文件 String jarName = jarInput.getName(); String md5Name = DigestUtils.md5Hex(jarInput.getFile().getAbsolutePath()); if (jarName.endsWith(".jar")) { jarName = jarName.substring(0, jarName.length() - 4); } //生成输出路径 File dest = outputProvider.getContentLocation(jarName + md5Name, jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR); jarMap.put(jarInput.getFile().getAbsolutePath(), dest.getAbsolutePath()); mClassPool.appendClassPath(new JarClassPath(jarInput.getFile().getAbsolutePath())); } } for (Map.Entry item : dirMap.entrySet()) { System.out.println("perform_directory : " + item.getKey()); Inject.injectDir(item.getKey(), item.getValue(), mClassPool); } for (Map.Entry item : jarMap.entrySet()) { System.out.println("perform_jar : " + item.getKey()); Inject.injectJar(item.getKey(), item.getValue(), mClassPool); } } catch (Exception e) { e.printStackTrace(); } System.out.println("MyJavassistTransform_end..."); }}
这个类里面的逻辑,其实是基本可以套娃的,不过这里面也是很多坑,花了很多时间来解决,需要注意的就有如下几点:
- 这里有个输入和输出路径,我们一定要注意的是我们不能更改输入路径的文件,只能改输出路径的文件,这些输出路径的文件
将作为下个transorm的输入路径;(网络上大部分照搬的文章都是改的输入路径的文件,这是个大坑,不知道相关作者是否真有试验过)- ClassPool可以看作是类的加载器,要预先设置加载的路径,这里的坑就是我们要注意添加类的加载路径的时机,
为了在编辑类的时候不会报错,我们这里先遍历了整个项目的依赖和源码(如果导入时机不对,就会找不到类)- ClassPool可以采用级联方式,这里网络上也有很多介绍,避免内存溢出,不多做介绍
- 翻看了很多文章,在处理jar的代码插入上,很多文章上都是先解压,再打包,这其实是一个巨坑,我们都知道解压和压缩是非常耗时的,
后来,还是查阅了很多插件源码,找到一种可行的方案,就是以流的形式读取压缩包jar,然后把流交给javassist去修改,修改后转化成输
出流,以这种形式, 基本上能够媲美直接复制的速度了, 介于该方式的速度快,在对class文件的拷贝上,也把拷贝的过程改成流的修改
过程,从而解决了整个插件运行速度上的问题;
3、定义代码的插入逻辑:
代码的插入,关于javassist的语法这里推荐简书文章
demo中有这样一个需求,我们看sample中定义了一个注解类PointAnnotation,注解类中主要有className,以及MethodName 这个className和methodName可以用来组成在注解方法上插入的语句,我们看这个注解类标注的地方的代码块:
@PointAnnotation(className = "com.alfredxl.javassist.sample.InsertClass", methodName = "showText") private void setText(String text) { ((TextView) findViewById(R.id.textView)).setText(text); }
在ManActivity类中的一个方法上我们添加了这个注解,根据这个注解的参数,我们最后插入的代码的效果应该如下:
@PointAnnotation(className = "com.alfredxl.javassist.sample.InsertClass", methodName = "showText") private void setText(String text) { InsertClass.showText(new Point(this, new Object[]{text})); ((TextView) findViewById(R.id.textView)).setText(text); }
这个示例本身比较简单,就是在标注有特殊注解的方法上,插入语句,便于项目的隔离;也是AOP概念的体现;
插件编写完毕,由于定义了本地插件发布,我们可以在右侧的gradle视图中,点击upload发布,如下图:
接下来就是在sample中添加这个插件了,插件的添加 需要在项目的目录gradle下配置仓库地址,并引入包,配置如下:
buildscript { repositories { google() jcenter() maven { url uri('localGradlePlugin') //仓库地址 } } dependencies { classpath 'com.android.tools.build:gradle:3.1.4' classpath 'com.github.alfredxl:testjavassist:1.0.0' // 我们在插件中定义的报名、插件名、版本号 // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }}
然后在sample的gradle中添加插件:
apply plugin: 'my-plugin'
添加完毕,我们同样可以在gradle视图中,去执行测试这个插件:
双击执行,看打印结果
我们也可以到sample的build文件夹下查看编译后的代码:
到这里,javassist在Android中的基本使用也就基本结束了。
三、总结:
对于javassist在android中的应用,可以找的资料也不是很多,所以大部分还是要多参考其它一些优秀的项目,
这里提供一个插件发布平台,可以在里面看到一些优秀的项目,本篇就写到这里。 email:[email protected]
项目地址:项目源码
更多相关文章
- Android(安卓)ADB超简单的安装方法
- android(NDK+JNI)---Android(安卓)JNI开发生成.h头文件问题
- android笔记-android基本操作和数据存储
- Android(安卓)Studio 慢吗?No!!你还不懂她···
- Cocos2dx发布Android包,配置开发环境(菜鸟级入门,一看就会)
- 纯ant命令行打包android apk之图文从原理角度完全详解android打
- 关于做Android+J2ee系统集成开发的一点心得来源
- Android(安卓)Studio 配置一键生成 JNI 头文件工具
- Android(安卓)Studio编译jar架包必看