Gradle 提示与诀窍

Gradle 和 Android Plugin for Gradle 提供了一种灵活的方式来编译、构建和打包您的 Android 应用或库。本页汇总了一些有用的提示和配置,旨在帮助您充分利用每个版本。如果您要了解可让您的构建速度更快的方式,请阅读优化您的构建速度

如果您刚开始接触 Gradle,请阅读配置编译系统来学习基础知识。您也可以查阅 Android 插件的 DSL 参考文档来详细了解本页中使用的属性。

管理项目和源代码

下面是一些可用于管理项目的模块及其源代码的配置。要详细了解如何创建和管理项目与模块,请阅读项目概览

更改默认源集配置

您可以使用模块级 build.gradle 文件中的 sourceSets 代码块来更改 Gradle 希望将源集的每个组件的文件收集在什么位置。

    android {
      ...
      sourceSets {
        // Encapsulates configurations for the main source set.
        main {
          // Changes the directory for Java sources. The default directory is
          // 'src/main/java'.
          java.srcDirs = ['other/java']

          // When you list multiple directories, Gradle uses all of them to collect
          // sources. You should avoid specifying a directory which is a parent to one
          // or more other directories you specify.
          res.srcDirs = ['other/res1', 'other/res2']

          // For each source set, you can specify only one Android manifest.
          // The following points Gradle to a different manifest for this source set.
          manifest.srcFile 'other/AndroidManifest.xml'
          ...
        }

        // Create additional blocks to configure other source sets.
        androidTest {

          // If all the files for a source set are located under a single root
          // directory, you can specify that directory using the setRoot property.
          // When gathering sources for the source set, Gradle looks only in locations
          // relative to the root directory you specify. For example, after applying
          // the configuration below for the androidTest source set, Gradle looks for
          // Java sources only in the src/tests/java/ directory.
          setRoot 'src/tests'
          ...
        }
      }
    }
    ...
    

配置项目范围的属性

对于包含多个模块的项目,在项目级别定义属性并在所有模块之间共享这些属性可能很有用。为此,您可以将额外属性添加到顶级 build.gradle 文件中的 ext 代码块。

    buildscript {...}
    allprojects {...}

    // This block encapsulates custom properties and makes them available to all
    // modules in the project.
    ext {
        // The following are only a few examples of the types of properties you can define.
        compileSdkVersion = 28
        // You can also use this to specify versions for dependencies. Having consistent
        // versions between modules can avoid behavior conflicts.
        supportLibVersion = "28.0.0"
        ...
    }
    ...
    

要从同一项目中的模块访问这些属性,请在模块级 build.gradle 文件中使用以下语法。

    android {
      // Use the following syntax to access properties you define at the project level:
      // rootProject.ext.property_name
      compileSdkVersion rootProject.ext.compileSdkVersion
      ...
    }
    ...
    dependencies {
        implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
        ...
    }
    

管理库和依赖项

Gradle 提供了一种稳健的机制来管理依赖项,不管它们是远程库还是本地库模块

让依赖项配置针对特定构建

如果您希望某个依赖项仅用于特定的编译变体源集或测试源集,请将依赖项配置名称的首字母大写,并在其前面加上编译变体或测试源集的名称作为前缀。

    android {...}

    // Creates Gradle dependency configurations to use in the dependencies block.
    configurations {
      // For variants that combine a product flavor and build type, you need to
      // intitialize a placeholder for its dependency configuration.
      freeDebugRuntimeOnly{}
      ...
    }

    dependencies {
        // Adds an implementation dependency only to the "free" product flavor.
        freeImplementation 'com.google.firebase:firebase-ads:9.8.0'
        // Adds a runtimeOnly dependency only to the "freeDebug" build variant.
        freeDebugRuntimeOnly fileTree(dir: 'libs', include: ['*.jar'])
        // Adds a remote binary dependency only for local tests.
        testImplementation 'junit:junit:4.12'
        // Adds a remote binary dependency only for the instrumented test APK.
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }
    

创建不同版本的应用

借助 Gradle 和 Android 插件,您可以通过配置编译变体,根据单个模块创建不同版本的应用。

配置多 APK 支持

利用 Android 插件,您可以构建多个 APK,让每个 APK 针对不同的 ABI 或屏幕密度,并充分利用 Google Play 的多 APK 支持

按屏幕密度配置单独的 APK

要为不同的屏幕密度创建单独的 APK,请将 android.splits.density 代码块添加到模块的 build.gradle 文件中。

    android {
      ...
      splits {

        // Configures multiple APKs based on screen density.
        density {

          // Enables building multiple APKs.
          enable true

          // Specifies a list of screen densities Gradle should not create APKs for.
          exclude "ldpi", "mdpi"

          // Alternatively, you can use the following to clear the default list of
          // screen densities and specify only the screen densities you want to build
          // APKs for:
          // reset()
          // include "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"

          // Specifies a list of compatible screen size settings. This property
          // configures the <compatible-screens> element in the manifest. You
          // typically don't need to configure this manifest property, but it's
          // important when building multiple APKs based on screen density.
          compatibleScreens 'normal', 'large', 'xlarge'
        }
      }
    }
    

按 ABI 配置单独的 APK

要为每个 ABI 创建单独的 APK,请将 android.splits.abi 代码块添加到模块的 build.gradle 文件中。

    android {
      ...
      splits {

        // Configures multiple APKs based on ABI.
        abi {

          // Enables building multiple APKs.
          enable true

          // By default all ABIs are included, so use reset() and include to specify that we only
          // want APKs for x86, armeabi-v7a, and mips.
          reset()

          // Specifies a list of ABIs that Gradle should create APKs for.
          include "x86", "armeabi-v7a", "mips"

          // Specify that we want to also generate a universal APK that includes all ABIs.
          universalApk true
        }
      }
    }
    

配置动态版本代码

默认情况下,在 Gradle 为您的项目生成 APK 时,每个 APK 都有相同的版本信息,该信息在模块级 build.gradle 文件中指定。由于 Google Play 商店不允许同一个应用的多个 APK 全都具有相同的版本信息,因此在上传到 Play 商店之前,您需要确保每个 APK 都有自己唯一的 versionCode

为此,您可以使用自定义构建逻辑,在构建时向每个 APK 分配不同的版本代码。例如,在为每个 ABI 创建单独的 APK 时,自动 APK 版本控制将如下所示:

    android {
      ...
      defaultConfig {
        ...
        versionCode 4
      }
      splits {
        ...
      }
    }

    // Map for the version code that gives each ABI a value.
    ext.abiCodes = ['armeabi-v7a':1, mips:2, x86:3]

    // For per-density APKs, create a similar map like this:
    // ext.densityCodes = ['hdpi': 1, 'xhdpi': 2, 'xxhdpi': 3, 'xxxhdpi': 4]

    import com.android.build.OutputFile

    // For each APK output variant, override versionCode with a combination of
    // ext.abiCodes * 1000 + variant.versionCode. In this example, variant.versionCode
    // is equal to defaultConfig.versionCode. If you configure product flavors that
    // define their own versionCode, variant.versionCode uses that value instead.
    android.applicationVariants.all { variant ->

      // Assigns a different version code for each output APK
      // other than the universal APK.
      variant.outputs.each { output ->

        // Stores the value of ext.abiCodes that is associated with the ABI for this variant.
        def baseAbiVersionCode =
                // Determines the ABI for this variant and returns the mapped value.
                project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))

        // Because abiCodes.get() returns null for ABIs that are not mapped by ext.abiCodes,
        // the following code does not override the version code for universal APKs.
        // However, because we want universal APKs to have the lowest version code,
        // this outcome is desirable.
        if (baseAbiVersionCode != null) {

          // Assigns the new version code to versionCodeOverride, which changes the version code
          // for only the output APK, not for the variant itself. Skipping this step simply
          // causes Gradle to use the value of variant.versionCode for the APK.
          output.versionCodeOverride =
                  baseAbiVersionCode * 1000 + variant.versionCode
        }
      }
    }
    

组合多个产品类型

在某些情况下,您可能需要组合多个产品类型的配置。为此,您可以使用 Android Plugin for Gradle 来创建称为“类型维度”的产品类型组。

以下代码示例使用 flavorDimensions 属性来创建“mode”类型维度和“api”类型维度,前者用于将“full”和“demo”产品类型进行分组,后者用于根据 API 级别对产品类型配置进行分组。随后,Gradle 会将“mode”维度的产品类型与“api”维度的产品类型组合在一起。

    android {
      ...
      buildTypes {
        debug {...}
        release {...}
      }

      // Specifies the flavor dimensions you want to use. The order in which you
      // list each dimension determines its priority, from highest to lowest,
      // when Gradle merges variant sources and configurations. You must assign
      // each product flavor you configure to one of the flavor dimensions.
      flavorDimensions "api", "mode"

      productFlavors {
        demo {
          // Assigns this product flavor to the "mode" flavor dimension.
          dimension "mode"
          ...
        }

        full {
          dimension "mode"
          ...
        }

        // Configurations in the "api" product flavors override those in "mode"
        // flavors and the defaultConfig block. Gradle determines the priority
        // between flavor dimensions based on the order in which they appear next
        // to the flavorDimensions property above--the first dimension has a higher
        // priority than the second, and so on.
        minApi24 {
          dimension "api"
          minSdkVersion '24'
          // To ensure the target device receives the version of the app with
          // the highest compatible API level, assign version codes in increasing
          // value with API level. To learn more about assigning version codes to
          // support app updates and uploading to Google Play, read Multiple APK Support
          versionCode 30000 + android.defaultConfig.versionCode
          versionNameSuffix "-minApi24"
          ...
        }

        minApi23 {
          dimension "api"
          minSdkVersion '23'
          versionCode 20000  + android.defaultConfig.versionCode
          versionNameSuffix "-minApi23"
          ...
        }

        minApi21 {
          dimension "api"
          minSdkVersion '21'
          versionCode 10000  + android.defaultConfig.versionCode
          versionNameSuffix "-minApi21"
          ...
        }
      }
    }
    ...
    

过滤变体

您可以使用模块的 build.gradle 文件中的 variantFilter 代码块过滤编译变体,将您不想要的变体过滤掉。以下示例代码将指示 Gradle 不构建任何将“minApi21”和“demo”产品类型组合在一起的变体:

    android {
     ...
     buildTypes {...}

     flavorDimensions "api", "mode"
     productFlavors {
        demo {...}
        full {...}
        minApi24 {...}
        minApi23 {...}
        minApi21 {...}
      }

      variantFilter { variant ->
        def names = variant.flavors*.name
        // To check for a build type instead, use variant.buildType.name == "buildType"
        if (names.contains("minApi21") && names.contains("demo")) {
          // Gradle ignores any variants that satisfy the conditions above.
          setIgnore(true)
        }
      }
    }
    ...
    

测试您的应用

要详细了解如何运行本地和集成单元测试,请阅读测试您的应用

配置 lint 选项

您可以使用模块级 build.gradle 文件中的 lintOptions 代码块来配置某些 lint 选项。要详细了解如何为您的 Android 项目使用 lint,请阅读使用 Lint 改进您的代码

    android {
      ...
      lintOptions {
        // Turns off checks for the issue IDs you specify.
        disable 'TypographyFractions','TypographyQuotes'
        // Turns on checks for the issue IDs you specify. These checks are in
        // addition to the default lint checks.
        enable 'RtlHardcoded', 'RtlCompat', 'RtlEnabled'
        // To enable checks for only a subset of issue IDs and ignore all others,
        // list the issue IDs with the 'check' property instead. This property overrides
        // any issue IDs you enable or disable using the properties above.
        check 'NewApi', 'InlinedApi'
        // If set to true, turns off analysis progress reporting by lint.
        quiet true
        // if set to true (default), stops the build if errors are found.
        abortOnError false
        // if true, only report errors.
        ignoreWarnings true
      }
    }
    ...
    

配置插桩测试清单设置

当 Gradle 构建您的测试 APK 时,它会自动生成 AndroidManifest.xml 文件并为其配置 <instrumentation> 节点。您可以更改此节点的某些设置,方法是在测试源集中再创建一个清单文件,或配置模块级 build.gradle 文件,如以下代码示例所示。

    android {
      ...
      // Each product flavor you configure can override properties in the
      // defaultConfig block. To learn more, go to Configure Product Flavors.
      defaultConfig {
        ...
        // Specifies the application ID for the test APK.
        testApplicationId "com.test.foo"
        // Specifies the fully-qualified class name of the test instrumentation runner.
        testInstrumentationRunner "android.test.InstrumentationTestRunner"
        // If set to 'true', enables the instrumentation class to start and stop profiling.
        // If set to false (default), profiling occurs the entire time the instrumentation
        // class is running.
        testHandleProfiling true
        // If set to 'true', indicates that the Android system should run the instrumentation
        // class as a functional test. The default value is 'false'
        testFunctionalTest true
      }
    }
    ...
    

更改测试版本类型

默认情况下,所有测试均针对调试版本类型运行。您可以使用模块级 build.gradle 文件中的 testBuildType 属性将其更改为其他版本类型。例如,如果您要针对“staging”版本类型运行测试,请按以下代码段所示对该文件进行修改。

    android {
        ...
        testBuildType "staging"
    }
    

配置 Gradle 测试选项

要指定相关选项来更改 Gradle 运行所有测试的方式,请配置模块级 build.gradle 文件中的 testOptions 代码块。

    android {
      ...
      // Encapsulates options for running tests.
      testOptions {
        // Changes the directory where Gradle saves test reports. By default, Gradle saves test reports
        // in the path_to_your_project/module_name/build/outputs/reports/ directory.
        // '$rootDir' sets the path relative to the root directory of the current project.
        reportDir "$rootDir/test-reports"
        // Changes the directory where Gradle saves test results. By default, Gradle saves test results
        // in the path_to_your_project/module_name/build/outputs/test-results/ directory.
        // '$rootDir' sets the path relative to the root directory of the current project.
        resultsDir "$rootDir/test-results"
      }
    }
    

要仅为本地单元测试指定选项,请配置 testOptions.unitTests 代码块。

    android {
      ...
      testOptions {
        ...
        // Encapsulates options for local unit tests.
        unitTests {
          // By default, local unit tests throw an exception any time the code you are testing tries to access
          // Android platform APIs (unless you mock Android dependencies yourself or with a testing
          // framework like Mockito). However, you can enable the following property so that the test
          // returns either null or zero when accessing platform APIs, rather than throwing an exception.
          returnDefaultValues true

          // Encapsulates options for controlling how Gradle executes local unit tests. For a list
          // of all the options you can specify, read Gradle's reference documentation.
          all {
            // Sets JVM argument(s) for the test JVM(s).
            jvmArgs '-XX:MaxPermSize=256m'

            // You can also check the task name to apply options to only the tests you specify.
            if (it.name == 'testDebugUnitTest') {
              systemProperty 'debug', 'true'
            }
          }
        }
      }
    }
    

优化您的构建

本部分将介绍一些有助于加快完整构建和增量构建速度的配置。要了解详情,请阅读优化您的构建速度

压缩代码

Android Studio 使用 ProGuard 来压缩代码。对于新项目,Android Studio 将使用 Android SDK tools/proguard/folder 下的默认设置文件 (proguard-android.txt)。要进一步压缩代码,请尝试使用位于同一位置的 proguard-android-optimize.txt 文件。

    android {
      buildTypes {
        release {
          minifyEnabled true
          proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
                                               'proguard-rules.pro'
        }
      }
      ...
    }
    ...
    

要添加每个编译变体专用的 ProGuard 规则,请为每个类型配置一个额外的 proguardFiles 属性。例如,以下示例会将 flavor2-rules.pro 添加到“flavor2”。现在,发布版本“flavor2”使用全部三个 ProGuard 规则,因为还应用了来自 release 代码块的规则。

    android {
      ...
      buildTypes {
        release {
          minifyEnabled true
          proguardFiles getDefaultProguardFile('proguard-android.txt'),
                 'proguard-rules.pro'
        }
      }
      productFlavors {
        flavor1 {
          ...
        }
        flavor2 {
          proguardFile 'flavor2-rules.pro'
        }
      }
    }
    ...
    

通过 Instant Run 启用代码压缩

通过 Instant Run 启用代码压缩,只需将 useProguard 设为 false(并保持 minifyEnabled 设为 true)。这将使用实验性代码压缩器,它不会对您的代码进行混淆处理或优化(因此,您应当仅为“debug”版本类型启用此压缩器)。

    android {
      buildTypes {
        debug {
          minifyEnabled true
          useProguard false
          proguardFiles getDefaultProguardFile('proguard-android.txt'),
                  'proguard-rules.pro'
        }
        release {
          minifyEnabled true
          proguardFiles getDefaultProguardFile('proguard-android.txt'),
                  'proguard-rules.pro'
        }
      }
    }
    

发布您的应用

要详细了解如何将您的应用发布到 Google Play,请阅读发布您的应用

为您的应用签名

虽然 Android Studio 提供了一种从界面中为发布版本配置签名的简便方法,但您可以手动配置模块的 build.gradle 文件中的 signingConfigs 代码块:

    android {
      ...
      defaultConfig { ... }

      // Encapsulates signing configurations.
      signingConfigs {
        // Creates a signing configuration called "release".
        release {
          // Specifies the path to your keystore file.
          storeFile file("my-release-key.jks")
          // Specifies the password for your keystore.
          storePassword "password"
          // Specifies the identifying name for your key.
          keyAlias "my-alias"
          // Specifies the password for your key.
          keyPassword "password"
        }
      }
      buildTypes {
        release {
          // Adds the "release" signing configuration to the release build type.
          signingConfig signingConfigs.release
          ...
        }
      }
    }
    ...
    

从您的项目中移除私密签名信息

默认情况下,签名配置将以纯文本形式记录到模块的 build.gradle 文件中。如果您正在与某个团队合作或参与一个开放源代码项目,可以执行以下步骤,将此敏感信息从编译文件中移出。

  1. 在项目的根目录下创建一个名为 keystore.properties 的文件,并添加以下信息:
        storePassword=myStorePassword
        keyPassword=myKeyPassword
        keyAlias=myKeyAlias
        storeFile=myStoreFileLocation
        
  2. build.gradle 文件中,添加以下代码(必须在 android 代码块前面)来加载 keystore.properties 文件:
        // Creates a variable called keystorePropertiesFile, and initializes it to the
        // keystore.properties file.
        def keystorePropertiesFile = rootProject.file("keystore.properties")
    
        // Initializes a new Properties() object called keystoreProperties.
        def keystoreProperties = new Properties()
    
        // Loads the keystore.properties file into the keystoreProperties object.
        keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
    
        android {
          ...
        }
        ...
        
  3. 输入存储在 keystoreProperties 对象中的签名信息:
        android {
          signingConfigs {
            config {
              keyAlias keystoreProperties['keyAlias']
              keyPassword keystoreProperties['keyPassword']
              storeFile file(keystoreProperties['storeFile'])
              storePassword keystoreProperties['storePassword']
            }
          }
          ...
        }
        ...
        
  4. 点击通知栏中的 Sync Now

要详细了解应用签名,请阅读为您的应用签名

简化应用开发

下面的提示有助于简化您的 Android 应用开发。

与应用的代码共享自定义字段和资源值

在构建时,Gradle 将生成 BuildConfig 类,以便您的应用代码可以检查与当前构建有关的信息。您还可以使用 buildConfigField() 方法从 Gradle 构建配置文件将自定义字段添加到 BuildConfig 类,并在应用的运行时代码中访问这些值。同样,您也可以使用 resValue() 添加应用资源值。

    android {
      ...
      buildTypes {
        release {
          // These values are defined only for the release build, which
          // is typically used for full builds and continuous builds.
          buildConfigField("String", "BUILD_TIME", "\"${minutesSinceEpoch}\"")
          resValue("string", "build_time", "${minutesSinceEpoch}")
          ...
        }
        debug {
          // Use static values for incremental builds to ensure that
          // resource files and BuildConfig aren't rebuilt with each run.
          // If they were dynamic, they would prevent certain benefits of
          // Instant Run as well as Gradle UP-TO-DATE checks.
          buildConfigField("String", "BUILD_TIME", "\"0\"")
          resValue("string", "build_time", "0")
        }
      }
    }
    ...
    

在您的应用代码中,您可以按如下方式访问属性:

Kotlin

    ...
    Log.i(TAG, BuildConfig.BUILD_TIME)
    Log.i(TAG, getString(R.string.build_time))
    

Java

    ...
    Log.i(TAG, BuildConfig.BUILD_TIME);
    Log.i(TAG, getString(R.string.build_time));
    

与清单共享属性

在某些情况下,您可能需要同时在清单和代码中声明同一属性(例如,在声明 FileProvider 的授权机构时)。如以下示例中所示,您可以在模块的 build.gradle 文件中定义一个属性,使其对清单和代码均可用,而不必在多个位置更新同一属性以反映更改。要了解详情,请阅读将编译变量注入清单

    android {
      // For settings specific to a product flavor, configure these properties
      // for each flavor in the productFlavors block.
      defaultConfig {
        // Creates a property for the FileProvider authority.
        def filesAuthorityValue = applicationId + ".files"
        // Creates a placeholder property to use in the manifest.
        manifestPlaceholders =
          [filesAuthority: filesAuthorityValue]
          // Adds a new field for the authority to the BuildConfig class.
          buildConfigField("String",
                           "FILES_AUTHORITY",
                           "\"${filesAuthorityValue}\"")
      }
      ...
    }
    ...
    

在您的清单中,按如下方式访问占位符:

    <manifest>
      ...
      <application>
        ...
        <provider
          android:name="android.support.v4.content.FileProvider"
          android:authorities="${filesAuthority}"
          android:exported="false"
          android:grantUriPermissions="true">
          ...
        </provider>
      </application>
    </manifest>
    

在应用的代码中,访问 FILES_AUTHORITY 字段的方式如下所示:

Kotlin

    ...
    val contentUri: Uri = FileProvider.getUriForFile(context, BuildConfig.FILES_AUTHORITY, myFile)
    

Java

    ...
    Uri contentUri = FileProvider.getUriForFile(getContext(),
      BuildConfig.FILES_AUTHORITY,
      myFile);