Save the date! Android Dev Summit is coming to Mountain View, CA on November 7-8, 2018.

迁移到 Android Plugin for Gradle 3.0.0

已知问题:如果您现在有一个使用 alpha 版 Android 插件 3.0.0(例如 3.0.0-alpha9)的 Android Studio 项目,则在您迁移到 Android 插件 3.0.0-beta4同步您的项目时,可能会遇到以下错误: Gradle project refresh failed

从菜单栏中选择 Build > Clean Project 可以解决此问题 - 您需要为每个项目仅执行一次此操作。 然后,您可以点击菜单栏中的 Sync Project ,将您的项目文件与 Gradle 同步。

如果您想要将您的项目迁移至 Android 插件 3.0.0-beta4 或更高版本,请阅读本页面,了解如何应用插件和特定版本的 Gradle,并根据一些突破性更改调整您的项目。

更新 Gradle 版本

新 Android 插件要求 Gradle 版本 4.1-rc-1 或更高版本。 如果您正在使用 Android Studio 3.0 Beta 1 或更高版本打开现有项目,请按照提示操作,将现有项目自动更新到兼容版本的 Gradle。

要手动更新 Gradle,请更新 gradle-wrapper.properties 中的网址,如下所示:

distributionUrl=\
  https\://services.gradle.org/distributions/gradle-4.1-rc-1-all.zip

应用插件

如果您正在使用 Android Studio 3.0 Beta 1 或更高版本打开现有项目,请按照提示操作,将您的项目自动更新到最新版本的 Android 插件。 要手动更新您的项目,请包含 Maven 存储区并在您的项目级 build.gradle 文件中更改插件版本,如下所示:

buildscript {
    repositories {
        ...
        // You need to add the following repository to download the
        // new plugin.
        google()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0-beta4'
    }
}

使用风味维度进行变体感知依赖项管理

插件 3.0.0 包含一项新的依赖项机制,这种机制可以在消费库时自动匹配变体。 也就是说,应用的 debug 变体将自动消费库的 debug 变体,等等。 这种机制也适用于使用风味的情况 - 应用的 redDebug 变体将消费库的 redDebug 变体。 要使这种机制发挥作用,插件现在要求属于给定风味维度的所有风味 - 即使您仅准备使用一个维度,也是如此。 否则,您将遇到以下构建错误:

Error:All flavors must now belong to a named flavor dimension.
The flavor 'flavor_name' is not assigned to a flavor dimension.

要解决此错误,请将每个风味分配至一个给定维度,如下面的示例中所示。 由于依赖项匹配现在由插件处理,您应当谨慎地为风味维度命名。 例如,如果您的所有应用和库模块均使用 foo 维度,您对插件可以匹配的风味的控制力较弱。

// Specifies a flavor dimension.
flavorDimensions "color"

productFlavors {
     red {
      // Assigns this product flavor to the 'color' flavor dimension.
      // This step is optional if you are using only one dimension.
      dimension "color"
      ...
    }

    blue {
      dimension "color"
      ...
    }
}

解决与依赖项匹配相关的构建错误

Android 插件会尝试将您的应用的每一个变体与库中的相同变体匹配。 因此,在您构建“freeDebug”版本的应用时,插件会尝试将其与“freeDebug”版本的本地库依赖项匹配。

不过,假设您的应用配置一个名称为“staging”的构建类型,但其库依赖项之一没有进行相应配置。 在插件尝试创建您的“staging”版本的应用时,它将无法了解库要使用哪一个版本,您将看到一条类似于如下的错误消息:

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

Android 插件 3.0.0-beta4 及更高版本包含一些 DSL 元素,这些 DSL 元素有助于控制插件应如何解决应用与依赖项之间无法实现直接变体匹配的情况。 下表可以帮助您确定应使用哪个 DSL 属性来解决与变体感知依赖项匹配相关的特定构建错误。

构建错误原因解决方法

您的应用包含库依赖项不包含的构建类型。

例如,您的应用包含“staging”构建类型,但依赖项仅包含“调试”和“发布”构建类型。

请注意,如果库依赖项包含您的应用不包含的构建类型,这不会引发问题。 这是因为插件在任何时候都不会从依赖项请求该构建类型。

使用 matchingFallbacks 为给定构建类型指定替代匹配,如下所示:
// In the app's build.gradle file.
android {
    buildTypes {
        debug {}
        release {}
        staging {
            // Specifies a sorted list of fallback build types that the
            // plugin should try to use when a dependency does not include a
            // "staging" build type. You may specify as many fallbacks as you
            // like, and the plugin selects the first build type that's
            // available in the dependency.
            matchingFallbacks = ['debug', 'qa', 'release']
        }
    }
}

对于应用及其库依赖项中均存在的给定风味维度,您的应用包含库不包含的风味。

例如,您的应用及其库依赖项都包含“级别”风味维度。 不过,应用中的“级别”维度包含“免费”和“付费”风味,但依赖项仅包含相同维度的“演示”和“付费”风味。

请注意,对于应用及其库依赖项中均存在的给定风味维度,如果库包含应用不包含的产品风味,这不会引发问题。 这是因为插件在任何时候都不会从依赖项请求该风味。

使用 matchingFallbacks 为应用的“免费”产品风味指定替代匹配,如下所示:
// In the app's build.gradle file.
android {
    defaultConfig{
    // Do not configure matchingFallbacks in the defaultConfig block.
    // Instead, you must specify fallbacks for a given product flavor in the
    // productFlavors block, as shown below.
  }
    flavorDimensions 'tier'
    productFlavors {
        paid {
            dimension 'tier'
            // Because the dependency already includes a "paid" flavor in its
            // "tier" dimension, you don't need to provide a list of fallbacks
            // for the "paid" flavor.
        }
        free {
            dimension 'tier'
            // Specifies a sorted list of fallback flavors that the plugin
            // should try to use when a dependency's matching dimension does
            // not include a "free" flavor. You may specify as many
            // fallbacks as you like, and the plugin selects the first flavor
            // that's available in the dependency's "tier" dimension.
            matchingFallbacks = ['demo', 'trial']
        }
    }
}

库依赖项包含应用不包含的风味维度。

例如,库依赖项包含“minApi”维度的风味, 但您的应用包含仅适用于“级别”维度的风味。 因此,当您想要构建“freeDebug”版本的应用时,插件不知道使用“minApi23Debug”还是“minApi18Debug” 版本的依赖项。

请注意,如果应用包含库依赖项不包含的风味维度,这不会引发问题。 这是因为插件仅会匹配依赖项中存在的维度的风味。 例如,如果依赖项不包含 ABI 的维度,您的“freeX86Debug”版本的应用将直接使用“freeDebug” 版本的依赖项。

defaultConfig 代码块中使用 missingDimensionStrategy 指定插件应从每个缺失维度中选择的默认风味,如下面的示例所示。 您也可以替换在 productFlavors 代码块中的选择,让每一个风味都可以为缺失维度指定一个不同的匹配策略。
// In the app's build.gradle file.
android {
    defaultConfig{
    // Specifies a sorted list of flavors that the plugin should try to use from
    // a given dimension. The following tells the plugin that, when encountering
    // a dependency that includes a "minApi" dimension, it should select the
    // "minApi18" flavor. You can include additional flavor names to provide a
    // sorted list of fallbacks for the dimension.
    missingDimensionStrategy 'minApi', 'minApi18', 'minApi23'
    // You should specify a missingDimensionStrategy property for each
    // dimension that exists in a local dependency but not in your app.
    missingDimensionStrategy 'abi', 'x86', 'arm64'
    }
    flavorDimensions 'tier'
    productFlavors {
        free {
            dimension 'tier'
            // You can override the default selection at the product flavor
            // level by configuring another missingDimensionStrategy property
            // for the "minApi" dimension.
            missingDimensionStrategy 'minApi', 'minApi23', 'minApi18'
        }
        paid {}
    }
}

迁移本地模块的依赖项配置

如果您已正确配置您的风味维度以充分利用变体感知依赖项解析,则不再需要为本地模块依赖项使用变体特定的配置,例如 redDebugImplementation - 插件将为您执行这项操作。使用变体特定的配置是可选项,并且不会破坏您的构建。

不过,针对本地模块依赖项的特定变体(例如使用 configuration: 'debug')会导致以下构建错误:

Error:Could not resolve all dependencies for configuration
  ':app:prodDebugCompileClasspath'.
Project :app declares a dependency from configuration 'compile'
  to configuration 'debug' which is not declared in the descriptor
  for project :foo.

您应当按如下方式配置您的依赖项:

dependencies {
    // This is the old method and no longer works for local
    // library modules:
    // debugCompile project(path: ':foo', configuration: 'debug')
    // releaseCompile project(path: ':foo', configuration: 'release')

    // Instead, simply use the following to take advantage of
    // variant-aware dependency resolution. You can learn more about
    // the 'implementation' configuration in the section about
    // new dependency configurations.
    implementation project(':foo')

    // You can, however, keep using variant-specific configurations when
    // targeting external dependencies. The following line adds 'app-magic'
    // as a dependency to only the 'debug' version of your module.

    debugImplementation 'com.example.android:app-magic:12.3'
}

注:您无法再为手动变体依赖项轻松使用旧机制,即使相关的 Gradle API 仍然存在。 向 project() DSL 提供的配置现在需要匹配构建类型和风味中的消费者(和其他属性)。 例如,无法通过这种机制让“调试”变体成为“发布”变体,因为生产者和消费者将不匹配。 (在这种情况下,名称“调试”是指在下文发布依赖项部分中提到的已发布配置对象。) 请注意,我们会发布两种配置,一种用于编译,一种用于运行时,这种比较旧的配置选择方式已经不再适用。

配置 Wear 应用依赖项

为了支持嵌入式 Wear 应用的变体感知依赖项解析,插件 3.0.0 在解析之前会将所有关系图组合起来,与其他依赖项的处理方式类似。 在之前版本的插件中,<component>WearApp 依赖关系图单独解析。 因此,您可以执行与下面类似的操作,蓝色变体将使用 :wear2,所有其他变体将使用 :wear1

dependencies {
    // This is the old way of configuring Wear App dependencies.
    wearApp project(':wear1')
    blueWearApp project(':wear2')
}

上面的配置不再适用于新插件。 不过,如果您的穿戴式设备模块与您的主应用配置相同的风味,那么您不需要使用 <flavor>WearApp 配置。 只需指定 wearApp 配置,主应用的每一个变体都将消费 wearable 中的匹配变体:

dependencies {
    // If the main app and wearable modules have the same flavors,
    // the following configuration uses automatic dependency matching.
    wearApp  project(':wearable')
}

如果您想要按应用风味指定不同的 Wear 应用模块,您仍可以按以下方式使用 <flavor>WearApp 配置(不过,您无法将其与 wearApp 配置组合):

dependencies {
    redWearApp project(':wear1')
    greenWearApp project(':wear1')
    blueWearApp project(':wear2')
}

使用新依赖项配置

Gradle 3.4 引入了新的 Java 库插件配置,允许您控制到编译和运行时类路径的发布(适用于模块间依赖项)。 Android 插件 3.0.0 正在迁移到这些新依赖项配置。 要迁移您的项目,只需更新您的依赖项以使用新配置,而非已弃用配置,如下表中所列。

新配置 已弃用配置 行为
implementation compile 依赖项在编译时对模块可用,并且仅在运行时对模块的消费者可用。 对于大型多项目构建,使用 implementation 而不是 api/compile 可以显著缩短构建时间,因为它可以减少构建系统需要重新编译的项目量。 大多数应用和测试模块都应使用此配置。
api compile 依赖项在编译时对模块可用,并且在编译时和运行时还对模块的消费者可用。 此配置的行为类似于 compile(现在已弃用),一般情况下,您应当仅在库模块中使用它。 应用模块应使用 implementation,除非您想要将其 API 公开给单独的测试模块。
compileOnly provided 依赖项仅在编译时对模块可用,并且在编译或运行时对其消费者不可用。 此配置的行为类似于 provided(现在已弃用)。
runtimeOnly apk 依赖项仅在运行时对模块及其消费者可用。 此配置的行为类似于 apk(现在已弃用)。

就像当前稳定版本的 Android 插件一样,上面的配置对风味或构建类型特定的依赖项可用。 例如,您可以使用 api 让依赖项对所有变体可用,也可以使用 redApi 让其仅对模块的 red 变体可用。

compileprovidedapk 目前仍然可用。 不过,它们将在下一个主要版本的 Android 插件中消失。

发布依赖项

以下配置将保持库的传递依赖项供其消费者消费:

  • <variant>ApiElements
  • <variant>RuntimeElements

每个变体都曾有一个名称为 <variant> 的配置。 由于库现在可以使用上一部分中介绍的 implementationapi 配置控制消费者可以看到的编译信息,现在有两种配置,一种用于消费者的编译,一种用于运行时。

要详细了解不同配置之间的关系,请转到 Java 库插件配置

迁移自定义依赖项解析策略

插件使用以下配置解析变体的所有依赖项:

  • <variant>CompileClasspath_<variant>Compile 不再奏效
  • <variant>RuntimeClasspath_<variant>Apk 不再奏效

如果您仍在使用旧配置,将会遇到一个类似于如下的构建错误:

Error:Configuration with old name _debugCompile found.
Use new name debugCompileClasspath instead.

在已解析配置上设置解析策略的插件或构建文件需要改成新的名称。 由于延迟依赖项解析,现在可以在使用变体 API 时设置解析策略,如下面的示例中所示。 (Android 插件现在包含用于访问变体配置对象的 getter 函数。)

// Previously, you had to apply a custom resolution strategy during the
// configuration phase, rather than in the execution phase. That's
// because, by the time the variant was created and the Variant API was
// called, the dependencies were already resolved. This no longer works, and
// you should use this method only while using an older version of the plugin.
// configurations {
//     _debugCompile
//     _debugApk
// }
// ...
//
// configurations._debugCompile.resolutionStrategy {
//     ...
// }
//
// configurations.all {
//     resolutionStrategy {
//     ...
//     }
// }

// Because the new build model delays dependency resolution, you should
// query and modify the resolution strategy using the Variant API.
android {
    applicationVariants.all { variant ->
        variant.getCompileConfiguration().resolutionStrategy {
            ...
        }
        variant.runtimeConfiguration.resolutionStrategy {
            ...
        }
        variant.getAnnotationProcessorConfiguration().resolutionStrategy {
            ...
        }
    }
}

注: 新插件目前使用旧名称查找配置,如果发现这些配置,将失败。 否则,它会静默忽略自定义解析策略。 我们可能会根据反馈对这一点进行更改,不过我们想要寻找一种方式来简化迁移。

使用注解处理器依赖项配置

在之前版本的 Android Plugin for Gradle 中,编译类路径中的依赖项将自动添加到处理器类路径中。 也就是说,您可以向编译类路径中添加一个注解处理器,它将按预期工作。 不过,由于向处理器添加了大量不需要的依赖项,这会对性能造成显著影响。

使用新插件时,必须使用 annotationProcessor 依赖项配置将注解处理器添加到处理器类路径中,如下所示:

dependencies {
    ...
    annotationProcessor 'com.google.dagger:dagger-compiler:<version-number>'
}

如果 JAR 文件包含以下文件,插件会将依赖项假设为注解处理器:META-INF/services/javax.annotation.processing.Processor。如果插件在编译类路径中检测到注解处理器,您的构建将失败,并且您将看到一条错误消息,其中列出了编译类路径中的每一个注解处理器。 要修复错误,只需更改这些依赖项的配置以使用 annotationProcessor。 如果依赖项包含也需要位于编译类路径中的组件,请再次声明该依赖项并使用 compile 依赖项配置。

android-apt 插件用户: 此行为变更目前不影响 android-apt 插件。 不过,插件将不会与未来版本的 Android Plugin for Gradle 兼容。

停用错误检查

如果编译类路径中的依赖项包含您不需要的注解处理器,您可以将以下代码添加到 build.gradle 文件中,停用错误检查。 请记住,您添加到编译类路径中的注解处理器仍不会添加到处理器类路径中。

android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                includeCompileClasspath false
            }
        }
    }
}

如果您在迁移到新依赖项解析策略时遇到问题,可以设置 includeCompileClasspath true,将行为恢复为 Android 插件 2.3 的行为。 不过,不建议将行为恢复为版本 2.3,未来更新中将移除恢复选项。 为了帮助我们提升与您正在使用的依赖项的兼容性,请提交错误

使用单独的测试模块

使用插件 3.0.0 时,单独的测试模块现在具备变体感知功能(请参见上面的部分)。也就是说,不再需要指定 targetVariant

测试模块中的每个变体都将尝试测试目标项目中的匹配变体。 默认情况下,测试模块仅包含一个 debug 变体,但您也可以创建新的构建类型和新风味来创建新的变体以匹配测试的应用项目。 将为每一个变体创建一个 connectedCheck 任务。

要让 Test 项目仅测试不同的构建类型,而不测试“调试”构建类型,请使用 VariantFilter 在测试项目中停用 debug 变体,如下所示:

android {
    variantFilter { variant ->
        if (variant.buildType.name.equals('debug') {
            variant.setIgnore(true);
        }
    }
}

如果您希望测试模块仅针对应用的特定风味变体,则可以使用 flavorSelection 属性针对您想要测试的风味。 这样也可以让测试模块不必为自己配置这些风味。

库中的本地 JAR

之前,库模块以非标准方式处理本地 JAR 上的依赖项,并将其打包到 AAR 中。 即使在多项目构建中,AAR 的消费者也能通过打包版本看到这些 JAR 文件。

Android 插件 3.0.0 及更高版本使用新的 Gradle API 以允许消费项目,从而将本地 JAR 视为常规传递依赖项,与基于 Maven 坐标的依赖项类似。 要适应新的 Gradle API,插件必须更改其处理本地 JAR 文件方式的多个方面。

项目间发布

  • 库模块不再处理本地 JAR。 旨在加快增量构建的速度,这些增量构建由库模块代码更改引起。

  • 库模块上的变换现在可以仅影响 PROJECT 范围。 应用使用 PROJECT_LOCAL_DEPS 的变换将失败,因为此范围现在已弃用。

  • 对于其本地 JAR 属于 EXTERNAL 流的应用模块,PROJECT_LOCAL_DEPSSUB_PROJECT_LOCAL_DEPS 流现在始终为空。

  • 为本地库模块启用 ProGuard 不再影响库的代码。 您应在最终应用模块上改为运行 ProGuard。

  • 之前,必须在库模块中解决库模块与其本地 JAR 依赖项之间的 Java 资源冲突。 由于本地 JAR 不再通过库模块处理,必须在消费该库的应用中解决冲突。

发布到 Maven 存储区

  • 发布到 Maven 存储区方面没有任何变化。 本地 JAR 已绑定,它们的非类资源合并到 AAR 的主 JAR 文件中。 如果模块启用 ProGuard,则所有 JAR 都将合并到主 JAR 文件中。

API 变更

Android 插件 3.0.0 引入了一些移除特定功能的 API 变更,可能会破坏您现有的构建。 后期版本的插件可能会引入新的公共 API 来取代破坏的功能。

变体输出

使用 Variant API 操作变体输出在新插件中已不再受支持。 不过,这种方式仍然适用于简单任务,例如在构建时更改 APK 名称,如下所示:

// If you use each() to iterate through the variant objects,
// you need to start using all(). That's because each() iterates
// through only the objects that already exist during configuration time—
// but those object don't exist at configuration time with the new model.
// However, all() adapts to the new model by picking up object as they are
// added during execution.
android.applicationVariants.all { variant ->
    variant.outputs.all {
        outputFileName = "${variant.name}-${variant.versionName}.apk"
    }
}

不过,涉及访问 outputFile 对象的更复杂任务不再奏效。 这是因为配置阶段期间不再创建变体特定的任务。 这就让插件无法提前了解其全部输出,不过,这会加快配置速度。

manifestOutputFile

processManifest.manifestOutputFile() 函数不再可用,如果您尝试调用该函数,您将遇到以下错误:

A problem occurred configuring project ':myapp'.
   Could not get unknown property 'manifestOutputFile' for task ':myapp:processDebugManifest'
   of type com.android.build.gradle.tasks.ProcessManifest.

您可以调用 processManifest.manifestOutputDirectory() 来返回包含所有已生成 manifest 的目录的路径,而不是调用 manifestOutputFile() 来获取每个变体的 manifest 文件。 然后,您可以找到某个 manifest 并向其应用您的逻辑。 下面的示例可以在 manifest 中动态更改版本代码:

android.applicationVariants.all { variant ->
    variant.outputs.all { output ->
        output.processManifest.doLast {
            // Stores the path to the maifest.
            String manifestPath = "$manifestOutputDirectory/AndroidManifest.xml"
            // Stores the contents of the manifest.
            def manifestContent = file(manifestPath).getText()
            // Changes the version code in the stored text.
            manifestContent = manifestContent.replace('android:versionCode="1"',
                    String.format('android:versionCode="%s"', generatedCode))
            // Overwrites the manifest with the new text.
            file(manifestPath).write(manifestContent)
        }
    }
}