You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JaCoCo 0.8.7适配Android API 32编译时无覆盖率结果问题

问题排查与解决

核心原因

compileSdkVersion 升级到32时,配套要求的Android Gradle Plugin(AGP)最低版本为7.2,该版本AGP默认将编译输出字节码版本升级至Java 11,同时调整了编译产物的输出路径;旧多模块JaCoCo聚合脚本普遍存在三个兼容问题:一是使用的JaCoCo版本低于0.8.8,无法解析Java 11字节码,不能正确识别插桩后的类文件与测试执行数据;二是脚本硬编码了旧版AGP的类文件输出路径,升级后扫描不到有效编译产物;三是默认聚合任务不会校验子模块是否产出有效覆盖率数据,只要任务链路执行完成就会返回成功状态,最终出现无任何统计结果的现象。

排查步骤

  • 校验JaCoCo版本:查看根目录构建脚本中配置的JaCoCo版本,确认是否低于0.8.8,低版本JaCoCo完全不支持AGP 7.2+与Java 11字节码解析。
  • 检查子模块执行数据:逐个进入子模块的build/jacoco/目录,确认测试任务执行后是否生成对应变体的.exec文件,若文件大小仅为几KB,说明插桩阶段未采集到任何有效执行数据。
  • 校验路径配置:检查聚合脚本中配置的类文件、源码文件扫描路径,确认是否硬编码了旧版AGP的intermediates/classes/路径,该路径在AGP 7.2+中已被调整为jar包输出格式,旧路径无法扫描到有效类文件。
  • 开启详细日志定位具体报错:执行覆盖率任务时添加--info参数,过滤日志中JaCoCo关键字,默认日志等级下被隐藏的无法读取执行数据、类文件缺失调试信息等警告会被打印,可直接定位具体失败点。

修复方案

  • 升级JaCoCo到兼容版本:在根目录build.gradle中固定JaCoCo版本为0.8.8及以上,不要使用AGP内置的JaCoCo版本避免版本不一致,配置参考:
buildscript {
    dependencies {
        classpath "org.jacoco:org.jacoco.core:0.8.11"
    }
}
// 所有模块应用JaCoCo时强制指定版本
jacoco {
    toolVersion = "0.8.11"
}
  • 重构聚合脚本的路径扫描逻辑,摒弃硬编码路径,通过AGP官方提供的Variant API获取各模块编译产物、源码、执行数据路径,适配不同AGP与compileSdk版本的路径变更,核心配置参考:
def coverageSourceDirs = []
def coverageClassCollection = files()
def coverageExecCollection = files()

subprojects {
    // 处理应用模块
    plugins.withId('com.android.application') {
        android.applicationVariants.all { variant ->
            if (variant.name == "debug") {
                // 收集Java、Kotlin源码路径
                variant.sourceSets.each { sourceSet ->
                    coverageSourceDirs.addAll(sourceSet.java.srcDirs)
                    coverageSourceDirs.addAll(sourceSet.kotlin.srcDirs)
                }
                // 通过AGP工件API获取编译后所有类文件(含jar格式产物)
                def classArtifact = variant.artifacts.get(com.android.build.api.artifact.SingleArtifact.ALL_CLASSES_JARS)
                coverageClassCollection.from(classArtifact)
                // 收集所有exec执行数据
                coverageExecCollection.from(fileTree(dir: buildDir, includes: ["**/jacoco/*.exec"]))
            }
        }
    }
    // 处理库模块
    plugins.withId('com.android.library') {
        android.libraryVariants.all { variant ->
            if (variant.name == "debug") {
                variant.sourceSets.each { sourceSet ->
                    coverageSourceDirs.addAll(sourceSet.java.srcDirs)
                    coverageSourceDirs.addAll(sourceSet.kotlin.srcDirs)
                }
                def classArtifact = variant.artifacts.get(com.android.build.api.artifact.SingleArtifact.ALL_CLASSES_JARS)
                coverageClassCollection.from(classArtifact)
                coverageExecCollection.from(fileTree(dir: buildDir, includes: ["**/jacoco/*.exec"]))
            }
        }
    }
}

// 注册聚合报告任务
tasks.register("jacocoMergeDebugReport", JacocoReport) {
    dependsOn coverageExecCollection.builtBy
    sourceDirectories.setFrom(files(coverageSourceDirs))
    classDirectories.setFrom(coverageClassCollection)
    executionData.setFrom(coverageExecCollection)

    // 排除无需统计的自动生成类、R类、BuildConfig类
    classDirectories.setFrom(classDirectories.files.collect {
        fileTree(dir: it, excludes: [
                '**/R.class',
                '**/R$*.class',
                '**/BuildConfig.*',
                '**/Manifest*.*'
        ])
    })

    reports {
        html.required.set(true)
        xml.required.set(true)
        csv.required.set(false)
    }
}
  • 配置测试任务兼容参数,在各模块的android配置块中添加单元测试配置,解决JaCoCo插桩与JDK内置类、Android测试框架的字节码冲突:
android {
    testOptions {
        unitTests.all {
            jvmArgs "-noverify"
            jacoco.includeNoLocationClasses = true
            jacoco.excludes = ['jdk.internal.*']
        }
    }
}
  • 清理缓存后重新执行任务:配置修改完成后先执行./gradlew clean清空所有旧构建产物,再执行聚合报告任务,避免旧缓存的无效类文件、exec数据干扰结果生成。

内容的提问来源于stack exchange,提问作者Ralf Wickum

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 21:06:26