AGP tramsform 阳光穿透心脏的1/2处 2022-11-08 14:23 201阅读 0赞 # Transform 是什么 # `Transform` 是`AGP`提供了一个`API`,可以在java编译成class后进行一系列的转化,如`插桩`和埋点统计等。 我们看下正常编译流程: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FmYW5taW5neWlx_size_16_color_FFFFFF_t_70] 启用`transform`之后: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FmYW5taW5neWlx_size_16_color_FFFFFF_t_70 1] 示例源码地址: https://github.com/fanmingyi/AGP-Transfrom-Example # Transform 案例 # 本文利用`Transform`完成如下几件事: 1. 将一个`com.example.agptramsform.MainActivity`修改继承自`BaseProxyActivity`, 2. 在onCreate函数前后统计调用时间 为了实现字节码的修改,我们利用`javassist`完成。`AGP`使用`4.1.2`. 首先声明一个插件类,让插件类找到AGP所提供的函数进行`Transform`注册. public abstract class MyGradlePlugin implements Plugin<Project> { @Override public void apply(Project project) { //模块应用android插件如plugins { id 'kotlin-android'}后 //会有一个BaseExtension扩展,这个扩展类提供了注册一个Transform的功能 project.getExtensions().findByType(BaseExtension.class) .registerTransform(new MyTransform(project)); } } 接下来编写`Transform`的实现类即可: 我们需要一个类继承`Transform`,然后覆盖一些特定的方法。 public class MyTransform extends Transform { /* * 你的Transform的名字,你可以随意取。方便在编译时查看日志 */ @Override public String getName() { return "MyFmyMyTransform"; } /* * 这个函数应返回的你的Transform应该处理什么类型的内容 * 你可以声明逆向处理java的资源文件如图片,或者你只处理类 * ContentType的自类有两种:CLASSES和RESOURCES * * TransformManager中有一些方面我们使用的预定义集合类型 * class TransformManager{ * Set<ContentType> CONTENT_CLASS = ImmutableSet.of(CLASSES); * } */ @Override public Set<QualifiedContent.ContentType> getInputTypes() { //我们这里只处理class文件 return CONTENT_CLASS; } /** * 这个函数你想处理的范围.比如说你只想处理当前工程的类和资源(QualifiedContent.Scope.PROJECT).或者你只想处理外部类库的资源(QualifiedContent.Scope.EXTERNAL_LIBRARIES) **/ @Override public Set<? super QualifiedContent.Scope> getScopes() { Set<QualifiedContent.ScopeType> d = ImmutableSet.of(QualifiedContent.Scope.PROJECT, QualifiedContent.Scope.SUB_PROJECTS, QualifiedContent.Scope.EXTERNAL_LIBRARIES); return d; } //当前工程是否支持增量 //如果不开启那么Transform在多次编译下,很耗费时间 @Override public boolean isIncremental() { return true; } //这个函数会在进行转化的时候进行回调,也就是执行核心的转化方法。 @Override public void transform(TransformInvocation transformInvocation) { } } 这里我们总结这个类的几个方法: 1 `getInputTypes` 你想处理类还是资源 2 `getScopes` 你想处理哪里的 类和资源?当前工程还是依赖的类库? 3 `isIncremental` 当前`Transform`是否支持增量调用?如果你不知道什么是增量 返回`false`即可 4 `transform` 执行具体的转化函数 我们看下`TransformInvocation`这个类的有关信息 public interface TransformInvocation { /** * 返回transform运行的上下文 */ @NonNull Context getContext(); /** * 这个集合是你getScopes和getInputTypes所定义的资源/class的输入信息 */ @NonNull Collection<TransformInput> getInputs(); /** * AGP要求getInputs处理之后的资源或者类输出目录。 * 这里注意getInputs的资源和类哪怕你没处理也要输出到TransformOutputProvider所指定的目录 */ @Nullable TransformOutputProvider getOutputProvider(); /** * 当前执行的是否是增量操作 */ boolean isIncremental(); } 我们在继续讲解`transform`类之前我们先写一个工具类,对某个`MainActivity`进行字节操作。 下面我们利用`javassist`完成字节操作 public class TransformKit { ClassPool pool = ClassPool.getDefault(); Project project; public TransformKit(Project project) { this.project = project; } //searchDir类路径 比如是build/intermediates/javac/debug/classes public void transform(String searchDir) throws NotFoundException, CannotCompileException, IOException { //添加到javassist搜索路径中 pool.appendClassPath(searchDir); //查找类 CtClass mainCtClass = pool.get("com.example.agptramsform.MainActivity"); //我们的将要MainActivity继承的类 CtClass baseProxyCtClass = pool.get("com.example.agptramsform.BaseProxyActivity"); //修改字节码 mainCtClass.setSuperclass(baseProxyCtClass); //这里再次获得AGP的扩展类,这里主要是为了得到sdk的中android部分类库。不然无法找到android.os.Bundle等android类 BaseExtension android = project.getExtensions().findByType(BaseExtension.class); //将sdk中的类库资源添加到搜索区域 pool.appendClassPath(android.getBootClasspath().get(0).toString(); pool.importPackage("android.os.Bundle"); CtMethod onCreate = mainCtClass.getDeclaredMethod("onCreate"); //函数运行前的插入一个代码 onCreate.insertBefore("long _startTime = System.currentTimeMillis();"); //函数运行最后一行插入代码 onCreate.insertAfter("long _endTime = System.currentTimeMillis();"); mainCtClass.writeFile(searchDir); mainCtClass.detach(); baseProxyCtClass.detach(); } } 我们最后看下`MyTransform`的`transform`函数实现 public class MyTransform extends Transform { Project project; @Override public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { super.transform(transformInvocation); //构造一个字节码操作工具类 TransformKit transformKit = new TransformKit(project); //得到期望的资源/类的输入路径信息 Collection<TransformInput> inputs = transformInvocation.getInputs(); //AGP要求transform输出的目录 TransformOutputProvider outputProvider = transformInvocation.getOutputProvider(); //当前不是增量更新的那么删除这个transform的缓存文件 //build/intermediates/transforms/xxxxxx if (!transformInvocation.isIncremental()) { //如果当前不是增量,应该删除之前的所有缓存信息,防止意料之外错误 outputProvider.deleteAll(); } //遍历所有输入信息,进行处理 for (TransformInput transformInput : inputs) { //TransformInput输入类型有两种目录类型,一种是jar类型的,一种就是目录 //遍历jar文件 对jar不操作,但是要输出到out路径 //parallelStream多线程进行处理集合类 transformInput.getJarInputs().parallelStream().forEach(jarInput -> { //获取AGP要求输出的目录,哪怕你没修改也要输出 File dst = outputProvider.getContentLocation( jarInput.getName(), jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR); //处理增量情况 if (transformInvocation.isIncremental()) { switch (jarInput.getStatus()) { //未做任何改变 case NOTCHANGED: break; case ADDED: //如果一个jar被添加,需要被拷贝回来 case CHANGED: //是一个新的文件,那么需要拷贝回来 try { FileUtils.copyFile(jarInput.getFile(), dst); } catch (IOException e) { e.printStackTrace(); } break; //当前的输入源已经被删除,那么transform下的对应文件理应被删除 case REMOVED: if (jarInput.getFile().exists()) { try { FileUtils.forceDelete(jarInput.getFile()); } catch (IOException e) { e.printStackTrace(); } } break; } } else { //非增量拷贝源集类路径 try { FileUtils.copyFile(jarInput.getFile(), dst); } catch (IOException e) { throw new RuntimeException(e); } } }); //同上 for (DirectoryInput directoryInput : transformInput.getDirectoryInputs()) { // 获取输出目录 File dest = outputProvider.getContentLocation(directoryInput.getName(), directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY); FileCollection filter = project.fileTree(directoryInput.getFile()) .filter(innerFile -> innerFile.getName().equals("MainActivity.class")); //当前是增量的状态,所以遍历这个文件夹下的所有文件 if (transformInvocation.isIncremental()) { Map<File, Status> changedFiles = directoryInput.getChangedFiles(); //遍历文件状态 BiConsumer<File, Status> fileStatusBiConsumer = (file, status) -> { switch (status) { //这个文件夹不做任何事情 case NOTCHANGED: break; case CHANGED: case ADDED: //顺带检查下是否存在我们目标的文件,如果存在那么修改字节码后在拷贝 if (file.getName().equals("MainActivity.class")) { try { transformKit.transform(directoryInput.getFile().getAbsolutePath()); } catch (Exception e) { e.printStackTrace(); } } try { /** * 处理方式一 简单粗暴 */ //偷懒就直接拷贝文件夹 但是效率低 // FileUtils.copyDirectory(directoryInput.getFile(), dest); /** * 处理方式二 高效 略复杂 */ //构建目录连带包名 //file 可能的目录是 /build/intermediates/java/debug/com/fmy/MainActivity.class //dest 可能目标地址 /build/intermediates/transforms/mytrasnsfrom/debug/40/ //directoryInput.getFile() 可能的输入类的文件夹 /build/intermediates/java/debug/ File dirFile = directoryInput.getFile(); String prefixPath = file.getAbsolutePath().replaceFirst(dirFile.getAbsolutePath(), ""); System.out.println(); //重新拼接成/build/intermediates/transforms/mytrasnsfrom/debug/40/com/fmy/MainActivity.class File specifyDest = new File(dest.getAbsolutePath(), prefixPath); FileUtils.copyFile(file, specifyDest); } catch (Exception e) { e.printStackTrace(); } break; case REMOVED: //文件被删除直接删除相关文件即可 try { FileUtils.forceDelete(file); } catch (IOException e) { e.printStackTrace(); } break; } }; changedFiles.forEach(fileStatusBiConsumer); } else { if (!filter.isEmpty()) { try { transformKit.transform(directoryInput.getFile().getAbsolutePath()); } catch (Exception e) { e.printStackTrace(); } } FileUtils.copyDirectory(directoryInput.getFile(), dest); } } } } } 运行后可以看到编译输出多了一个task任务: ![在这里插入图片描述][20210314191649230.png] # 参考 # [Gradle-初探代码注入Transform][Gradle-_Transform] [Gradle 学习之 Android 插件的 Transform API][Gradle _ Android _ Transform API] [Transform Api][] [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FmYW5taW5neWlx_size_16_color_FFFFFF_t_70]: /images/20221023/751bab65548f4fe7ac52e7847c0f4f26.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FmYW5taW5neWlx_size_16_color_FFFFFF_t_70 1]: /images/20221023/26911ad367a64ef1b8039c53a7383b61.png [20210314191649230.png]: /images/20221023/ea9165f3409f4d42a34f823c62ec12ac.png [Gradle-_Transform]: https://blog.csdn.net/a296777513/article/details/90665134 [Gradle _ Android _ Transform API]: https://juejin.cn/post/6844903891138674696 [Transform Api]: https://google.github.io/android-gradle-dsl/javadoc/3.4/
相关 【ijkplayer】编译 Android 版本的 ijkplayer ⑦ ( 使用 AS 打开源码 | 重新设置 AGP 和 Gradle 版本号 | 设置依赖仓库 | 设置依赖 | 编译运行 ) 文章目录 一、Android Studio 打开编译后的 ijkplayer 源码 二、重新设置 Android Gradle 插件版本号和 Gradle 构 灰太狼/ 2023年10月14日 19:34/ 0 赞/ 111 阅读
相关 The project in using an incompatible version(AGP 7.3.0) of the Android Gradle plugin Latest support The project in using an incompatible version(AGP 7.3.0) of the Android Gradle plugin Lat 淩亂°似流年/ 2023年09月23日 16:16/ 0 赞/ 203 阅读
相关 agp模式_AGP的完整形式是什么? agp模式 AGP:加速图形端口 (AGP: Accelerated Graphics Port ) AGP is an abbreviation of the "Ac 叁歲伎倆/ 2023年03月05日 09:50/ 0 赞/ 121 阅读
相关 AGP tramsform Transform 是什么 `Transform` 是`AGP`提供了一个`API`,可以在java编译成class后进行一系列的转化,如`插桩`和埋点统计等。 我们看 阳光穿透心脏的1/2处/ 2022年11月08日 14:23/ 0 赞/ 202 阅读
相关 agp c语言课程,网易云课堂_C语言程序设计进阶_第一周:数据类型:整数类型、浮点类型、枚举类型_1计算分数精确值... 1 计算分数精确值(10分) 题目内容: 由于计算机内部表达方式的限制,浮点运算都有精度问题,为了得到高精度的计算结果,就需要自己设计实现方法。 (0,1)之间的任何浮 梦里梦外;/ 2022年10月15日 05:53/ 0 赞/ 103 阅读
相关 计算机中agp显卡的原理,电脑安装agp显卡的具体方法【图文】 AGP显卡是什么?很多小伙伴不是很清楚,它的高数据输出可以跟上很多游戏的数据。电脑显卡是运作中非常重要的,它代表着对于图形的处理,每时每刻都会有大量的数据。那么在电脑怎么安装a 本是古典 何须时尚/ 2022年08月29日 14:59/ 0 赞/ 317 阅读
还没有评论,来说两句吧...