🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 自定义 gradle 插件 ### 推荐阅读 [Gradle自定义插件以及发布方法](https://www.jianshu.com/p/d1d7fd48ff0b) ### 简介 自定义插件基于源码放置可以分为3种: 1. Build script:源码放置在模块内的 build.gradle 中 2. buildSrc project:源码放置在 rootProjectDir/buildSrc/src/main/groovy目录内(也就是工程根目录下创建 buildSrc 目录) 3. Standalone project:使用单独的一个工程/模块创建我们的 [Gradle](https://gradle.org/guides/) 插件,这种方法会构建和发表一个JAR文件,可以提供给多工程构建和其他开发者共同使用 ### 实践 ~~~ public class SimpleGradlePlugin implements Plugin<Project> { void apply(Project project) { note() //create an extension object:Whyn,so others can config via Whyn project.extensions.create("simpleGradle", SimpleGradleExtension) project.task('simpleGradle'){ group = "test" description = "gradle Standalone project demo,shares everywhere" doLast{ println '**************************************' println "$project.simpleGradle.description" println '**************************************' } } } private void note(){ println '------------------------' println 'apply StandAlonePlugin' println '------------------------' } } class SimpleGradleExtension { String description = 'default description' } ~~~ ## Transform ### 推荐阅读 [官网](http://tools.android.com/tech-docs/new-build-system/transform-api) [Gradle自定义插件以及发布方法](https://www.jianshu.com/p/d1d7fd48ff0b) ### 简介 Gradle Transform是Android官方提供给开发者在项目构建阶段即由class到dex转换期间修改class文件的一套api ![](https://img.kancloud.cn/15/b4/15b4aace81af312b66ce4b3ac5028f44_900x1090.png) 每个Transform其实都是一个gradle task,Android编译器中的TaskManager将每个Transform串连起来,第一个Transform接收来自javac编译的结果,以及已经拉取到在本地的第三方依赖(jar. aar),还有resource资源,注意,这里的resource并非android项目中的res资源,而是asset目录下的资源。这些编译的中间产物,在Transform组成的链条上流动,每个Transform节点可以对class进行处理再传递给下一个Transform。我们常见的混淆,Desugar等逻辑,它们的实现如今都是封装在一个个Transform中,而我们自定义的Transform,会插入到这个Transform链条的最前面。 ![](https://img.kancloud.cn/39/e3/39e3a5eb43adacef93fcd888687fd457_1020x634.png) ### 实践 #### 1 引入下面这个依赖 ~~~ compile 'com.android.tools.build:transform-api:1.5.0' ~~~ #### 2 自定义的Transform ~~~ //Transform 就是把输入的 .class 文件转变成目标字节码文件。 public class AutoTransform extends Transform { Project project public AutoTransform(Project project) { this.project = project } @Override String getName() { return "AutoTrack" } @Override Set<QualifiedContent.ContentType> getInputTypes() { // 要处理的数据类型 // TransformManager.CONTENT_CLASS 编译后的字节码,有可能是jar也有可能是目录 // TransformManager.CONTENT_RESOURCES 标准java资源 return TransformManager.CONTENT_CLASS } @Override Set<? super QualifiedContent.Scope> getScopes() { // 作用域 // Scope.PROJECT 只处理当前项目 // Scope.SUB_PROJECTS 只处理子项目 // Scope.PROJECT_LOCAL_DEPS 只处理当前项目本地依赖 jar aar // Scope.SUB_PROJECTS_LOCAL_DEPS 只处理子项目本地依赖 jar aar // Scope.EXTERNAL_LIBRARIES 只处理外部的依赖 return TransformManager.SCOPE_FULL_PROJECT } @Override boolean isIncremental() { // 是否增量构建? return true } // TransformInput 是指这些输入文件的抽象。它包括两部分: // 1)DirectoryInput 集合 // 是指以源码方式参与项目编译的所有目录结构及其目录下 的源码文件。 // 2)JarInput 集合 // 是指以 jar 包方式参与项目编译的所有本地 jar 包和远程 jar 包。 // // TransformOutputProvider是指 Transform 的输出,通过它可以获取输出路径。 @Override void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { Collection<TransformInput> inputs = transformInvocation.inputs TransformOutputProvider outputProvider = transformInvocation.outputProvider printlnJarAndDir(inputs) inputs.each { TransformInput input -> input.directoryInputs.each { DirectoryInput directoryInput -> handleDirectoryInput(directoryInput, outputProvider) } input.jarInputs.each { JarInput jarInput -> //处理jarInputs handleJarInputs(jarInput, outputProvider) } } } } ~~~ #### 3 自定义的Transform ~~~ class SimpleTransformPlugin implements Plugin<Project> { void apply(Project project) { def android = project.extensions.getByType(AppExtension) android.registerTransform(new AutoTransform(project)) } } ~~~ ## ASM ### 推荐阅读 [官网](https://asm.ow2.io/) [一起玩转Android项目中的字节码](http://quinnchen.cn/2018/09/13/2018-09-13-asm-transform/) [Android】函数插桩(Gradle + ASM)](https://www.jianshu.com/p/16ed4d233fd1) ### 原理和简介 JVM平台上,处理字节码的框架最常见的就三个,ASM,Javasist,AspectJ。最推荐选择ASM,因为使用它可以更底层地处理字节码的每条命令,处理速度、内存占用,也优于其他两个框架。 ASM处理涉及三个类 ClassReader、ClassWriter、ClassVisitor ~~~ static void handleDirectoryInput(DirectoryInput directoryInput, TransformOutputProvider outputProvider, MethodIdFactory factory) { //是否是目录 if (directoryInput.file.isDirectory()) { //列出目录所有文件(包含子文件夹,子文件夹内文件) directoryInput.file.eachFileRecurse { File file -> def name = file.name if (name.endsWith(".class") && !name.startsWith("R\$") && !"R.class".equals(name) && !"BuildConfig.class".equals(name) && !name.contains("Manifest") ) { // ClassReader负责读取class字节码 ClassReader classReader = new ClassReader(file.bytes) ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS) //代理自己的ClassVisitor ClassVisitor cv = new MethodStatClassVisitor(classWriter, factory) //真正触发这个逻辑就是通过ClassWriter的accept方式。 classReader.accept(cv, ClassReader.EXPAND_FRAMES) //我们通过ClassWriter的toByteArray(),将从ClassReader传递到ClassWriter的字节码导出,写入新的文件即可 byte[] code = classWriter.toByteArray() FileOutputStream fos = new FileOutputStream( file.parentFile.absolutePath + File.separator + name) fos.write(code) fos.close() } } } //处理完输入文件之后,要把输出给下一个任务 def dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY) FileUtils.copyDirectory(directoryInput.file, dest) } ~~~ ClassReader负责读取class字节码,将字节码的每个细节按顺序通过接口的方式,传递给然后ClassReader通过一个ClassVisitor接口,将字节码的每个细节按顺序通过接口的方式,传递给ClassVisitor(你会发现ClassVisitor中有多个visitXXXX接口),这个过程就像ClassReader带着ClassVisitor游览了class字节码的每一个指令。 ClassWriter我们可以看它源码,其实就是一个ClassVisitor的实现。 ClassWriter的toByteArray(),将从ClassReader传递到ClassWriter的字节码导出,写入新的文件即可。这就完成了class文件的复制 ![](https://img.kancloud.cn/3b/a0/3ba0ea6fcd0b0a0eb9cd5291549a29b3_900x524.png) ![](https://img.kancloud.cn/f0/21/f02130f5864a1a117a276d451058973d_900x524.png) 自定义 ## 应用案例