Utilisation de types de build dans Gradle pour exécuter la même application qui utilise ContentProvider sur un appareil


124

J'ai configuré Gradle pour ajouter le suffixe de nom de package à mon application de débogage afin que je puisse avoir la version de sortie que j'utilise et la version de débogage sur un 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 parce que deux applications (version et débogage) enregistrent la même autorité ContentProvider.

Je vois une possibilité de 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 être en mesure de mettre différentes autorités dans différents fichiers de ressources (et à partir de l'autorité de jeu Manifest en tant que ressource de chaîne) et dire à Gradle d'utiliser une ressource différente pour la construction de débogage. Est-ce possible? Si oui, tout indice sur la façon d'y parvenir serait génial!

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


Pour ceux qui s'intéressent au suivi du support en amont pour ce cas d'utilisation: rapport de bogue AOSP . La position actuelle «officielle» est d'utiliser la solution de remplacement du manifeste .
desseim

Réponses:


226

Aucune des réponses existantes ne m'a satisfait, cependant Liberty était proche. Alors voilà comment je fais. Tout d'abord en ce moment je travaille avec:

  • Android Studio bêta 0.8.2
  • Plug-in Gradle 0.12. +
  • Gradle 1.12

Mon objectif est d'exécuter la Debugversion avec la Releaseversion sur le même appareil en utilisant le même ContentProvider.


Dans build.gradle de votre suffixe de jeu d'application pour la build Debug:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

Dans le jeu de fichiers AndroidManifest.xml , android:authoritiespropriété de votre ContentProvider:

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

Dans votre propriété de jeu de codesAUTHORITY qui peut être utilisée partout où cela est nécessaire dans votre implémentation:

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

Conseil: avantBuildConfig.PACKAGE_NAME

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


Mise à jour pour SyncAdapter (14.11.2014)

Encore une fois, je vais commencer avec ma configuration actuelle:

  • Android Studio bêta 0.9.2
  • Plugin Gradle 0.14.1
  • Gradle 2.1

Fondamentalement, si vous devez personnaliser certaines valeurs pour différentes versions, vous pouvez le faire à partir du fichier build.gradle:

  • utilisez buildConfigField pour y accéder depuis la BuildConfig.javaclasse
  • utilisez resValue pour y accéder à partir de ressources, par exemple @ string / your_value

Comme alternative aux 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 la classe BuildConfig.java

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

et dans build / generated / res / generated / debug / values ​​/ generated.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 authentificateur.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é 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 saisie semi - automatique (Ctrl + Espace) ne fonctionne pas pour ces ressources générées, vous devez donc les saisir manuellement


7
La meilleure réponse à mon humble avis. Bel exemple court et simple.
rekire le

Oui, c'est la meilleure solution de contournement que j'ai vue jusqu'à présent. Merci beaucoup pour le partage! J'ai encore un autre problème sans rapport avec cela, car je dois mettre à jour une intention explicite dans un fichier preferences.xml pour utiliser le nouveau nom de package. code.google.com/p/android/issues/detail?id=57460
Bernd S

@BerndS J'ai publié un commentaire sur votre problème avec la solution. Vous devez comprendre que changer applicationId en le remplaçant ou en définissant le suffixe n'affecte pas les packages java. Il s'agit simplement d'un identifiant de votre application et il est découplé des packages java. Voir ma réponse à une autre question stackoverflow.com/questions/24178007/…
Damian Petla

1
@JJD Les modifications auxquelles vous créez un lien fonctionneraient sans aucun script de construction personnalisé. Si vous souhaitez utiliser des espaces réservés $ {applicationId} pour sync_adapter.xml, authentication.xml, vous devez personnaliser votre script build.gradle. Je vois que vous avez déjà beaucoup fait dans votre script build.gradle donc vous êtes à l'aise avec l'idée. Avez-vous suivi les instructions de ma réponse et cela ne fonctionnait toujours pas?
Rob Meeuwisse

1
J'ai mis à jour ma réponse avec des instructions pour le syncadapter
Damian Petla

39

Nouvelle astuce pour le système de build 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 basculé la plupart de mon projet vers ce nouveau système de construction et j'ai eu quelques problèmes à cause du manque de support dans certaines situations particulières. L'un d'eux est la prise en charge du changement de nom d'autorité ContentProvider

Le nouveau système Android intégré vous permet de gérer différents types de votre application en modifiant simplement le nom du package au moment de la construction. L'un des principaux avantages de cette amélioration est que vous pouvez désormais avoir deux versions différentes de votre application installées 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 APK différents:

• Un APK de débogage avec le nom de package com.cyrilmottier.android.app.debug • Un APK de version avec le nom de package com.cyrilmottier.android.app

Le seul problème avec cela est que vous ne pourrez pas installer les deux APK en même temps s'ils exposent tous les deux un ContentProvider avec les mêmes autorités. Assez logiquement, nous devons renommer l'autorité en fonction du type de build actuel ... mais cela n'est pas supporté par le système de build Gradle (encore? ... Je suis sûr que cela sera corrigé bientôt). Alors, voici une voie à suivre:

Nous devons d'abord déplacer la déclaration ContentProvider du manifeste Android du fournisseur 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 des ContentProviders ayant le même nom mais une autorité différente.

Enfin, nous pouvons avoir 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 ProviderContract et installer deux versions différentes de votre application en même temps.


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


1
Pour quelqu'un qui ne peut pas exécuter gradle, une erreur sintaxy bacause. Voici la réponse: stackoverflow.com/questions/20678118/…
Renan Franca

23

Bien que l'exemple de Cyril fonctionne très bien si vous n'avez que quelques types de build, cela se complique rapidement si vous avez de nombreux types de build et / ou saveurs de produits car vous devez gérer de nombreux AndroidManifest.xml différents.

Notre projet se compose de 3 types de build différents et de 6 saveurs totalisant 18 variantes de build, nous avons donc ajouté la prise en charge de ".res-auto" dans les autorités ContentProvider, qui s'étendent au nom du pack actuel et suppriment le besoin de maintenir différents 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)
    }
}

Un exemple de code peut être trouvé ici: https://gist.github.com/cmelchior/6988275


J'ai également utilisé quelque chose de très similaire pour mon projet, car j'avais le même problème avec les versions de build. Cette approche fonctionne très bien pour le moment.
MantasV

2
FileWriter pose des problèmes sur les fichiers utf-8, du moins sur mon Mac OS. J'ai changé la ligne associée en: def writer = new OutputStreamWriter (new FileOutputStream (pathToFile), "UTF-8")
Reza Mohammadi

C'est vraiment super, merci! J'ai fait une petite modification pour éviter la rupture avec les chaînes formatées. gist.github.com/paour/8475929
Pierre-Luc Paour

Cela a été très utile, mais j'ai rencontré un problème où il ne serait pas construit après un nettoyage car il n'y avait pas de fichier values.xml dans le dossier de construction à l'étape processManifest. Cela n'existe pas avant l'étape processResources, auquel point il est trop tard pour modifier le manifeste, donc pour remplacer .res-auto dans les fichiers manifest et values, je pense que vous auriez besoin de 2 fonctions, une appelée par variante. processManifest.doLast, l'autre appelé par variant.processResources.doLast.
Niall

20

Depuis la version 0.8.3 du plugin (en fait 0.8.1 mais cela ne fonctionnait pas correctement), vous pouvez définir des ressources dans le fichier de construction afin que cela puisse être une solution plus propre car vous n'avez pas besoin de créer de fichiers de chaînes ni de débogage / version 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>

2
Attention, les autorités basées sur les ressources ne fonctionnent que sur Android 2.2.1 et versions
Pierre-Luc Paour

Merci pour la clarification.
rciovati

1
ceci est également très utile dans le fichier searchable.xml pour android: searchSuggestAuthority, car là vous ne pouvez pas utiliser $ {applicationId}
user114676

13

Je ne sais pas si quelqu'un en a parlé. En fait, après le plugin android gradle 0.10+, la fusion du manifeste fournira le support officiel de 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


C'est une excellente nouvelle, mais il ne semble pas que ce soit une solution complète dans le cas des éléments <searchable> qui doivent référencer l'autorité, car ils ne font pas partie du manifeste (mais les stratégies de fusion existantes fonctionnent pour ces fichiers, contrairement au Manifest).
Pierre-Luc Paour

1
Vous n'êtes pas obligé d'utiliser des saveurs pour cela, cela fonctionne également avec les types de construction. De plus, il serait bon de mentionner que vous pouvez utiliser BuildConfig.PACKAGE_NAME pour obtenir une référence statique à votre package. Ceci est utile pour les fournisseurs de contenu pour lesquels l'autorité doit être connue au moment de l'exécution pour interroger le fournisseur de contenu.
Matt Wolfe du

1
Doit également être mis à jour pour utiliser $ {applicationId} au lieu de $ {packageName} pour Android: Authorities
Bernd S

8

Utilisez des ${applicationId}espaces réservés dans xml et BuildConfig.APPLICATION_IDdans le 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 vite 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" />

Notez le ${applicationId}bit. Celui-ci est remplacé au moment de la construction par l'ID d'application réel pour la variante de construction en cours de construction.

Dans du code

Votre ContentProvider doit construire 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";
    // ...
}

Notez le BuildConfig.APPLICATION_IDbit. Il s'agit d'une classe générée avec l'identifiant d'application réel 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 devrez 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 construction 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 gradle ci-dessous à la racine de votre module et que vous l'appliquez depuis build.gradle.

build.gradle

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

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

android {
    compileSdkVersion 21
    // etc.

build-processApplicationId.gradle

Vous trouverez ci-dessous la source de travail d'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 modifier 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 ​​/.


6

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

Le build.gradleressemble à:

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>

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

authentificateur.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;

4

Sur la base de l'exemple de @ChristianMelchior, voici ma solution, qui corrige deux problèmes dans les solutions précédentes:

  • les solutions qui modifient values.xml dans le répertoire de construction provoquent une reconstruction complète des ressources (y compris une adaptation de tous les drawables)

  • pour une raison inconnue, IntelliJ (et probablement Android Studio) ne traite pas les ressources de manière fiable, ce qui fait que la compilation contient des .res-autoautorités de fournisseur non remplacées

Cette nouvelle solution fait plus les choses à la manière Gradle en créant une nouvelle tâche et permet des constructions incrémentielles en définissant des fichiers d'entrée et de sortie.

  1. créer un fichier (dans l'exemple, je l'ai mis dans un variantsrépertoire), formaté comme un fichier xml de ressources, qui contient des ressources de chaîne. Ceux-ci seront fusionnés dans les ressources de l'application, et toute occurrence de .res-autodans les valeurs sera remplacée par le nom du package de la variante, par exemple<string name="search_provider">.res-auto.MySearchProvider</string>

  2. ajoutez le build_extras.gradlefichier de cet élément essentiel à votre projet et référencez-le à partir du fichier principal build.gradleen ajoutant apply from: './build_extras.gradle'quelque part au-dessus du androidbloc

  3. assurez-vous de définir un nom de package par défaut en l'ajoutant au android.defaultConfigbloc debuild.gradle

  4. dans AndroidManifest.xmlet d'autres fichiers de configuration (comme xml/searchable.xmlpour les fournisseurs de recherche de saisie semi-automatique), référencez le fournisseur (par exemple @string/search_provider)

  5. si vous avez besoin du même nom, vous pouvez utiliser la BuildConfig.PACKAGE_NAMEvariable, par exempleBuildConfig.PACKAGE_NAME + ".MySearchProvider"

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


Mise à jour: cette méthode ne fonctionne que sur Android 2.2.1 et versions ultérieures. Pour les plates-formes antérieures, voyez cette réponse , qui a son propre ensemble de problèmes, car la nouvelle fusion manifeste est encore très difficile sur les bords ...


Où mettez-vous votre répertoire de variantes? J'ai un grand projet Android Studio qui dépend de plusieurs modules Android - mon application principale et plusieurs modules de bibliothèque Android. Je peux créer à partir de la ligne de commande, mais lorsque j'essaie de créer à partir d'Android Studio, cela recherche par variants/res-auto-values.xmlrapport à /Applications/Android Studio.app/bin/. c'est-à-dire que je n'obtiens aucune FileNotFoundException pour /Applications/Android Studio.app/bin/variants/res-auto-values.xml. Je cours sur un mac. C'est une excellente solution, mais j'aimerais qu'elle fonctionne dans l'EDI pour les autres membres de l'équipe.
user1978019

1
Correction de mon propre problème. Gradle semble résoudre les chemins en utilisant System.getProperty("user.dir"), qui renvoie un résultat différent lorsqu'il est appelé par la version Android Studio. La solution consiste à utiliser le chemin relatif au répertoire du projet, qui est retourné avec gradle.startParameter.getProjectDir(). Voir également mon commentaire dans l'essentiel lié de Paour.
user1978019

Attention, les autorités basées sur les ressources ne fonctionnent que sur Android 2.2.1 et versions
Pierre-Luc Paour


2

Malheureusement, la version actuelle (0.4.1) du plugin Android ne semble pas fournir une bonne solution pour cela. Je ne l' ai pas eu le temps d'essayer encore, mais une solution possible à ce problème serait d'utiliser une ressource de chaîne @string/provider_authority, et l' utilisation que dans le manifeste: android:authority="@string/provider_authority". Vous avez alors un res/values/provider.xmldans le dossier res de chaque type de construction qui devrait remplacer l'autorité, dans votre cas, ce seraitsrc/debug/res

J'ai cherché à générer le fichier xml à la volée, mais encore une fois, il ne semble pas y avoir de bons hooks pour cela dans la version actuelle du plugin. Je recommanderais cependant de faire une demande de fonctionnalité, je peux imaginer que plus de personnes rencontreront ce même problème.


Salut Marcus, merci pour votre réponse. Votre solution suggérée est la seule à laquelle je puisse penser pour le moment. Mais mon problème est que je ne sais pas comment y parvenir avec Gradle.
MantasV

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 un fournisseur de contenu dans chaque saveur, comme l'a dit kevinrschultz:

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

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

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

Votre manifeste principal 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 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>

0

Pourquoi ne pas simplement ajouter ceci?

type.packageNameSuffix = ". $ type.name"


0

Ma solution consiste à utiliser le remplacement d'espace réservé dans AndroidManifest.xml. Il gère également les packageNameSuffixattributs afin que vous puissiez avoir debuget releaseainsi que toute autre version personnalisée sur le même appareil.

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 approches d'analyse XML.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.