Usando tipos de compilação no Gradle para executar o mesmo aplicativo que usa o ContentProvider em um dispositivo

124

Eu configurei o Gradle para adicionar o sufixo do nome do pacote ao meu aplicativo de depuração para que eu pudesse ter a versão de lançamento que estou usando e a versão de depuração em um telefone. Eu estava fazendo referência a isso: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

Meu arquivo build.gradle fica assim:

...
android
{
    ...
    buildTypes
    {
        debug
        {
            packageNameSuffix ".debug"
            versionNameSuffix " debug"
        }
    }
}

Tudo funciona bem até eu começar a usar um ContentProvider no meu aplicativo. Eu recebo:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

Entendo que isso acontece porque dois aplicativos (versão e depuração) estão registrando a mesma autoridade do ContentProvider.

Eu vejo uma possibilidade de resolver isso. Se bem entendi, você poderá especificar arquivos diferentes para usar na criação. Então eu devo poder colocar autoridades diferentes em arquivos de recursos diferentes (e do Manifest Set Authority como recurso de string) e dizer ao Gradle para usar recursos diferentes para a compilação de depuração. Isso é possível? Se sim, qualquer dica sobre como conseguir isso seria incrível!

Ou talvez seja possível modificar diretamente o Manifest usando Gradle? Qualquer outra solução sobre como executar o mesmo aplicativo com o ContentProvider em um dispositivo é sempre bem-vinda.

MantasV
fonte
Para os interessados ​​em rastrear o suporte upstream para este caso de uso: Relatório de bug do AOSP . A postura atual "oficial" é usar a solução anuladora de manifesto .
desseim

Respostas:

226

Nenhuma das respostas existentes me satisfez, no entanto, a liberdade estava próxima. Então é assim que eu estou fazendo isso. Primeiro de tudo no momento em que estou trabalhando:

  • Android Studio Beta 0.8.2
  • Gradle plugin 0.12. +
  • Gradle 1.12

Meu objetivo é executar a Debugversão juntamente com a Releaseversão no mesmo dispositivo, usando o mesmo ContentProvider.


No build.gradle do seu conjunto de aplicativos, sufixo para a compilação Debug:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

Na propriedade do conjunto de arquivos AndroidManifest.xml :android:authoritiesContentProvider

<provider
    android:name="com.example.app.YourProvider"
    android:authorities="${applicationId}.provider"
    android:enabled="true"
    android:exported="false" >
</provider>

Na propriedade do conjunto de códigosAUTHORITY que pode ser usada sempre que necessário em sua implementação:

public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";

Dica: antes de serBuildConfig.PACKAGE_NAME

É isso aí! Funcionará como um encanto. Continue lendo se você usar o SyncAdapter!


Atualização para SyncAdapter (14.11.2014)

Mais uma vez, começarei com minha configuração atual:

  • Android Studio Beta 0.9.2
  • Gradle plugin 0.14.1
  • Gradle 2.1

Basicamente, se você precisar personalizar alguns valores para diferentes construções, poderá fazê-lo no arquivo build.gradle:

  • use buildConfigField para acessá-lo a partir da BuildConfig.javaclasse
  • use resValue para acessá-lo a partir de recursos, por exemplo, @ string / your_value

Como alternativa aos recursos, você pode criar diretórios buildType ou flavor separados e substituir XMLs ou valores dentro deles. No entanto, não vou usá-lo no exemplo abaixo.

Exemplo


No arquivo build.gradle, adicione o seguinte:

defaultConfig {
    resValue "string", "your_authorities", applicationId + '.provider'
    resValue "string", "account_type", "your.syncadapter.type"
    buildConfigField "String", "ACCOUNT_TYPE", '"your.syncadapter.type"'
}

buildTypes {
    debug {
        applicationIdSuffix ".debug"
        resValue "string", "your_authorities", defaultConfig.applicationId + '.debug.provider'
        resValue "string", "account_type", "your.syncadapter.type.debug"
        buildConfigField "String", "ACCOUNT_TYPE", '"your.syncadapter.type.debug"'
    }
}

Você verá resultados na classe BuildConfig.java

public static final String ACCOUNT_TYPE = "your.syncadapter.type.debug";

e em build / gerados / res / gerados / depuração / valores / gerados.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- Automatically generated file. DO NOT MODIFY -->
    <!-- Values from default config. -->
    <item name="account_type" type="string">your.syncadapter.type.debug</item>
    <item name="authorities" type="string">com.example.app.provider</item>

</resources>

No seu authenticator.xml, use o recurso especificado no arquivo build.gradle

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
                       android:accountType="@string/account_type"
                       android:icon="@drawable/ic_launcher"
                       android:smallIcon="@drawable/ic_launcher"
                       android:label="@string/app_name"
/>

No syncadapter.xml, use o mesmo recurso novamente e @ string / autoridades também

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
              android:contentAuthority="@string/authorities"
              android:accountType="@string/account_type"
              android:userVisible="true"
              android:supportsUploading="false"
              android:allowParallelSyncs="false"
              android:isAlwaysSyncable="true"
        />

Dica: o preenchimento automático (Ctrl + Espaço) não funciona para esses recursos gerados, portanto, você deve digitá-los manualmente

Damian Petla
fonte
7
A melhor resposta IMHO. Bom exemplo curto e simples.
Rekire
Sim, essa é a melhor solução alternativa que eu já vi até agora. Muito obrigado por compartilhar! Ainda tenho outro problema não relacionado a isso, pois preciso atualizar um Intent explícito em um arquivo preferências.xml para usar o novo nome do pacote. code.google.com/p/android/issues/detail?id=57460
Bernd S
@BerndS Publiquei um comentário no seu problema com a solução. Você precisa entender que a alteração do applicationId, substituindo-o ou configurando o sufixo, não afeta os pacotes java. É apenas um identificador do seu aplicativo e é dissociado dos pacotes java. Veja minha resposta para outra pergunta stackoverflow.com/questions/24178007/…
Damian Petla 8/08
1
@JJD As modificações às quais você vincula funcionariam sem nenhum script de construção personalizado. Se você deseja usar espaços reservados $ {applicationId} para sync_adapter.xml, authenticator.xml, deverá customizar seu script build.gradle. Vejo que você já fez muito em seu script build.gradle para se sentir confortável com a ideia. Você seguiu as instruções na minha resposta e ainda não funcionou?
22814 Rob Robeeeeee
1
Atualizei minha resposta com instruções para o syncadapter
Damian Petla
39

Nova dica do sistema de compilação do Android: renomeação da autoridade ContentProvider

Acho que todos vocês já ouviram falar do novo sistema de compilação baseado no Android Gradle. Sejamos honestos, este novo sistema de compilação é um grande passo à frente em comparação com o anterior. Ainda não é final (até o momento em que este artigo foi escrito, a versão mais recente é 0.4.2), mas você já pode usá-lo com segurança na maioria dos seus projetos.

Pessoalmente, mudei a maior parte do meu projeto para esse novo sistema de compilação e tive alguns problemas devido à falta de suporte em algumas situações específicas. Um deles é o suporte à renomeação da autoridade ContentProvider

O novo sistema criado para Android permite lidar com diferentes tipos de aplicativo, modificando o nome do pacote no momento da criação. Uma das principais vantagens dessa melhoria é que agora você pode ter duas versões diferentes do seu aplicativo instaladas no mesmo dispositivo ao mesmo tempo. Por exemplo:

android {
   compileSdkVersion 17
   buildToolsVersion "17.0.0"

   defaultConfig {
       packageName "com.cyrilmottier.android.app"
       versionCode 1
       versionName "1"
       minSdkVersion 14 // Listen to +Jeff Gilfelt advices :)
       targetSdkVersion 17
   }

   buildTypes {
       debug {
        packageNameSuffix ".debug"
            versionNameSuffix "-debug"
       }
   }
}

Usando essa configuração Gradle, você pode montar dois APKs diferentes:

• Um APK de depuração com o nome do pacote com.cyrilmottier.android.app.debug • Um APK de lançamento com o nome do pacote com.cyrilmottier.android.app

O único problema é que você não poderá instalar os dois APKs ao mesmo tempo se ambos expuserem um ContentProvider com as mesmas autoridades. Logicamente, precisamos renomear a autoridade, dependendo do tipo de compilação atual ... mas isso não é suportado pelo sistema de compilação Gradle (ainda? ... Tenho certeza que será corrigido em breve). Então, aqui está um caminho a percorrer:

Primeiro, precisamos mover a declaração ContentProvider do manifesto Android do provedor para o tipo de construção apropriado. Para fazer isso, teremos simplesmente:

src / debug / AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.cyrilmottier.android.app"
   android:versionCode="1"
   android:versionName="1">

   <application>

       <provider
           android:name=".provider.Provider1"
           android:authorities="com.cyrilmottier.android.app.debug.provider"
           android:exported="false" />

   </application>
</manifest>

src / release / AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.cyrilmottier.android.app"
   android:versionCode="1"
   android:versionName="1">

   <application>

       <provider
           android:name=".provider.Provider1"
           android:authorities="com.cyrilmottier.android.app.provider"
           android:exported="false" />

   </application>
</manifest>

Certifique-se de remover a declaração ContentProvider do AndroidManifest.xml em src / main / porque Gradle não sabe como mesclar ContentProviders com o mesmo nome, mas com uma autoridade diferente.

Finalmente, podemos precisar acessar a autoridade no código. Isso pode ser feito facilmente usando o arquivo BuildConfig e o método buildConfig:

android {   
   // ...

    final PROVIDER_DEBUG = "com.cyrilmottier.android.app.debug.provider"
    final PROVIDER_RELEASE = "com.cyrilmottier.android.app.provider"

   buildTypes {
       debug {
           // ...
           buildConfigField "String", "PROVIDER_AUTHORITY", PROVIDER_DEBUG
       }

       release {
           buildConfigField "String", "PROVIDER_AUTHORITY", PROVIDER_RELEASE
       }
   }
}

Graças a esta solução alternativa, você poderá usar o BuildConfig.PROVIDER_AUTHORITY no seu ProviderContract e instalar duas versões diferentes do seu aplicativo ao mesmo tempo.


Originalmente no Google+: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ

Cyril Mottier
fonte
1
Para alguém que não pode executar o gradle, devido ao erro de sintaxe. Aqui está a resposta: stackoverflow.com/questions/20678118/…
Renan Franca
23

Embora o exemplo de Cyril funcione muito bem se você tiver apenas alguns tipos de compilação, fica rapidamente complicado se você tiver muitos tipos de compilação e / ou sabores de produtos, pois precisará manter muitos AndroidManifest.xml diferentes.

Nosso projeto consiste em 3 tipos de compilação diferentes e 6 tipos, totalizando 18 variantes de compilação. Em vez disso, adicionamos suporte para ".res-auto" nas autoridades ContentProvider, que se expandem para o nome do pacote atual e eliminam a necessidade de manter AndroidManifest.xml diferente

/**
 * Version 1.1.
 *
 * Add support for installing multiple variants of the same app which have a
 * content provider. Do this by overriding occurrences of ".res-auto" in
 * android:authorities with the current package name (which should be unique)
 *
 * V1.0 : Initial version
 * V1.1 : Support for ".res-auto" in strings added, 
 *        eg. use "<string name="auth">.res-auto.path.to.provider</string>"
 *
 */
def overrideProviderAuthority(buildVariant) {
    def flavor = buildVariant.productFlavors.get(0).name
    def buildType = buildVariant.buildType.name
    def pathToManifest = "${buildDir}/manifests/${flavor}/${buildType}/AndroidManifest.xml"

    def ns = new groovy.xml.Namespace("http://schemas.android.com/apk/res/android", "android")
    def xml = new XmlParser().parse(pathToManifest)
    def variantPackageName = xml.@package

    // Update all content providers
    xml.application.provider.each { provider ->
        def newAuthorities = provider.attribute(ns.authorities).replaceAll('.res-auto', variantPackageName)
        provider.attributes().put(ns.authorities, newAuthorities)
    }

    // Save modified AndroidManifest back into build dir
    saveXML(pathToManifest, xml)

    // Also make sure that all strings with ".res-auto" are expanded automagically
    def pathToValues = "${buildDir}/res/all/${flavor}/${buildType}/values/values.xml"
    xml = new XmlParser().parse(pathToValues)
    xml.findAll{it.name() == 'string'}.each{item ->
        if (!item.value().isEmpty() && item.value()[0].startsWith(".res-auto")) {
            item.value()[0] = item.value()[0].replace(".res-auto", variantPackageName)
        }
    }
    saveXML(pathToValues, xml)
}

def saveXML(pathToFile, xml) {
    def writer = new FileWriter(pathToFile)
    def printer = new XmlNodePrinter(new PrintWriter(writer))
    printer.preserveWhitespace = true
    printer.print(xml)
}

// Post processing of AndroidManifest.xml for supporting provider authorities
// across build variants.
android.applicationVariants.all { variant ->
    variant.processManifest.doLast {
        overrideProviderAuthority(variant)
    }
}

O código de exemplo pode ser encontrado aqui: https://gist.github.com/cmelchior/6988275

Christian Melchior
fonte
Também mudei para usar algo muito semelhante ao meu projeto, porque eu tinha o mesmo problema com os sabores de compilação. Essa abordagem funciona muito bem por enquanto.
MantasV
2
O FileWriter causa problemas nos arquivos utf-8, pelo menos no meu Mac OS. Eu mudei a linha relacionada com: escritor def = new OutputStreamWriter (new FileOutputStream (pathtofile), "UTF-8")
Reza Mohammadi
Isso é realmente ótimo, obrigado! Fiz uma pequena alteração para evitar quebras com seqüências de caracteres formatadas. gist.github.com/paour/8475929
Pierre-Luc Paour
Isso foi muito útil, mas encontrei um problema em que ele não era compilado após uma limpeza porque não havia arquivo values.xml na pasta de compilação no estágio processManifest. Isso não existe até o estágio processResources, quando é tarde demais para modificar o manifesto, para substituir .res-auto nos arquivos de manifesto e de valores, acho que você precisaria de 2 funções, uma chamada por variante. processManifest.doLast, o outro chamado por variant.processResources.doLast.
Niall
20

Desde a versão 0.8.3 do plug-in (na verdade, 0.8.1, mas não estava funcionando corretamente), é possível definir recursos no arquivo de construção para que esta seja uma solução mais limpa, pois você não precisa criar arquivos de strings nem depuração / liberação adicional pastas

build.gradle

android {
    buildTypes {
        debug{
            resValue "string", "authority", "com.yourpackage.debug.provider"
        }
        release {
            resValue "string", "authority", "com.yourpackage.provider"
        }
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.yourpackage"
   android:versionCode="1"
   android:versionName="1">

   <application>

       <provider
           android:name=".provider.Provider1"
           android:authorities="@string/authority"
           android:exported="false" />

   </application>
</manifest>
rciovati
fonte
2
Cuidado, as autoridades baseadas em recursos só funcionam em Android 2.2.1 e posterior: github.com/android/platform_frameworks_base/commit/...
Pierre-Luc Paour
Obrigado pelo esclarecimento.
rciovati 14/05
1
isso também é muito útil no searchable.xml para android: searchSuggestAuthority, porque lá você não pode usar $ {applicationId}
user114676
13

Não sei se alguém mencionou. Na verdade, após o plug-in Android Gradle 0.10+, a fusão do manifesto fornecerá o suporte oficial para esta função: http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger

No AndroidManifest.xml, você pode usar $ {packageName} assim:

<provider
    android:name=".provider.DatabasesProvider"
    android:authorities="${packageName}.databasesprovider"
    android:exported="true"
    android:multiprocess="true" />

E no seu build.gradle você pode ter:

productFlavors {
    free {
        packageName "org.pkg1"
    }
    pro {
        packageName "org.pkg2"
    }
}

Veja o exemplo completo aqui: https://code.google.com/p/anymemo/source/browse/AndroidManifest.xml#152

e aqui: https://code.google.com/p/anymemo/source/browse/build.gradle#41

Liberdade
fonte
São ótimas notícias, mas não parece ser uma solução completa no caso de elementos <searchable> que precisam fazer referência à autoridade, pois eles não fazem parte do manifesto (mas as estratégias de mesclagem existentes funcionam para esses arquivos, ao contrário do manifesto).
Pierre-Luc Paour
1
Você não precisa usar sabores para isso, ele funciona com tipos de compilação também. Além disso, seria bom mencionar que você pode usar o BuildConfig.PACKAGE_NAME para obter uma referência estática ao seu pacote. Isso é útil para provedores de conteúdo em que a autoridade precisa ser conhecida em tempo de execução para consultar o provedor de conteúdo.
Matt Wolfe
1
Também devem ser atualizados para usar $ {applicationId} em vez de $ {packageName} para o Android: autoridades
Bernd S
8

Use ${applicationId}espaços reservados no xml e BuildConfig.APPLICATION_IDno código.

Você precisará estender o script de construção para habilitar espaços reservados em arquivos xml diferentes do manifesto. Você pode usar um diretório de origem por variante de compilação para fornecer versões diferentes dos arquivos xml, mas a manutenção se tornará complicada muito rapidamente.

AndroidManifest.xml

Você pode usar o espaço reservado applicationId fora da caixa no manifesto. Declare seu provedor assim:

<provider
    android:name=".provider.DatabaseProvider"
    android:authorities="${applicationId}.DatabaseProvider"
    android:exported="false" />

Observe o ${applicationId}bit. Isso é substituído no momento da construção pelo applicationId real da variante de construção que está sendo construída.

Em código

Seu ContentProvider precisa construir a cadeia de autoridade no código. Pode usar a classe BuildConfig.

public class DatabaseContract {
    /** The authority for the database provider */
    public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".DatabaseProvider";
    // ...
}

Observe o BuildConfig.APPLICATION_IDbit. É uma classe gerada com o applicationId real para a variante de compilação que está sendo construída.

res / xml / files, por exemplo, syncadapter.xml, accountauthenticator.xml

Se você deseja usar um adaptador de sincronização, precisará fornecer metadados para o ContentProvider e o AccountManager em arquivos xml no diretório res / xml /. O espaço reservado applicationId não é suportado aqui. Mas você pode estender o script de construção para invadir.

<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="${applicationId}"
    android:allowParallelSyncs="false"
    android:contentAuthority="${applicationId}.DatabaseProvider"
    android:isAlwaysSyncable="true"
    android:supportsUploading="true"
    android:userVisible="true" />

<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="${applicationId}"
    android:icon="@drawable/ic_launcher"
    android:label="@string/account_authenticator_label"
    android:smallIcon="@drawable/ic_launcher" />

Mais uma vez, observe o ${applicationId}. Isso funciona apenas se você adicionar o script gradle abaixo à raiz do seu módulo e aplicá-lo em build.gradle.

build.gradle

Aplique o script de construção extra no módulo build.gradle script. Um bom lugar é abaixo do plug-in Android gradle.

apply plugin: 'com.android.application'
apply from: './build-processApplicationId.gradle'

android {
    compileSdkVersion 21
    // etc.

build-processApplicationId.gradle

Abaixo está a fonte de trabalho para um script de construção res / xml / placeholder. Uma versão melhor documentada está disponível no github . Melhorias e extensões são bem-vindas.

def replace(File file, String target, String replacement) {
    def result = false;

    def reader = new FileReader(file)
    def lines = reader.readLines()
    reader.close()

    def writer = new FileWriter(file)
    lines.each { line ->
        String replacedLine = line.replace(target, replacement)
        writer.write(replacedLine)
        writer.write("\n")
        result = result || !replacedLine.equals(line)
    }
    writer.close()

    return result
}

def processXmlFile(File file, String applicationId) {
    if (replace(file, "\${applicationId}", applicationId)) {
        logger.info("Processed \${applicationId} in $file")
    }
}

def processXmlDir(File dir, String applicationId) {
    dir.list().each { entry ->
        File file = new File(dir, entry)
        if (file.isFile()) {
            processXmlFile(file, applicationId)
        }
    }
}

android.applicationVariants.all { variant ->
    variant.mergeResources.doLast {
        def applicationId = variant.mergedFlavor.applicationId + (variant.buildType.applicationIdSuffix == null ? "" : variant.buildType.applicationIdSuffix)
        def path = "${buildDir}/intermediates/res/${variant.dirName}/xml/"
        processXmlDir(new File(path), applicationId)
    }
}

Strings.xml

Na minha opinião, não há necessidade de adicionar suporte de espaço reservado para cadeias de recursos. Para o caso de uso acima, pelo menos, não é necessário. No entanto, você pode alterar facilmente o script, não apenas substituindo espaços reservados no diretório res / xml /, mas também no diretório res / values ​​/.

Rob Meeuwisse
fonte
6

Prefiro uma mistura entre Cyril e rciovati. Eu acho que é mais simples, você só tem duas modificações.

Os build.gradleparece:

android {
    ...
    productFlavors {
        production {
            packageName "package.name.production"
            resValue "string", "authority", "package.name.production.provider"
            buildConfigField "String", "AUTHORITY", "package.name.production.provider"
        }

        testing {
            packageName "package.name.debug"
            resValue "string", "authority", "package.name.debug.provider"
            buildConfigField "String", "AUTHORITY", "package.name.debug.provider"
        }
    }
    ...
}

E o AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="package.name" >

    <application
        ...>

        <provider android:name=".contentprovider.Provider" android:authorities="@string/authority" />

    </application>
</manifest>
icastell
fonte
5

gradle.build

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.example.awsomeapp"
        minSdkVersion 9
        targetSdkVersion 23
        versionCode 1
        versionName "1.0.0"
    }

    productFlavors
    {
        prod {
            applicationId = "com.example.awsomeapp"
        }

        demo {
            applicationId = "com.example.awsomeapp.demo"
            versionName = defaultConfig.versionName + ".DEMO"
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
            debuggable false
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }

        debug {
            applicationIdSuffix ".debug"
            versionNameSuffix = ".DEBUG"
            debuggable true
        }
    }

    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            // rename the apk
            def file = output.outputFile;
            def newName;
            newName = file.name.replace(".apk", "-" + defaultConfig.versionName + ".apk");
            newName = newName.replace(project.name, "awsomeapp");
            output.outputFile = new File(file.parent, newName);
        }

        //Generate values Content Authority and Account Type used in Sync Adapter, Content Provider, Authenticator
        def valueAccountType = applicationId + '.account'
        def valueContentAuthority = applicationId + '.authority'

        //generate fields in Resource string file generated.xml
        resValue "string", "content_authority", valueContentAuthority
        resValue "string", "account_type", valueAccountType

        //generate fields in BuildConfig class
        buildConfigField "String", "ACCOUNT_TYPE", '"'+valueAccountType+'"'
        buildConfigField "String", "CONTENT_AUTHORITY", '"'+valueContentAuthority+'"'

        //replace field ${valueContentAuthority} in AndroidManifest.xml
        mergedFlavor.manifestPlaceholders = [ valueContentAuthority: valueContentAuthority ]
    }
}

authenticator.xml

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="@string/account_type"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:smallIcon="@drawable/ic_launcher" />

sync_adapter.xml

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
              android:contentAuthority="@string/content_authority"
              android:accountType="@string/account_type"
              android:userVisible="true"
              android:allowParallelSyncs="false"
              android:isAlwaysSyncable="true"
              android:supportsUploading="true"/>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0.0" package="com.example.awsomeapp">

    <uses-permission android:name="android.permission.GET_ACCOUNTS"/><!-- SyncAdapter and GCM requires a Google account. -->
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>

    <!-- GCM Creates a custom permission so only this app can receive its messages. -->
    <permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
    <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>

    <application....
    .......

        <!-- Stub Authenticator --> 
        <service 
                android:name="com.example.awsomeapp.service.authenticator.CAuthenticatorService"
                android:exported="true">
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator"/>
            </intent-filter>
            <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator"/>
        </service>
        <!--  -->

        <!-- Sync Adapter -->
        <service
                android:name="com.example.awsomeapp.service.sync.CSyncService"
                android:exported="true"
                android:process=":sync">
            <intent-filter>
                <action android:name="android.content.SyncAdapter"/>
            </intent-filter>
            <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/sync_adapter" />
        </service>
        <!--  -->

        <!-- Content Provider -->
        <provider android:authorities="${valueContentAuthority}"
            android:exported="false" 
            android:name="com.example.awsomeapp.database.contentprovider.CProvider">
        </provider>
        <!--  --> 
    </application>
</manifest>

Código:

public static final String CONTENT_AUTHORITY = BuildConfig.CONTENT_AUTHORITY;
public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
maros136
fonte
4

Com base no exemplo de @ChristianMelchior, aqui está minha solução, que corrige dois problemas nas soluções anteriores:

  • soluções que alteram values.xml no diretório build causam uma reconstrução completa dos recursos (incluindo aapt de todos os drawables)

  • por um motivo desconhecido, o IntelliJ (e provavelmente o Android Studio) não processa os recursos de maneira confiável, fazendo com que a construção contenha .res-autoautoridades do fornecedor não substituídas

Essa nova solução faz as coisas da maneira Gradle, criando uma nova tarefa e permite construções incrementais, definindo arquivos de entrada e saída.

  1. crie um arquivo (no exemplo, coloquei-o em um variantsdiretório), formatado como um arquivo xml de recurso, que contém recursos de string. Eles serão mesclados aos recursos do aplicativo e qualquer ocorrência .res-autonos valores será substituída pelo nome do pacote da variante, por exemplo<string name="search_provider">.res-auto.MySearchProvider</string>

  2. adicione o build_extras.gradlearquivo dessa essência ao seu projeto e faça referência a ele do principal build.gradleadicionando apply from: './build_extras.gradle'algum lugar acima do androidbloco

  3. certifique-se de definir um nome de pacote padrão adicionando-o ao android.defaultConfigbloco debuild.gradle

  4. em AndroidManifest.xmle outros arquivos de configuração (como xml/searchable.xmlpara provedores de pesquisa de preenchimento automático), faça referência ao provedor (por exemplo @string/search_provider)

  5. se você precisar obter o mesmo nome, poderá usar a BuildConfig.PACKAGE_NAMEvariável, por exemploBuildConfig.PACKAGE_NAME + ".MySearchProvider"

https://gist.github.com/paour/9189462


Atualização: esse método funciona apenas no Android 2.2.1 e posterior. Para plataformas anteriores, consulte esta resposta , que tem seu próprio conjunto de problemas, já que a nova fusão de manifestos ainda é muito difícil nas bordas…

Pierre-Luc Paour
fonte
Onde você está colocando seu diretório de variantes? Eu tenho um grande projeto do Android Studio que depende de vários módulos do Android - meu aplicativo principal e vários módulos da Biblioteca do Android. Posso construir a partir da linha de comando, mas quando tento construir a partir do Android Studio, ele procura em variants/res-auto-values.xmlrelação a /Applications/Android Studio.app/bin/. ou seja, não recebo FileNotFoundException para /Applications/Android Studio.app/bin/variants/res-auto-values.xml. Eu estou correndo em um mac. Essa é uma ótima solução, mas eu adoraria fazê-la funcionar no IDE para os outros membros da equipe.
user1978019
1
Corrigido meu próprio problema. Gradle parece resolver os caminhos usando System.getProperty("user.dir"), que retorna um resultado diferente quando chamado pela compilação do Android Studio. A solução é usar o caminho relativo ao diretório do projeto, retornado com gradle.startParameter.getProjectDir(). Veja meu comentário na essência vinculada de Paour também.
user1978019
Cuidado, as autoridades baseadas em recursos só funcionam em Android 2.2.1 e posterior: github.com/android/platform_frameworks_base/commit/...
Pierre-Luc Paour
2

Infelizmente, a versão atual (0.4.1) do plugin android não parece fornecer uma boa solução para isso. Eu não tive tempo para tentar isso ainda, mas uma possível solução para este problema seria a utilização de um recurso de cadeia @string/provider_authority, eo uso que no manifesto: android:authority="@string/provider_authority". Você tem um res/values/provider.xmlna pasta res de cada tipo de construção que deve substituir a autoridade; no seu caso, isso seriasrc/debug/res

Eu olhei para gerar o arquivo xml em tempo real, mas, novamente, não parece haver bons ganchos para ele na versão atual do plugin. Eu recomendaria fazer uma solicitação de recurso, no entanto, posso imaginar que mais pessoas irão encontrar esse mesmo problema.

Marcus Forsell Stahre
fonte
Olá Marcus, obrigado pela sua resposta. Sua solução sugerida é a única que consigo pensar por enquanto. Mas o meu problema é que não sei como conseguir isso com Gradle.
MantasV
2

A resposta neste post funciona para mim.

http://www.kevinrschultz.com/blog/2014/03/23/using-android-content-providers-with-multiple-package-names/

Eu uso 3 sabores diferentes, então crio 3 manifestos com o provedor de conteúdo em cada sabor, como disse kevinrschultz:

productFlavors {
    free {
        packageName "your.package.name.free"
    }

    paid {
        packageName "your.package.name.paid"
    }

    other {
        packageName "your.package.name.other"
    }
}

Seu principal manifesto não inclui provedores:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- Permissions -->
<application>
    <!-- Nothing about Content Providers at all -->
    <!-- Activities -->
    ...
    <!-- Services -->
    ...
</application>

E seu manifesto em cada sabor, incluindo o provedor.

Livre:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
    <!-- Content Providers -->
    <provider
        android:name="your.package.name.Provider"
        android:authorities="your.package.name.free"
        android:exported="false" >
    </provider>
</application>
</manifest>

Pago:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
    <!-- Content Providers -->
    <provider
        android:name="your.package.name.Provider"
        android:authorities="your.package.name.paid"
        android:exported="false" >
    </provider>
</application>
</manifest>

De outros:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
    <!-- Content Providers -->
    <provider
        android:name="your.package.name.Provider"
        android:authorities="your.package.name.other"
        android:exported="false" >
    </provider>
</application>
</manifest>
jcmore2
fonte
0

Por que não apenas adicionar isso?

type.packageNameSuffix = ". $ type.name"

guydemossyrock
fonte
0

Minha solução é usar a substituição de espaço reservado no AndroidManifest.xml. Ele também lida com packageNameSuffixatributos para que você possa ter debuge releasetambém qualquer outra compilação personalizada no mesmo dispositivo.

applicationVariants.all { variant ->
    def flavor = variant.productFlavors.get(0)
    def buildType = variant.buildType
    variant.processManifest.doLast {
        println '################# Adding Package Names to Manifest #######################'
        replaceInManifest(variant,
            'PACKAGE_NAME',
            [flavor.packageName, buildType.packageNameSuffix].findAll().join()) // ignores null
    }
}

def replaceInManifest(variant, fromString, toString) {
    def flavor = variant.productFlavors.get(0)
    def buildtype = variant.buildType
    def manifestFile = "$buildDir/manifests/${flavor.name}/${buildtype.name}/AndroidManifest.xml"
    def updatedContent = new File(manifestFile).getText('UTF-8').replaceAll(fromString, toString)
    new File(manifestFile).write(updatedContent, 'UTF-8')
}

Eu tenho isso em um gist se você quiser ver se ele evolui mais tarde.

Eu achei uma abordagem mais elegante do que os vários recursos e abordagens de análise de XML.

Saad Farooq
fonte