如何修改依赖库中Gradle Task的实现以适配配置缓存?
问题描述
Android项目使用Gradle构建,依赖的库A包含MPManifestTask任务,每次构建都会执行。开启Gradle配置缓存后,构建日志报错:
task:app:mpProcessItDebugit-debugManifest of type com.alipay.mpaas.easy.config.MPManifestTask
invocation of Task.project at execution time is unsupported. invocation of Task.project at execution time is unsupported.
查看该任务的核心实现代码:
@TaskAction public void executeTask() throws Exception { Logger logger = this.getProject().getLogger(); logger.info("======================ANDROIDMANIFEST CONVERTER START================================"); logger.info(" start to convert" + this.appAndroidManifestFile); }
问题根源是Gradle配置缓存禁止在任务执行阶段调用getProject(),正确的做法是直接使用任务自身的getLogger()方法,但无法直接修改依赖库的源码,需要通过其他方式修复。
可行解决方案
方案1:替换任务实现(推荐)
创建一个修复后的自定义任务,替换依赖库中的原任务,保留原有逻辑的同时修复配置缓存问题。
步骤1:在buildSrc中定义修复任务
在项目的buildSrc模块(若没有则新建)中创建自定义任务类,复制原任务的属性和逻辑,仅修改日志获取方式:
import org.gradle.api.DefaultTask; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.TaskAction; import org.gradle.api.logging.Logger; public class FixedMPManifestTask extends DefaultTask { @InputFile private File appAndroidManifestFile; public File getAppAndroidManifestFile() { return appAndroidManifestFile; } public void setAppAndroidManifestFile(File appAndroidManifestFile) { this.appAndroidManifestFile = appAndroidManifestFile; } @TaskAction public void executeTask() throws Exception { Logger logger = getLogger(); // 替换为任务自身的getLogger() logger.info("======================ANDROIDMANIFEST CONVERTER START================================"); logger.info(" start to convert" + this.appAndroidManifestFile); // 若原任务有其他逻辑,完整复制过来 } }
步骤2:在App模块中替换任务
在app/build.gradle中添加任务替换逻辑,禁用原任务并让依赖链指向修复后的任务:
tasks.withType(com.alipay.mpaas.easy.config.MPManifestTask) { originalTask -> // 创建修复后的任务 def fixedTaskName = "fixed${originalTask.name.capitalize()}" def fixedTask = tasks.create(fixedTaskName, FixedMPManifestTask) { // 复制原任务的输入输出和属性 appAndroidManifestFile = originalTask.appAndroidManifestFile inputs.files originalTask.inputs.files outputs.files originalTask.outputs.files // 复制依赖关系 dependsOn originalTask.dependsOn mustRunAfter originalTask.mustRunAfter } // 禁用原任务 originalTask.enabled = false // 将所有依赖原任务的任务改为依赖修复任务 tasks.matching { it.dependsOn.contains(originalTask) }.each { dependentTask -> dependentTask.dependsOn fixedTask } }
方案2:字节码增强动态修改
如果原任务逻辑复杂,复制成本高,可以使用ASM字节码框架在类加载时动态修改executeTask方法,替换违规调用。
步骤1:在buildSrc中添加ASM依赖
// buildSrc/build.gradle dependencies { implementation 'org.ow2.asm:asm:9.5' implementation 'org.ow2.asm:asm-commons:9.5' }
步骤2:编写类转换逻辑
创建Gradle插件,注册类转换器,修改MPManifestTask的字节码:
import org.gradle.api.Plugin; import org.gradle.api.Project; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map; public class MPManifestFixPlugin implements Plugin<Project> { @Override public void apply(Project project) { // 拦截类加载,修改MPManifestTask的字节码 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { // 读取原类字节码 InputStream is = classLoader.getResourceAsStream("com/alipay/mpaas/easy/config/MPManifestTask.class"); ClassReader cr = new ClassReader(is); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES); ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); // 只处理executeTask方法 if ("executeTask".equals(name) && "()V".equals(desc)) { return new MethodVisitor(Opcodes.ASM9, mv) { @Override public void visitMethodInsn(int opcode, String owner, String methodName, String methodDesc, boolean isInterface) { // 替换this.getProject().getLogger()为this.getLogger() if (opcode == Opcodes.INVOKEVIRTUAL && "getProject".equals(methodName)) { // 跳过getProject()调用 return; } if (opcode == Opcodes.INVOKEVIRTUAL && "getLogger".equals(methodName) && owner.equals("org/gradle/api/Project")) { // 将调用目标改为当前任务类 super.visitMethodInsn(opcode, "com/alipay/mpaas/easy/config/MPManifestTask", methodName, "()Lorg/gradle/api/logging/Logger;", false); return; } super.visitMethodInsn(opcode, owner, methodName, methodDesc, isInterface); } }; } return mv; } }; cr.accept(cv, ClassReader.EXPAND_FRAMES); byte[] modifiedClassBytes = cw.toByteArray(); // 利用反射将修改后的类注入类加载器 Field field = ClassLoader.class.getDeclaredField("classes"); field.setAccessible(true); Map<String, Class<?>> classes = (Map<String, Class<?>>) field.get(classLoader); classes.remove("com.alipay.mpaas.easy.config.MPManifestTask"); // 重新加载修改后的类 Method defineMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); defineMethod.setAccessible(true); defineMethod.invoke(classLoader, "com.alipay.mpaas.easy.config.MPManifestTask", modifiedClassBytes, 0, modifiedClassBytes.length); } catch (Exception e) { project.getLogger().error("Failed to fix MPManifestTask: ", e); } } }
步骤3:应用插件
在settings.gradle中配置插件仓库,然后在app/build.gradle中应用:
// settings.gradle pluginManagement { repositories { gradlePluginPortal() mavenLocal() } } // app/build.gradle plugins { id 'com.android.application' id 'your.package.mp-manifest-fix' }
方案3:提交修复PR(最彻底)
如果该依赖库是开源项目,直接修改源码将this.getProject().getLogger()改为this.getLogger(),提交PR给维护者,等待合并后升级依赖版本即可。
内容的提问来源于stack exchange,提问作者jim

