web-dev-qa-db-fra.com

Utilisation de types de construction dans Gradle pour exécuter la même application qui utilise ContentProvider sur un périphérique

J'ai configuré Gradle pour ajouter le suffixe du nom du package à mon application de débogage afin que je puisse disposer de la version que j'utilise et de la version de débogage sur un seul téléphone. Je faisais référence à ceci: http://tools.Android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

Mon fichier build.gradle ressemble à ceci:

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

Tout fonctionne bien jusqu'à ce que je commence à utiliser un ContentProvider dans mon application. Je reçois:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

Je comprends que cela se produit car deux applications (release et debug) enregistrent la même autorité ContentProvider.

Je vois une possibilité pour résoudre ce problème. Si je comprends bien, vous devriez pouvoir spécifier différents fichiers à utiliser lors de la construction. Ensuite, je devrais pouvoir mettre différentes autorités dans différents fichiers de ressources (et à partir de l'autorité de l'ensemble de manifestes en tant que ressource de chaîne) et indiquer à Gradle d'utiliser différentes ressources pour la construction du débogage. Est-ce possible? Si oui, alors des astuces pour y parvenir seraient géniales!

Ou peut-être est-il possible de modifier directement Manifest à l'aide de Gradle? Toute autre solution permettant d’exécuter la même application avec ContentProvider sur un seul appareil est toujours la bienvenue.

121
MantasV

Aucune des réponses existantes ne me satisfaisait, cependant Liberty était proche. Alors voici comment je le fais. Tout d’abord au moment où je travaille avec:

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

Mon objectif est d'exécuter Debug version avec Release version sur le même périphérique en utilisant le même ContentProvider.


Dans build.gradle de votre suffixe de jeu d'applications pour la construction de débogage:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

Dans AndroidManifest.xml ensemble de fichiers Android:authorities propriété de votre ContentProvider:

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

Dans votre code définissez AUTHORITY propriété qui peut être utilisée à tout moment dans votre implémentation:

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

Astuce: Avant c'était BuildConfig.PACKAGE_NAME

C'est ça! Cela fonctionnera comme un charme. Continuez à lire si vous utilisez SyncAdapter!


Mise à jour pour SyncAdapter (14.11.2014)

Une fois encore, je vais commencer par ma configuration actuelle:

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

En gros, si vous avez besoin de personnaliser certaines valeurs pour différentes versions, vous pouvez le faire à partir du fichier build.gradle:

  • utilisez buildConfigField pour y accéder depuis le BuildConfig.Java classe
  • utilisez resValue pour y accéder à partir de ressources, par ex. @ string/your_value

En guise d'alternative pour les ressources, vous pouvez créer des répertoires buildType ou flavour distincts et remplacer les XML ou les valeurs qu'ils contiennent. Cependant, je ne vais pas l'utiliser dans l'exemple ci-dessous.

Exemple


Dans le fichier build.gradle, ajoutez ce qui suit:

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

Vous verrez les résultats dans BuildConfig.Java class

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

et dans construire/généré/res/généré/debug/values ​​/ généré.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>

Dans votre authenticator.xml, utilisez la ressource spécifiée dans le fichier 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"
/>

Dans votre syncadapter.xml, utilisez à nouveau la même ressource et @ string/autorités aussi

<?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"
        />

Astuce: La complétion automatique (Ctrl + Espace) ne fonctionne pas pour ces ressources générées, vous devez donc les taper manuellement

221
Damian Petla

Nouveau conseil système Android: Renommer l’autorité ContentProvider

Je suppose que vous avez tous entendu parler du nouveau système de construction basé sur Android Gradle. Soyons honnêtes, ce nouveau système de construction est un énorme pas en avant par rapport au précédent. Ce n'est pas encore définitif (au moment d'écrire ces lignes, la dernière version est la 0.4.2), mais vous pouvez déjà l'utiliser en toute sécurité dans la plupart de vos projets.

J'ai personnellement transféré l'essentiel de mon projet vers ce nouveau système de construction et j'ai eu quelques problèmes en raison du manque de support dans certaines situations particulières. L’un d’eux est le support de l’autorité ContentProvider de renommer

Le nouveau système construit Android vous permet de traiter différents types de votre application en modifiant simplement le nom du package au moment de la compilation. L'un des principaux avantages de cette amélioration est que vous pouvez désormais installer deux versions différentes de votre application sur le même appareil en même temps. Par exemple:

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

En utilisant une telle configuration Gradle, vous pouvez assembler deux fichiers APK différents:

• Un APK de débogage avec le nom du paquet com.cyrilmottier.Android.app.debug • Un APK de version avec le nom du paquet com.cyrilmottier.Android.app

Le seul problème avec cela est que vous ne pourrez pas installer les deux fichiers APK en même temps s'ils exposent un fournisseur de contenu avec les mêmes autorités. Logiquement, nous devons renommer l'autorité en fonction du type de génération actuel… mais cela n'est pas pris en charge par le système de génération Gradle (pour le moment? ... je suis sûr que cela sera corrigé prochainement). Donc, voici un chemin à parcourir:

Premièrement, nous devons déplacer la déclaration ContentProvider du fournisseur Android vers le type de construction approprié. Pour ce faire, nous aurons simplement:

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>

Assurez-vous de supprimer la déclaration ContentProvider du fichier AndroidManifest.xml dans src/main/car Gradle ne sait pas comment fusionner ContentProviders ayant le même nom mais une autorité différente.

Enfin, nous aurons peut-être besoin d'accéder à l'autorité dans le code. Cela peut être fait assez facilement en utilisant le fichier BuildConfig et la méthode 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
       }
   }
}

Grâce à cette solution de contournement, vous pourrez utiliser BuildConfig.PROVIDER_AUTHORITY dans votre contrat ProviderContract et installer deux versions différentes de votre application simultanément.


À l'origine sur Google+: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ

39
Cyril Mottier

Bien que l'exemple de Cyril fonctionne très bien si vous ne possédez que quelques types de version, il devient rapidement compliqué si vous avez plusieurs types de version et/ou types de produits, car vous devez gérer de nombreux fichiers AndroidManifest.xml différents.

Notre projet se compose de 3 types de construction différents et de 6 versions différentes, totalisant 18 variantes de construction. Nous avons donc ajouté la prise en charge de ".res-auto" dans les autorisations ContentProvider, qui s'étend au nom de package actuel et supprime la nécessité de gérer un autre fichier AndroidManifest.xml.

/**
 * 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)
    }
}

Exemple de code peut être trouvé ici: https://Gist.github.com/cmelchior/6988275

23
Christian Melchior

Depuis la version 0.8.3 du plugin (en réalité 0.8.1 mais elle ne fonctionnait pas correctement), vous pouvez définir des ressources dans le fichier de construction. Il pourrait donc s'agir d'une solution plus propre, car vous n'avez pas besoin de créer de fichiers de chaînes ni de débogage/publication supplémentaire. Dossiers.

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>
20
rciovati

Je ne sais pas si quelqu'un en parle. En fait, après Android gradle plugin 0.10+, la fusion de manifestes fournira le support officiel pour cette fonction: http://tools.Android.com/tech-docs/new-build -system/user-guide/manifest-merger

Dans AndroidManifest.xml, vous pouvez utiliser $ {packageName} comme ceci:

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

Et dans votre build.gradle, vous pouvez avoir:

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

Voir l'exemple complet ici: https://code.google.com/p/anymemo/source/browse/AndroidManifest.xml#152

et ici: https://code.google.com/p/anymemo/source/browse/build.gradle#41

13
Liberty

Utilisation ${applicationId} espaces réservés en xml et BuildConfig.APPLICATION_ID dans du code.

Vous devrez étendre le script de génération pour activer les espaces réservés dans les fichiers XML autres que le manifeste. Vous pouvez utiliser un répertoire source par variante de construction pour fournir différentes versions des fichiers xml, mais la maintenance deviendra très fastidieuse.

AndroidManifest.xml

Vous pouvez utiliser l’espace réservé applicationId prêt à l'emploi dans le manifeste. Déclarez votre fournisseur comme ceci:

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

Noter la ${applicationId} peu. Celui-ci est remplacé au moment de la construction par l'ID application réel de la variante de construction en cours de construction.

En code

Votre fournisseur de contenu doit créer la chaîne d'autorité dans le code. Il peut utiliser la classe BuildConfig.

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

Noter la BuildConfig.APPLICATION_ID peu. Il s'agit d'une classe générée avec l'identificateur d'application actuel pour la variante de construction en cours de construction.

res/xml/files, par exemple. syncadapter.xml, accountauthenticator.xml

Si vous souhaitez utiliser un adaptateur de synchronisation, vous devez fournir des métadonnées pour ContentProvider et AccountManager dans des fichiers xml dans le répertoire res/xml /. L'espace réservé applicationId n'est pas pris en charge ici. Mais vous pouvez étendre le script de compilation vous-même pour le pirater.

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

Encore une fois, notez le ${applicationId}. Cela ne fonctionne que si vous ajoutez le script ci-dessous à la racine de votre module et l'appliquez à partir de build.gradle.

build.gradle

Appliquez le script de construction supplémentaire à partir du script de module build.gradle. Un bon endroit est en dessous du plugin Android gradle.

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

Android {
    compileSdkVersion 21
    // etc.

build-processApplicationId.gradle

Vous trouverez ci-dessous une source de travail pour un script de construction res/xml/placeholder. Une version mieux documentée est disponible sur github . Les améliorations et extensions sont les bienvenues.

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

À mon avis, il n'est pas nécessaire d'ajouter un support d'espace réservé pour les chaînes de ressources. Pour le cas d'utilisation ci-dessus au moins, ce n'est pas nécessaire. Cependant, vous pouvez facilement changer le script pour remplacer non seulement les espaces réservés dans le répertoire res/xml /, mais également dans le répertoire res/values ​​/.

8
Rob Meeuwisse

Je préférerais un mélange entre Cyril et Rciovati. Je pense que c'est plus simple, vous n'avez que deux modifications.

Le build.gradle Ressemble à ceci:

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

Et le 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>
6
icastell

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>

Code:

public static final String CONTENT_AUTHORITY = BuildConfig.CONTENT_AUTHORITY;
public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
5
maros136

J'ai écrit un exemple de projet blogpost avec Github qui aborde ce problème (et d'autres problèmes similaires) d'une manière légèrement différente de celle de Cyril.

http://brad-Android.blogspot.com/2013/08/Android-gradle-building-unique-build.html

3
icecreamman

Malheureusement, la version actuelle (0.4.1) du plugin Android ne semble pas être une bonne solution à cela. Je n'ai pas encore eu le temps de l'essayer, mais une solution de contournement possible pour ce problème serait d'utiliser une ressource de chaîne @string/provider_authority, et utilisez-le dans le manifeste: Android:authority="@string/provider_authority". Vous avez alors un res/values/provider.xml dans le dossier res de chaque type de construction qui doit remplacer l’autorité; dans votre cas, il s’agirait de src/debug/res

J'ai examiné la possibilité de générer le fichier XML à la volée, mais encore une fois, il ne semble pas y avoir de bon point d'ancrage pour celui-ci dans la version actuelle du plugin. Je recommanderais toutefois de faire une demande de fonctionnalité, je peux imaginer que plus de personnes se heurteront au même problème.

2

La réponse dans ce post fonctionne pour moi.

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

J'utilise 3 saveurs différentes, je crée donc 3 manifestes avec le fournisseur de contenu dans chaque saveur, comme le dit kevinrschultz:

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

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

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

Votre principal manifeste n'inclut pas les fournisseurs:

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

Et votre manifeste dans votre chaque saveur, y compris le fournisseur.

Libre:

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

Payé:

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

Autre:

<?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>
2
jcmore2

Ma solution consiste à utiliser un espace réservé de remplacement dans AndroidManifest.xml. Il gère également les attributs packageNameSuffix afin que vous puissiez avoir debug et release ainsi que toute autre génération personnalisée sur le même périphérique.

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

Je l'ai sur un Gist aussi si vous voulez voir si cela évolue plus tard.

J'ai trouvé une approche plus élégante que les multiples ressources et les méthodes d'analyse XML.

0
Saad Farooq

Pourquoi ne pas simplement ajouter ceci?

type.packageNameSuffix = ". $ type.name"

0
guydemossyrock