欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Gradle使用详解(二)--添加依赖

程序员文章站 2022-06-06 09:55:08
...

Android Gradle使用技巧-gradle使用详细介绍
Android Gradle使用详解(一)–设置applicationId

利用 Android Studio 中的 Gradle 构建系统,您可以轻松地将外部二进制文件或其他库模块作为依赖项添加到您的 build 中。这些依赖项可位于您的计算机上或远程代码库中,并且它们声明的所有传递依赖项也会自动包含在内。

注意:指定依赖项时,不应使用动态版本号,如 ‘com.android.tools.build:gradle:3.+’。使用此功能可能会导致意外的版本更新和难以解析版本差异。

一、依赖项类型

如需向您的项目添加依赖项,请在 build.gradle 文件的 dependencies 代码块中指定依赖项配置,如 implementation。
例如,应用模块的以下 build.gradle 文件包含三种不同类型的依赖项:

apply plugin: 'com.android.application'

android { ... }

dependencies {
    // 对本地库模块的依赖
    implementation project(":mylibrary")
    //或者
    implementation project(path: ":mylibrary")

    // 对本地二进制文件的依赖
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    
    // 对远程二进制文件的依赖
    implementation 'com.example.android:app-magic:12.3'
}

其中每种依赖项配置都请求不同种类的库依赖项,如下所示:

1、本地库模块依赖项

    implementation project(':mylibrary')

这声明了对一个名为“mylibrary”(此名称必须与在您的 settings.gradle 文件中使用 include: 定义的库名称相符)的 Android 库模块的依赖关系。在构建您的应用时,构建系统会编译该库模块,并将生成的编译内容打包到 APK 中。

2、本地二进制文件依赖项

    implementation fileTree(dir: 'libs', include: ['*.jar'])

Gradle 声明了对项目的 module_name/libs/ 目录中 JAR 文件的依赖关系(因为 Gradle 会读取 build.gradle 文件的相对路径)。
或者,您也可以按如下方式指定各个文件:

   implementation files('libs/foo.jar', 'libs/bar.jar')

3、远程二进制文件依赖项

    implementation 'com.example.android:app-magic:12.3'

这实际上是以下代码的简写形式:

    implementation group: 'com.example.android', name: 'app-magic',     version: '12.3'

这声明了对“com.example.android”命名空间组内的 12.3 版“app-magic”库的依赖关系。

注意:此类远程依赖项要求您声明适当的远程代码库,Gradle 应在其中查找相应的库。如果本地不存在相应的库,则当 build 需要它时(例如,当您点击 Sync Project with Gradle Files 图标或运行 build 时),Gradle 会从远程站点提取它。

二、依赖项配置

在 dependencies 代码块内,您可以从多种不同的依赖项配置中选择其一(如上面所示的 implementation)来声明库依赖项。每种依赖项配置都向 Gradle 提供了有关如何使用该依赖项的不同说明。下表介绍了您可以对 Android 项目中的依赖项使用的各种配置。此表还将这些配置与自 Android Gradle 插件 3.0.0 起弃用的配置进行了比较

新配置 已弃用配置 行为
implementation compile Gradle 会将依赖项添加到编译类路径,并将依赖项打包到构建输出。不过,当您的模块配置 implementation 依赖项时,会让 Gradle 了解您不希望该模块在编译时将该依赖项泄露给其他模块。也就是说,其他模块只有在运行时才能使用该依赖项。 注意:使用此依赖项配置代替 api 或 compile(已弃用)可以显著缩短构建时间,因为这样可以减少构建系统需要重新编译的模块数。例如,如果 implementation 依赖项更改了其 API,Gradle 只会重新编译该依赖项以及直接依赖于它的模块。大多数应用和测试模块都应使用此配置。
api compile Gradle 会将依赖项添加到编译类路径和构建输出。当一个模块包含 api 依赖项时,会让 Gradle 了解该模块要以传递方式将该依赖项导出到其他模块,以便这些模块在运行时和编译时都可以使用该依赖项。 注意:此配置的行为类似于 compile(现已弃用),但使用它时应格外小心,只能对您需要以传递方式导出到其他上游消费者的依赖项使用它。这是因为,如果 api 依赖项更改了其外部 API,Gradle 会在编译时重新编译所有有权访问该依赖项的模块。因此,拥有大量的 api 依赖项会显著增加构建时间。除非要将依赖项的 API 公开给单独的模块,否则库模块应改用 implementation 依赖项。
compileOnly provided Gradle 只会将依赖项添加到编译类路径(也就是说,不会将其添加到构建输出)。如果您创建 Android 模块时在编译期间需要相应依赖项,但它在运行时可有可无,此配置会很有用。如果您使用此配置,那么您的库模块必须包含一个运行时条件,用于检查是否提供了相应依赖项,然后适当地改变该模块的行为,以使该模块在未提供相应依赖项的情况下仍可正常运行。这样做不会添加不重要的瞬时依赖项,因而有助于减小最终 APK 的大小。注意:您不能将 compileOnly 配置与 AAR 依赖项配合使用。
runtimeOnly apk Gradle 只会将依赖项添加到构建输出,以便在运行时使用。也就是说,不会将其添加到编译类路径。
annotationProcessor compile 如需添加对作为注释处理器的库的依赖关系,您必须使用 annotationProcessor 配置将其添加到注释处理器类路径。这是因为,使用此配置可以将编译类路径与注释处理器类路径分开,从而提高构建性能。如果 Gradle 在编译类路径上找到注释处理器,则会禁用避免编译功能,这样会对构建时间产生负面影响(Gradle 5.0 及更高版本会忽略在编译类路径上找到的注释处理器)。如果 JAR 文件包含以下文件,则 Android Gradle 插件会假定依赖项是注释处理器:META-INF/services/javax.annotation.processing.Processor。如果插件检测到编译类路径上包含注释处理器,则会生成构建错误。
lintChecks 使用此配置可以添加您希望 Gradle 在构建项目时执行的 lint 检查.注意:使用 Android Gradle 插件 3.4.0 及更高版本时,此依赖项配置不再将 lint 检查打包在 Android 库项目中。如需将 lint 检查依赖项包含在 AAR 库中,请使用下面介绍的 lintPublish 配置。
lintPublish 在 Android 库项目中使用此配置可以添加您希望 Gradle 编译成 lint.jar 文件并打包在 AAR 中的 lint 检查。这会使得使用 AAR 的项目也应用这些 lint 检查。如果您之前使用 lintChecks 依赖项配置将 lint 检查包含在已发布的 AAR 中,则需要迁移这些依赖项以改用 lintPublish 配置。

1.为构建变体添加依赖

以上配置会将依赖项应用于所有构建变体。如果您只想为特定的构建变体源代码集或测试源代码集声明依赖项,则必须将配置名称的首字母大写,并在其前面加上构建变体或测试源代码集的名称作为前缀。

例如,如需只向“free”产品变种添加 implementation 依赖项(使用远程二进制文件依赖项),请使用如下所示的代码:

dependencies {
    freeImplementation 'com.google.firebase:firebase-ads:9.8.0'
}

不过,如果您想为将产品变种和构建类型组合在一起的变体添加依赖项,就必须在 configurations 代码块中初始化配置名称。以下示例向“freeDebug”构建变体添加了 runtimeOnly 依赖项(使用本地二进制文件依赖项):

configurations {
    // 为freeDebugRuntimeOnly依赖项初始化一个占位符配置。
    freeDebugRuntimeOnly {}
}

dependencies {
    freeDebugRuntimeOnly fileTree(dir: 'libs', include: ['*.jar'])
}

如需为本地测试和插桩测试添加 implementation 依赖项,请使用如下所示的代码:

dependencies {
    // 仅为本地测试添加远程二进制依赖项。
    testImplementation 'junit:junit:4.12'

    // 仅为已检测的测试APK添加远程二进制依赖项。
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

不过,某些配置在这种情况下没有意义。例如,因为其他模块不能依赖于 androidTest,所以如果您使用 androidTestApi 配置,会收到以下警告:

WARNING: Configuration 'androidTestApi' is obsolete and has been replaced with
'androidTestImplementation'.

2.使用变体感知型依赖项管理机制

Android 插件 3.0.0 及更高版本包含一种新的依赖项机制,该机制可在使用库时自动匹配变体。这意味着,应用的 debug 变体会自动使用库的 debug 变体,依此类推。在使用变种时,这种机制也同样适用 - 应用的 freeDebug 变体将使用库的 freeDebug 变体。
Android 插件 3.0.0 及更高版本包含一种新的依赖项机制,该机制可在使用库时自动匹配变体。这意味着,应用的 debug 变体会自动使用库的 debug 变体,依此类推。在使用变种时,这种机制也同样适用 - 应用的 freeDebug 变体将使用库的 freeDebug 变体。

为了让插件准确匹配变体,您需要在无法进行直接匹配的情况下提供匹配回退机制。不妨假设您的应用配置了一个名为“staging”的构建类型,但该应用的一个库依赖项没有进行相应配置。当插件尝试构建“staging”版本的应用时,它不知道要使用哪个版本的库,因此您将看到一条与以下内容类似的错误消息:

Error:Failed to resolve: Could not resolve project :mylibrary.Required by: project :app

3.解决与变体匹配相关的构建错误

  1. 编译错误原因:您的应用包含库依赖项不包含的构建类型
    例如,您的应用包含“staging”版本类型,但依赖项仅包含“debug”和“release”版本类型。
    解决方案:使用 matchingFallbacks 为给定的构建类型指定替代匹配,如下所示:
    android {
    buildTypes {
        debug {}
        release {}
        staging {
            //指定后备的构建类型的排序列表,当依赖项不包含“ staging”构建类型。您可以指定一个,然后插件选择第一个可用的构建类型添在依赖项。
            matchingFallbacks = ['debug', 'qa', 'release']
        }
    }
    

}
```

  1. 编译错误原因:对于应用及其库依赖项中均存在的给定变种维度,您的应用包含库不包含的变种。
    例如,您的应用及其库依赖项都包含“tier”变种维度。不过,应用中的“tier”维度包含“free”和“paid”变种,但依赖项中的同一维度仅包含“demo”和“paid”变种。
    解决方案:使用 matchingFallbacks 为应用的“free”产品变种指定替代匹配,如下所示:
    android {
    defaultConfig{
    // 不要在defaultConfig块中配置matchFallbacks。相反,您必须在productFlavors块中添加他;
    }
    flavorDimensions 'tier'
    productFlavors {
        paid {
            dimension 'tier'
            // 因为依赖项已在其依赖项中包含“付费”的形式“层级”维度,您无需提供后备列表代表“付费”变体。
        }
        free {
            dimension 'tier'
            matchingFallbacks = ['demo', 'trial']
        }
    }
    

}
```

  1. 编译错误原因:库依赖项包含您的应用不包含的变种维度
    例如,库依赖项包含“minApi”维度的变种,但您的应用仅包含“tier”维度的变种。因此,当您要构建“freeDebug”版本的应用时,插件不知道是使用“minApi23Debug”还是“minApi18Debug”版本的依赖项。
    解决方案:在 defaultConfig 代码块中使用 missingDimensionStrategy 指定插件应从每个缺失维度中选择的默认变种,如以下示例所示。您也可以替换在 productFlavors 代码块中的选择,让每一个变种都可以为缺失维度指定一个不同的匹配策略。
    android {
    defaultConfig{
    // 指定插件应尝试从中使用的变体的排序列表给定的尺寸下面告诉插件,当遇到包含“ minApi”维的依赖项,应选择“ minApi18”变体。您可以包括其他变体名称以提供对维度的后备列表进行排序。
    missingDimensionStrategy 'minApi', 'minApi18', 'minApi23'
    // 您应该为每个属性指定一个missingDimensionStrategy属性存在于本地依赖项中但不存在于您的应用程序中。
    missingDimensionStrategy 'abi', 'x86', 'arm64'
    }
    flavorDimensions 'tier'
    productFlavors {
        free {
            dimension 'tier'
            // 您可以覆盖产品变体的默认选择通过配置另一个missingDimensionStrategy属性进行级别用于“ minApi”维度。
            missingDimensionStrategy 'minApi', 'minApi23', 'minApi18'
        }
        paid {}
    }
    

}
```

4.排除传递依赖项

随着应用的范围不断扩大,它可能会包含许多依赖项,包括直接依赖项和传递依赖项(应用中导入的库所依赖的库)。如需排除不再需要的传递依赖项,您可以使用 exclude 关键字,如下所示:

dependencies {
    implementation('some-library') {
        exclude group: 'com.example.imgtools', module: 'native'
    }
}

三、远程代码库

当您的依赖项不是本地库或文件树时,Gradle 会在 build.gradle 文件的 repositories 代码块中指定的所有在线代码库中查找相关文件。各个代码库的列出顺序决定了 Gradle 在这些代码库中搜索各个项目依赖项的顺序。例如,如果从代码库 A 和 B 均可获得某个依赖项,而您先列出了代码库 A,则 Gradle 会从代码库 A 下载该依赖项。

默认情况下,新的 Android Studio 项目会将 Google 的 Maven 代码库和 JCenter 指定为项目的* build.gradle 文件中的代码库位置,如下所示:

allprojects {
    repositories {
        google()
        jcenter()
    }
}

如果您要从 Maven *代码库获取某些内容,则添加 mavenCentral();对于本地代码库,则使用 mavenLocal():

allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
        mavenLocal()
    }
}

或者,您也可以按如下方式声明特定的 Maven 或 Ivy 代码库:

allprojects {
    repositories {
        maven {
            url "https://repo.example.com/maven2"
        }
        maven {
            url "file://local/repo/"
        }
        ivy {
            url "https://repo.example.com/ivy"
        }
    }
}

四、依赖项顺序

依赖项的列出顺序指明了每个库的优先级:第一个库的优先级高于第二个,第二个库的优先级高于第三个,依此类推。在合并资源或将清单元素从库中合并到应用中时,此顺序很重要。
例如,如果您的项目声明以下内容:

  • 依赖 LIB_A 和 LIB_B(按此顺序)
  • LIB_A 依赖于 LIB_C 和 LIB_D(按此顺序)
  • LIB_B 也依赖于 LIB_C
    那么,扁平型依赖项顺序将如下所示:
  1. LIB_A
  2. LIB_D
  3. LIB_B
  4. LIB_C
    这可以确保 LIB_A 和 LIB_B 都可以替换 LIB_C;并且 LIB_D 的优先级仍高于 LIB_B,因为 LIB_A(依赖前者)的优先级高于 LIB_B。

五、查看模块依赖项

一些直接依赖项可能具有自己的依赖项。此类依赖项称为“传递依赖项”。Gradle 将会自动为您收集并添加这些传递依赖项,无需您手动逐一加以声明。Android Plugin for Gradle 提供了一项任务,用来列出 Gradle 为给定模块解析的依赖项。

对于每个模块,报告还会根据构建变体、测试源代码集和类路径对依赖项进行分组。下面是一个应用模块的依赖项示例报告,其中按该模块的调试构建变体的运行时类路径和该模块的插桩测试源代码集的编译类路径对依赖项进行了分组。
如需运行该任务,请按以下步骤操作:

  1. 依次选择 View > Tool Windows > Gradle(或点击工具窗口栏中的 Gradle 图标)。
  2. 依次展开 AppName > Tasks > android,然后双击 androidDependencies。Gradle 执行该任务后,系统应该会打开 Run 窗口以显示输出。
    显示如下:
debugRuntimeClasspath - Dependencies for runtime/packaging
+--- :mylibrary (variant: debug)
+--- com.google.android.material:material:aaa@qq.com
+--- androidx.appcompat:appcompat:aaa@qq.com
+--- androidx.constraintlayout:constraintlayout:aaa@qq.com
+--- androidx.fragment:fragment:aaa@qq.com
+--- androidx.vectordrawable:vectordrawable-animated:aaa@qq.com
+--- androidx.recyclerview:recyclerview:aaa@qq.com
+--- androidx.legacy:legacy-support-core-ui:aaa@qq.com
...

debugAndroidTest
debugAndroidTestCompileClasspath - Dependencies for compilation
+--- androidx.test.ext:junit:aaa@qq.com
+--- androidx.test.espresso:espresso-core:aaa@qq.com
+--- androidx.test:runner:aaa@qq.com
+--- junit:junit:aaa@qq.com
...

六、修复依赖项解析错误

当您向应用项目添加多个依赖项时,这些直接和传递依赖项可能会相互冲突。Android Gradle 插件会尝试妥善解决这些冲突,但有些冲突可能会导致编译时或运行时错误。

为帮助您调查是哪些依赖项导致了错误,请如上检查您的应用的依赖项树,从中查找多次出现的依赖项或版本冲突的依赖项。

如果无法轻松识别重复的依赖项,请尝试使用 Android Studio 的界面搜索包含重复类的依赖项,具体操作步骤如下:

  1. 从菜单栏中依次选择 Navigate > Class。
  2. 在弹出式搜索对话框中,确保已勾选 Include non-project items 旁边的框。
  3. 输入出现在构建错误中的类的名称。
  4. 检查结果以查找包含该类的依赖项。

下面几部分介绍您可能会遇到的不同类型的依赖项解析错误及其修复方法。

1.修复重复类错误

如果某个类多次出现在运行时类路径上,您会收到一条与以下内容类似的错误:

Program type already present com.example.MyClass

此错误通常是下列其中一种情况所致:

  • 二进制文件依赖项包含一个库,该库也作为直接依赖项包含在您的应用中。例如,您的应用声明直接依赖于库 A 和库 B,但库 A 已在其二进制文件中包含库 B。

    • 如需解决此问题,请取消将库 B 作为直接依赖项。
  • 您的应用的本地二进制文件依赖项和远程二进制文件依赖项是同一个库。

    • 如需解决此问题,请移除其中一个二进制文件依赖项。

2.解决类路径之间的冲突

当 Gradle 解析编译类路径时,会先解析运行时类路径,然后使用所得结果确定应添加到编译类路径的依赖项版本。换句话说,运行时类路径决定了下游类路径上完全相同的依赖项所需的版本号。

应用的运行时类路径还决定了 Gradle 需要对应用的测试 APK 的运行时类路径中的匹配依赖项使用的版本号。图 1 说明了类路径的层次结构。
Android Gradle使用详解(二)--添加依赖

例如,当应用使用 implementation 依赖项配置加入某个依赖项的一个版本,而库模块使用 runtimeOnly 配置加入该依赖项的另一个版本时,就可能会发生多个类路径中出现同一依赖项的不同版本的冲突。

在解析对运行时和编译时类路径的依赖关系时,Android Gradle 插件 3.3.0 及更高版本会尝试自动解决某些下游版本冲突。例如,如果运行时类路径包含库 A 版本 2.0,而编译类路径包含库 A 版本 1.0,则插件会自动将对编译类路径的依赖关系更新为库 A 版本 2.0,以避免错误。

不过,如果运行时类路径包含库 A 版本 1.0,而编译类路径包含库 A 版本 2.0,插件不会将对编译类路径的依赖关系降级为库 A 版本 1.0,您仍会收到一条与以下内容类似的错误:

Conflict with dependency 'com.example.library:some-lib:2.0' in project 'my-library'.
Resolved versions for runtime classpath (1.0) and compile classpath (2.0) differ.

如需解决此问题,请执行以下某项操作:

  • 将所需版本的依赖项作为 api 依赖项添加到库模块。也就是说,只有库模块声明相应依赖项,但应用模块也能以传递方式访问其 API。
  • 或者,您也可以同时在两个模块中声明相应依赖项,但应确保每个模块使用的版本相同。不妨考虑配置项目全局属性,以确保每个依赖项的版本在整个项目中保持一致。