Usando Build Flavors - Estruturando pastas de origem e build.gradle corretamente

166

Nota: Resposta editada após a Resposta de Xavier

Estou tentando usar diferentes sabores de compilação para um mesmo projeto de aplicativo no Android Studio. No entanto, parece que estou tendo um momento terrível para configurá-lo para funcionar adequadamente.

Passos:

  1. Crie um novo projeto do Android Studio, chamado 'Teste'.
  2. Abra build.gradle * e incluiu as seguintes linhas:

    productFlavors {
    flavor1 {
        packageName 'com.android.studio.test.flavor1'
        }
    flavor2 {
        packageName 'com.android.studio.test.flavor2'
        }
    }
  3. Depois de reiniciar o Android Studio, agora vejo 4 variantes de compilação na seção Variantes de compilação. O que significa que fomos bem-sucedidos em configurar os sabores dos produtos até agora. **
  4. Criou uma nova pasta de origem para flavor1 ; no entanto, não tenho certeza se estou fazendo da maneira certa. Aqui está como eu fiz isso:

    • Lembre-se de que meu nome de pacote para este projeto é: com.foo.test
    • Clique com o botão direito do mouse na srcpasta, para sabor1, eu realmente criei as pastas individuais no explorer, da maneira que a estrutura é src/flavor1/java/com/foo/test/MainActivity.java.
    • O exemplo acima funcionou bem, já que a pasta 'java' está em azul , o que significa que o IDE sabe que é um diretório de origem ativo. Além disso, o pacote foi criado automaticamente. Apesar disso, estou recebendo um aviso para a classe duplicada encontrada. Veja a captura de tela aqui.
    • Para o flavor2, tentei criar o pacote manualmente, mas a pasta 'src' para o flavor2 parece não estar em azul e, portanto, as opções são diferentes quando clicadas com o botão direito, e o 'Novo Pacote' não está disponível para eu usar. Veja a imagem aqui.
    • Observe que, para o flavor1, também criei um diretório 'res', que fica azul, mas, apesar disso, não oferece a capacidade de criar um arquivo de recursos do Android ou um diretório de recursos do Andorid, caso eu queira usar diferentes reaparece para sabores diferentes.

Estou fazendo algo errado? Ou eu estou esquecendo de alguma coisa? Entre em contato se precisar de mais informações.

* Meu projeto parece ter dois arquivos build.gradle. Um localizado na raiz da pasta do projeto (\ GradleTest), este está vazio. O segundo, localizado na raiz de uma subpasta de \ GradleTest, também denominada 'GradleTest' (GradleTest-GradleTest), é o que já tinha código quando aberto; portanto, esse é o que eu editei.

** Verifiquei as configurações do gradle e, aparentemente, a opção Usar importação automática estava ativada. Apesar disso, fazer alterações no arquivo build.gradle não atualiza automaticamente as variantes de compilação. Nota: Eu também tentei usar o Build - Rebuild Project e / ou Build - Make Project, no-go. Ainda tenho que fechar o projeto e reabri-lo para que as alterações entrem em vigor.

daniel_c05
fonte
Observe que applicationIdagora é o suporte em vez de packageName.
Hamzeh Soboh

Respostas:

220

Se você acessou as preferências do Studio, na seção Gradle, pode ativar a importação automática para o seu projeto (ativaremos isso por padrão mais tarde). Isso permitirá que o Studio importe novamente o build.gradle sempre que você o editar.

Criar sabores não significa que você usará um código personalizado para eles, para não criarmos as pastas. Você precisa criá-los você mesmo.

Se você olhar para minha palestra de E / S, verá como misturamos valores dos sabores e construímos o tipo para criar a variante.

Para a fonte Java:

src/main/java
src/flavor1/java
src/debug/java

são todos os 3 usados ​​para criar uma única saída. Isso significa que eles não podem definir a mesma classe.

Se você quiser ter uma versão diferente da mesma classe nos dois tipos, precisará criar nos dois tipos.

src/flavor1/java/com/foo/A.java
src/flavor2/java/com/foo/A.java

E então seu código em src / main / java pode fazer

import com.foo.A

dependendo do sabor selecionado, a versão correta do com.foo.A é usada.

Isso também significa que ambas as versões de A devem ter a mesma API (pelo menos quando se trata da API usada pelas classes em src / main / java / ...

Editar para corresponder à pergunta revisada

Além disso, é importante colocar a mesma classe A apenas em pastas de origem mutuamente exclusivas. Nesse caso, src / flavor1 / java e src / flavor2 / java nunca são selecionados juntos, mas main e flavor1 são.

Se você deseja fornecer uma versão diferente de uma atividade com um sabor diferente, não a coloque em src / main / java.

Observe que, se você tivesse três sabores e apenas desejasse um sabor personalizado para o sabor1, enquanto sabor2 e sabor3 compartilhavam a mesma atividade, era possível criar pastas de origem comuns para essas duas outras atividades. Você tem total flexibilidade na criação de novas pastas de origem e na configuração do conjunto de fontes para usá-las.

Para seus outros pontos:

É normal que a pasta da segunda fonte de sabor não seja azul. Você precisa mudar para o segundo sabor para ativá-lo e, em seguida, poderá criar pacotes e classes dentro. Até então, o Studio não considerava uma pasta de origem. Esperamos melhorar isso no futuro para conscientizar o IDE dessas pastas de origem inativas .

Eu acho que também é normal que você não possa criar arquivos de recursos na pasta res. O sistema de menus não foi atualizado para lidar com todas essas pastas de recursos extras. Isso virá mais tarde.

Xavier Ducrohet
fonte
1
Eu adicionei alguns elementos novos no final da minha resposta, mas a duplicata faz sentido. Você não pode ter a mesma classe em src / main / java e src / flavor1 / java, pois ambos são usados ​​ao selecionar flavor1. Na minha resposta, observe como eu coloco a mesma classe em apenas flavor1 / java e flavor2 / java, pois são exclusivos e nunca ativados juntos.
Xavier Ducrohet
Ei, Xavier, você pode me dar uma descrição mais detalhada de como eu posso usar uma versão diferente de uma atividade nos meus sabores? Eu tenho um projeto de teste no qual quero usar versões diferentes da minha MainActivity, mas nos dois aplicativos (sabor1 e sabor2) há apenas a versão do main / java. Quando não coloco MainActivity dentro de main / java, o aplicativo falha quando inicio.
JensJensen
@XavierDucrohet, que tal ter recursos diferentes e código diferente com base nos sabores, mas tê-los em módulos diferentes, para que possamos incluir um módulo ou outro com base no sabor, sem precisar misturar código e recursos no mesmo projeto raiz? Isso é suportado?
Valerio Santinelli
3
@ValerioSantinelli Você pode fazer dependências por sabor. UseflavorCompile ...
Xavier Ducrohet
@XavierDucrohet Eu tentei o que você sugeriu, mas não está funcionando como eu esperava. Você pode ver como o meu projeto está estruturado lá: stackoverflow.com/q/24410995/443136
Valerio Santinelli
19

"Sabores do produto" no Android

Algumas vezes me perguntam como trabalhar com diferentes hosts, ícones ou mesmo nomes de pacotes, dependendo de versões diferentes do mesmo aplicativo.

Há muitas razões para fazer isso e um caminho fácil: sabores do produto.

Você pode definir em seu script build.gradle esse tipo de coisa que eu descrevi antes.

Sabores do produto Parte deste artigo foi escrita pensando em sabores do produto, então, o que são? Em relação à documentação do Android:

Um tipo de produto define uma versão customizada da construção do aplicativo pelo projeto. Um único projeto pode ter diferentes tipos que alteram o aplicativo gerado.

Como você pode defini-los? Você deve escrever em seu build.gradle quais os sabores que deseja definir:

productFlavors {  
        ...
        devel {
            ...
        }

        prod {
            ...
        }
    }

Agora, teremos dois sabores diferentes do nosso aplicativo. Você pode verificá-lo no Android Studio também na guia Build Variants

Criar variantes

Vários nomes de pacotes

E se você quiser instalar no seu telefone um aplicativo com estado de desenvolvimento e outro para estado de produção. Como você deve saber, é possível instalar apenas um aplicativo com o mesmo nome de pacote (se você tentar instalar um novo APK com o mesmo instalado no seu telefone, ele tentará atualizá-lo).

A única coisa que você precisa fazer é defini-lo em cada um dos sabores do seu produto:

android {  
    productFlavors {
        devel {
            applicationId "zuul.com.android.devel"
        }
        prod {
            applicationId "zuul.com.android"
        }
    }
}

Enviar solicitações para vários hosts, dependendo do sabor Como antes, você deve incluir alguns parâmetros no campo de configuração de sabor do seu produto.

android {  
    productFlavors {
        devel {
            applicationId "zuul.com.android.devel"
            buildConfigField 'String', 'HOST', '"http://192.168.1.34:3000"'

        }

        prod {
            applicationId "zuul.com.android"
               buildConfigField 'String', 'HOST', '"http://api.zuul.com"'

        }
    }
}

Como exemplo, tentaremos mostrar como você pode integrar isso ao Retrofit para enviar uma solicitação ao servidor apropriado sem manipular qual servidor você está apontando e com base no sabor. Nesse caso, este é um trecho do aplicativo para Android Zuul:

public class RetrofitModule {

    public ZuulService getRestAdapter() {
        RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(BuildConfig.HOST)
                .setLogLevel(RestAdapter.LogLevel.FULL)
                .build();
        return restAdapter.create(ZuulService.class);
    }

}

Como você pode ver, basta usar a BuildConfigclass para acessar a variável que você acabou de definir.

Qualquer variável disponível através do seu código A variável HOST não é a única que você pode expor no seu código. Você pode fazer o que quiser:

prod {  
    applicationId "zuul.com.android"
    buildConfigField 'String', 'HOST', '"http://api.zuul.com"'
    buildConfigField 'String', 'FLAVOR', '"prod"'
    buildConfigField "boolean", "REPORT_CRASHES", "true"
}

Você pode acessá-los da seguinte maneira:

BuildConfig.HOST  
BuildConfig.FLAVOR  
BuildConfig.REPORT_CRASHES  

Ícones diferentes por sabor Se você deseja ter ícones diferentes por sabor, para poder detectar visualmente qual deles está abrindo (você pode fazê-lo pelo nome também ... Mas não cabe no espaço!), Basta para definir novas estruturas de diretório para cada um dos tipos.

No exemplo que acabei de usar, existem dois sabores: devel e prod. Em seguida, poderíamos definir duas novas estruturas de diretório para definir os recursos que desejamos:

estrutura

Isso funciona com outros tipos de recursos strings.xml, integers.xml, arrays.xml, como etc.

Definir configurações de assinatura

Para configurar manualmente as configurações de assinatura para o seu tipo de versão, usando as configurações de versão Gradle:

1. Crie um keystore. Um keystore é um arquivo binário que contém um conjunto de chaves privadas. Você deve manter seu keystore em um local seguro. 2. Crie uma chave privada. Uma chave privada representa a entidade a ser identificada com o aplicativo, como uma pessoa ou uma empresa. 3. Adicione a configuração de assinatura ao arquivo build.gradle no nível do módulo:

android {
...
defaultConfig {...}
signingConfigs {
    release {
        storeFile file("myreleasekey.keystore")
        storePassword "password"
        keyAlias "MyReleaseKey"
        keyPassword "password"
    }
}
buildTypes {
    release {
        ...
        signingConfig signingConfigs.release
    }
}

}

Gere um APK assinado:

Para gerar um APK assinado, selecione Criar> Gerar APK assinado no menu principal. O pacote em app / build / apk / app-release.apk agora está assinado com sua chave de lançamento.

ref: https://developer.android.com/studio/build/build-variants.html#signing,http://blog.brainattica.com/how-to-work-with-flavours-on-android/

A-Droid Tech
fonte
7

Parece que você precisa recarregar seu projeto depois de adicionar novos sabores build.gradle. Depois disso, você verá 4 variantes de compilação na visualização Variantes de compilação (você acessa a partir da borda esquerda da janela).

Em relação aos diretórios de origem adicionais, parece que você precisa criá-los manualmente: src/flavor1/javae src/flavor2/java. Você verá que alterar o sabor na visualização "Construir variantes" alterará os diretórios de origem ativos no momento (o diretório fica azul quando é um diretório de origem ativo )

Finalmente, "Gradle criará novas sourceSets para seus novos sabores" significa que Gradle irão criar os objetos android.sourceSets.flavor1e android.sourceSets.flavor2e você pode usá-los em seu script build.gradle. Mas esses objetos são criados dinamicamente, é por isso que você não os vê no build.gradle(sugiro que você leia isto: http://www.gradle.org/docs/current/userguide/tutorial_using_tasks.html Especialmente no 6.6: ele explica o criação de tarefa dinâmica.Um script gradle é um script groovy, então eu sugiro que você se familiarize com o groovy também)

ben75
fonte
2
Eu acho que a nota de importação é a Build VariantsView, eu não percebi isso.
Chris.Jenkins
2

Eu tive o mesmo problema ao migrar meu projeto para Gradle. O problema era que a compilação não encontrou a pasta de recursos apropriada. Corrigi-o adicionando isso sob o elemento android em build.gradle:

sourceSets {
        main {
            res.srcDirs = ['myProject/res']
        }
    }
Tomer
fonte
0

Algo que é importante e me bloqueou por um bom tempo é que o nome do sabor que precisa corresponder ao pacote em oposição ao pacote definido dentro da definição de sabor em gradle. Por exemplo:

src/flavor1/java/com/foo/A.java

vai combinar

productFlavors {
  flavor1 {
    packageName 'com.android.studio.test.foobar'
  }
}

mas

src/foobar/java/com/foo/A.java não será usado para a compilação flavor1.

bitrock
fonte
0

Em gradle:

Para tipos de compilação, você só precisa de:

buildTypes {
   release{
    //proguard, signing etc.
   }
   debug {
    //development
   }
  }
}

E então, para sabores, você adiciona os que precisa

productFlavors {
    pro {
        applicationIdSuffix '.paid'
        buildConfigField 'boolean', 'PRO', 'true'
    }
    free {
        applicationIdSuffix '.free'
        buildConfigField 'boolean', 'PRO', 'false'
    }
}
TouchBoarder
fonte