Skip to content

传递依赖排除

源:Gradle 官方文档 - Excluding transitive dependencies

传递依赖是依赖管理中最常见的场景,理解如何正确排除和管理传递依赖,能有效避免版本冲突和减小应用体积。

传递依赖机制

什么是传递依赖

场景

app -> retrofit:2.11.0 -> okhttp:4.12.0
                       -> gson:2.10.1

当你添加 retrofit 依赖时,它的依赖(okhttpgson)会自动添加到项目中,这些就是传递依赖。

传递依赖的作用

kotlin
dependencies {
    implementation("com.squareup.retrofit2:retrofit:2.11.0")
    // 自动获得:
    // - okhttp:4.12.0
    // - gson:2.10.1
}

优点

  • 自动管理依赖
  • 避免手动添加
  • 确保版本兼容

缺点

  • 可能引入不需要的库
  • 可能导致版本冲突
  • 可能增加APK体积

排除传递依赖

exclude 排除特定依赖

排除单个传递依赖

kotlin
dependencies {
    implementation("com.squareup.retrofit2:retrofit:2.11.0") {
        exclude(group = "com.squareup.okhttp3", module = "okhttp")
    }
}

排除多个传递依赖

kotlin
dependencies {
    implementation("com.example:library:1.0") {
        exclude(group = "com.google.code.gson", module = "gson")
        exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
    }
}

仅指定 group

kotlin
dependencies {
    implementation("com.example:library:1.0") {
        exclude(group = "com.android.support")  // 排除整个group
    }
}

禁用所有传递依赖

kotlin
dependencies {
    implementation("com.example:library:1.0") {
        isTransitive = false
    }
}

使用场景

  • 仅需要库本身
  • 手动管理所有依赖
  • 精确控制依赖树

全局排除

配置级别排除

排除所有配置

kotlin
configurations.all {
    exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk7")
}

排除特定配置

kotlin
configurations.named("implementation") {
    exclude(group = "com.google.guava", module = "listenablefuture")
}

排除运行时配置

kotlin
configurations.named("runtimeClasspath") {
    exclude(group = "androidx.legacy", module = "legacy-support-v4")
}

实战案例

案例1:Kotlin stdlib 冲突

问题:多个库引入不同版本的 Kotlin stdlib

app -> lib-a -> kotlin-stdlib:1.8.0
    -> lib-b -> kotlin-stdlib:1.9.0

解决方案

kotlin
configurations.all {
    exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk7")
    exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
}

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
}

案例2:AndroidX 迁移

问题:旧库依赖 Support Library

app -> androidx.appcompat:appcompat:1.6.1
    -> old-lib:1.0 -> com.android.support:support-v4:28.0.0

解决方案

kotlin
dependencies {
    implementation("com.example:old-lib:1.0") {
        exclude(group = "com.android.support")
    }
}

或启用 Jetifier:

properties
# gradle.properties
android.useAndroidX=true
android.enableJetifier=true

案例3:重复类冲突

问题:两个库包含相同的类

app -> lib-a -> guava:30.0-android
    -> lib-b -> listenablefuture:1.0

错误

Duplicate class com.google.common.util.concurrent.ListenableFuture

解决方案

kotlin
configurations.all {
    exclude(group = "com.google.guava", module = "listenablefuture")
}

案例4:日志库冲突

问题:多个日志实现

app -> lib-a -> slf4j-android:1.7.36
    -> lib-b -> logback-android:2.0.0

解决方案

kotlin
dependencies {
    implementation("com.example:lib-a:1.0") {
        exclude(group = "org.slf4j")
    }
    implementation("com.example:lib-b:2.0") {
        exclude(group = "ch.qos.logback")
    }
    
    // 统一使用一个日志库
    implementation("org.slf4j:slf4j-android:1.7.36")
}

替换传递依赖

依赖替换

替换特定模块

kotlin
configurations.all {
    resolutionStrategy {
        dependencySubstitution {
            substitute(module("com.example:old-lib"))
                .using(module("com.example:new-lib:2.0"))
                .because("迁移到新库")
        }
    }
}

替换整个 group

kotlin
configurations.all {
    resolutionStrategy {
        dependencySubstitution {
            all {
                if (requested.group == "com.android.support") {
                    useTarget("androidx.legacy:legacy-support-v4:1.0.0")
                }
            }
        }
    }
}

强制使用特定版本

使用 force

kotlin
configurations.all {
    resolutionStrategy {
        force("com.squareup.okhttp3:okhttp:4.12.0")
    }
}

与排除的区别

  • exclude:完全移除依赖
  • force:强制使用特定版本

使用依赖约束

kotlin
dependencies {
    constraints {
        implementation("com.squareup.okhttp3:okhttp:4.12.0") {
            because("统一 OkHttp 版本")
        }
    }
}

查看传递依赖

依赖树

bash
./gradlew :app:dependencies --configuration implementation

输出

implementation
+--- com.squareup.retrofit2:retrofit:2.11.0
|    +--- com.squareup.okhttp3:okhttp:4.12.0
|    |    \--- com.squareup.okio:okio:3.6.0
|    \--- com.google.code.gson:gson:2.10.1

依赖洞察

bash
./gradlew :app:dependencyInsight --dependency okhttp

输出

com.squareup.okhttp3:okhttp:4.12.0
   variant "runtime" [
      org.gradle.status = release
   ]

com.squareup.okhttp3:okhttp:4.12.0
\--- com.squareup.retrofit2:retrofit:2.11.0
     \--- implementation

最佳实践

最小化排除

  • 仅排除必要的依赖
  • 避免全局排除
  • 记录排除原因

使用 BOM 替代排除

  • 优先使用 BOM 统一版本
  • 减少手动管理
  • 避免遗漏

验证排除效果

  • 检查依赖树
  • 确认库是否正常工作
  • 测试所有功能

文档化决策

kotlin
dependencies {
    implementation("com.example:library:1.0") {
        // 排除旧版本 Support Library,使用 AndroidX
        exclude(group = "com.android.support")
    }
}

定期审查

  • 检查是否还需要排除
  • 更新到不需要排除的版本
  • 清理无用的排除规则

常见问题

排除后运行时错误

问题:排除依赖后应用崩溃

原因:排除了必需的依赖

解决

  • 检查堆栈跟踪
  • 确认是否需要该依赖
  • 仅排除真正冲突的依赖

排除不生效

问题:排除规则不起作用

原因

  • 配置错误
  • 其他路径引入了依赖

解决

  • 检查依赖树
  • 使用 dependencyInsight 查看所有引入路径
  • 在所有路径上排除

APK 体积未减小

问题:排除依赖后 APK 仍然很大

原因:其他依赖引入了相同的库

解决

  • 使用 APK Analyzer 分析
  • 检查所有传递依赖
  • 使用全局排除

工具推荐

Gradle 依赖分析

bash
# 查看依赖树
./gradlew :app:dependencies

# 查看特定依赖
./gradlew :app:dependencyInsight --dependency <name>

# 生成报告
./gradlew :app:htmlDependencyReport

Android Studio

  • Gradle Tool Window → Dependencies
  • APK Analyzer
  • 查看依赖图

插件

kotlin
plugins {
    id("com.autonomousapps.dependency-analysis") version "1.30.0"
}
bash
./grad lew buildHealth