Dicas e receitas para o Gradle

O Gradle e o plug-in do Android para Gradle oferecem uma maneira flexível de compilar, criar e empacotar seu app ou biblioteca Android. Esta página traz algumas dicas e configurações úteis para ajudar você a tirar o máximo proveito de cada compilação. Para saber mais sobre outras maneiras de agilizar suas compilações, leia Otimizar a velocidade da sua compilação.

Caso não tenha experiência com o Gradle, aprenda os princípios básicos lendo Configurar sua compilação. Você também pode ler a documentação de referência de DSL para saber mais sobre as propriedades usadas nesta página.

Gerenciar projetos e fontes

Veja a seguir algumas configurações para gerenciar os módulos do seu projeto e as fontes deles. Para saber mais sobre como criar e gerenciar projetos e módulos, leia a Visão geral de projetos.

Alterar as configurações do conjunto de fontes padrão

Você pode usar o bloco sourceSets no arquivo build.gradle de nível de módulo para alterar onde o Gradle buscará para coletar arquivos para cada componente de um conjunto de fontes.

    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'
          ...
        }
      }
    }
    ...
    

Configurar propriedades para todo o projeto

Para projetos que incluem diversos módulos, pode ser útil definir as propriedades no nível do projeto e compartilhá-las em todos os módulos. É possível fazer isso adicionando outras propriedades ao bloco ext no arquivo build.gradle de nível superior.

    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"
        ...
    }
    ...
    

Para acessar essas propriedades de um módulo no mesmo projeto, use a seguinte sintaxe no arquivo build.gradle de nível de módulo.

    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}"
        ...
    }
    

Gerenciar bibliotecas e dependências

O Gradle oferece um mecanismo robusto para gerenciar dependências, sejam elas bibliotecas remotas ou módulos de bibliotecas locais.

Direcionar configurações de dependência para compilações específicas

Se quiser uma dependência apenas para um conjunto de fontes de uma variante de compilação específica ou para um conjunto de fontes de teste, capitalize o nome da configuração de dependência e adicione o prefixo do nome da variante de compilação ou do conjunto de fontes de teste.

    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'
    }
    

Criar diferentes versões do seu app

O Gradle e o plug-in do Android permitem que você crie diferentes versões do seu app a partir de um só módulo configurando variantes de compilação.

Configurar a compatibilidade com vários APKs

Com o plug-in do Android, é possível compilar vários APKs, cada um direcionado ao ABI ou à densidade de tela, e aproveitar a compatibilidade com vários APKs do Google Play.

Configurar APKs separados por densidade de tela

Para criar APKs separados para diferentes densidades de tela, adicione o bloco android.splits.density ao arquivo build.gradle do seu módulo.

    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'
        }
      }
    }
    

Configurar APKs separados por ABI

Para criar APKs separados para cada ABI, adicione o bloco android.splits.abi ao arquivo build.gradle do seu módulo.

    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
        }
      }
    }
    

Configurar códigos de versão dinâmicos

Por padrão, quando o Gradle gera APKs para seu projeto, cada APK tem as mesmas informações de versão, conforme especificado no arquivo build.gradle de nível de módulo. Como a Google Play Store não permite que o mesmo app tenha vários APKs com as mesmas informações de versão, você precisa garantir que cada APK tenha o próprio versionCode exclusivo antes de fazer o upload para a Play Store.

Você pode fazer isso com uma lógica de compilação personalizada que atribui um código de versão diferente para cada APK no tempo de compilação. Por exemplo, ao criar APKs separados para cada ABI, a geração automática de versão do APK é semelhante a:

    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
        }
      }
    }
    

Combinar diversas variações de produto

Em alguns casos, você pode querer combinar as configurações de diferentes variações de produto. Para fazer isso, o plug-in do Android para Gradle permite criar grupos de variações de produto, chamados de dimensões de variação.

O exemplo de código a seguir usa a propriedade flavorDimensions para criar uma dimensão de variação "mode" para agrupar as variações de produto "full" e "demo", e uma dimensão de variação "api" para agrupar as configurações das variações baseadas no nível da API. Em seguida, o Gradle combina variações de produtos da dimensão "mode" com os da dimensão "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"
          ...
        }
      }
    }
    ...
    

Filtrar variantes

Você pode filtrar as variantes de compilação que não quiser que utilizem o bloco variantFilter no arquivo build.gradle do módulo. O exemplo de código a seguir solicita que o Gradle não compile variantes que combinem as variações de produto “minApi21” e “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)
        }
      }
    }
    ...
    

Testar seu app

Para saber mais sobre como executar testes de unidade local e integrada, leia Testar seu app.

Configurar opções de lint

É possível configurar certas opções de lint usando o bloco lintOptions no arquivo build.gradle de nível de módulo. Para saber mais sobre como usar o lint para seu projeto Android, leia Melhorar seu código com 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
      }
    }
    ...
    

Definir configurações do manifesto de instrumentação

Quando o Gradle compila seu APK de teste, ele gera o arquivo AndroidManifest.xml automaticamente e o configura com o nó <instrumentation>. Você pode alterar algumas das configurações desse nó criando outro arquivo de manifesto no conjunto de fontes de teste ou configurando seu arquivo build.gradle de nível de módulo, conforme é mostrado no exemplo de código a seguir:

    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
      }
    }
    ...
    

Alterar o tipo de compilação de teste

Por padrão, todos os testes são executados com o tipo de compilação de depuração. Para alterar isso para outro tipo de compilação, use a propriedade testBuildType no arquivo build.gradle de nível de módulo. Por exemplo, se você quiser executar os testes com o tipo de compilação "preparação", edite o arquivo, como mostrado no snippet a seguir:

    android {
        ...
        testBuildType "staging"
    }
    

Configurar opções de teste do Gradle

Para especificar opções que alteram o modo como o Gradle executa todos os seus testes, configure o bloco testOptions no build.gradle de nível de módulo.

    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"
      }
    }
    

Para especificar opções apenas para testes de unidade local, configure o bloco 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'
            }
          }
        }
      }
    }
    

Otimizar sua compilação

Esta seção traz algumas configurações para ajudar a agilizar suas compilações completas e incrementais. Para saber mais, leia Otimizar a velocidade da sua compilação.

Reduzir seu código

O Android Studio usa o ProGuard para reduzir seu código. Para novos projetos, o Android Studio usa um arquivo de configurações padrão (proguard-android.txt) da pasta tools/proguard/folder do Android SDK. Para reduzir ainda mais o código, experimente o arquivo proguard-android-optimize.txt, encontrado no mesmo local.

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

Para adicionar regras do ProGuard específicas a cada variante de compilação, configure outra propriedade proguardFiles para cada variação. O exemplo a seguir adiciona flavor2-rules.pro a "flavor2". Agora, a versão de lançamento do “flavor2” usa todas as três regras do ProGuard porque as do bloco "release" também são aplicáveis.

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

Publicar seu app

Para saber mais sobre como publicar seu app no Google Play, leia Publicar seu app.

Assinar o app

Apesar de o Android Studio oferecer uma maneira fácil de configurar a assinatura para compilações de lançamento na IU, você pode configurar manualmente o bloco signingConfigs no arquivo build.gradle do seu módulo:

    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
          ...
        }
      }
    }
    ...
    

Remover informações de assinatura particulares do seu projeto

Por padrão, as configurações de assinatura são gravadas em texto simples no arquivo build.gradle do módulo. Se você está trabalhando com uma equipe ou um projeto de código aberto, é possível remover essas informações confidenciais dos arquivos de compilação seguindo os procedimentos a seguir:

  1. Crie um arquivo denominado keystore.properties no diretório raiz do seu projeto e inclua as seguintes informações:
        storePassword=myStorePassword
        keyPassword=myKeyPassword
        keyAlias=myKeyAlias
        storeFile=myStoreFileLocation
        
  2. No arquivo build.gradle, carregue o arquivo keystore.properties da seguinte forma (isso precisa ser feito antes do bloco do 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. Insira as informações de assinatura armazenadas no objeto keystoreProperties:
        android {
          signingConfigs {
            config {
              keyAlias keystoreProperties['keyAlias']
              keyPassword keystoreProperties['keyPassword']
              storeFile file(keystoreProperties['storeFile'])
              storePassword keystoreProperties['storePassword']
            }
          }
          ...
        }
        ...
        
  4. Clique em Sync Now na barra de notificações.

Para saber mais sobre a assinatura de apps, leia Assinar seu app.

Simplificar o desenvolvimento de apps

As dicas a seguir facilitam o desenvolvimento do seu app Android.

Compartilhar valores personalizados de campos e recursos com o código do seu app

No tempo de compilação, o Gradle gera a classe BuildConfig para que o código do seu app possa inspecionar informações sobre a compilação atual. Também é possível adicionar campos personalizados à classe BuildConfig do arquivo de configuração de compilação do Gradle usando o método buildConfigField() e acessar esses valores no código de tempo de execução do seu app. Da mesma forma, é possível adicionar valores de recurso de app com 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")
        }
      }
    }
    ...
    

No código do app, você pode acessar as propriedades da seguinte maneira:

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));
    

Compartilhar propriedades com o manifesto

Em alguns casos, pode ser necessário declarar a mesma propriedade no manifesto e no código, por exemplo, ao declarar autoridades para um FileProvider. Em vez de atualizar a mesma propriedade em vários locais para refletir uma mudança, defina uma única propriedade no arquivo build.gradle do seu módulo para que ela esteja disponível para o manifesto e para o código, conforme mostrado no exemplo a seguir. Para saber mais, leia Injetar variáveis de versão no manifesto.

    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}\"")
      }
      ...
    }
    ...
    

No manifesto, acesse o marcador da seguinte maneira:

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

O acesso ao campo FILES_AUTHORITY no código do seu app é semelhante a:

Kotlin

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

Java

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