AGP tramsform 阳光穿透心脏的1/2处 2022-11-08 14:23 135阅读 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 赞/ 19 阅读
相关 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 赞/ 107 阅读
相关 agp模式_AGP的完整形式是什么? agp模式 AGP:加速图形端口 (AGP: Accelerated Graphics Port ) AGP is an abbreviation of the "Ac 叁歲伎倆/ 2023年03月05日 09:50/ 0 赞/ 8 阅读
相关 AGP tramsform Transform 是什么 `Transform` 是`AGP`提供了一个`API`,可以在java编译成class后进行一系列的转化,如`插桩`和埋点统计等。 我们看 阳光穿透心脏的1/2处/ 2022年11月08日 14:23/ 0 赞/ 136 阅读
相关 agp c语言课程,网易云课堂_C语言程序设计进阶_第一周:数据类型:整数类型、浮点类型、枚举类型_1计算分数精确值... 1 计算分数精确值(10分) 题目内容: 由于计算机内部表达方式的限制,浮点运算都有精度问题,为了得到高精度的计算结果,就需要自己设计实现方法。 (0,1)之间的任何浮 梦里梦外;/ 2022年10月15日 05:53/ 0 赞/ 65 阅读
相关 计算机中agp显卡的原理,电脑安装agp显卡的具体方法【图文】 AGP显卡是什么?很多小伙伴不是很清楚,它的高数据输出可以跟上很多游戏的数据。电脑显卡是运作中非常重要的,它代表着对于图形的处理,每时每刻都会有大量的数据。那么在电脑怎么安装a 本是古典 何须时尚/ 2022年08月29日 14:59/ 0 赞/ 228 阅读
还没有评论,来说两句吧...