欢迎参加我们将于 6 月 3 日举行的 #Android11:Beta 版发布会

Gradle 提示与诀窍

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

如果您刚开始接触 Gradle,请阅读配置构建来学习基础知识。您也可以查看 Android Plugin for Gradle 的 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 Plugin for Gradle,您可以通过配置构建变体,根据单个模块为应用创建不同的版本。

配置多 APK 支持

利用 Android Plugin for Gradle,您可以构建多个 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 使用 R8(它使用 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'
        }
      }
      ...
    }
    ...
    

要添加特定于每个构建变体的规则,请为每个变种分别配置额外的 proguardFiles 属性。例如,以下示例会将 flavor2-rules.pro 添加到“flavor2”。现在,“flavor2”的发布版本使用全部三个规则文件,因为该变种还应用了 release 代码块中的规则文件。

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

发布应用

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

为应用签名

尽管 Android Studio 提供了一种从界面为发布 build 配置签名的简易方式,您仍然可以手动配置模块的 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 文件中,按以下步骤加载 keystore.properties 文件(必须在 android 代码块之前加载):
        // 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. 点击通知栏中的立即同步

如需详细了解应用签名,请阅读为应用签名

简化应用开发

下面的提示有助于更轻松地开发 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 these rebuild dynamically, they can interfere with
          // Apply Changes 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);