122 votes

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

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

Je vois une possibilité pour résoudre cela. Si je comprends correctement, 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 du Manifeste définir l'autorité comme ressource de chaîne) et dire à Gradle d'utiliser une ressource différente pour la construction de débogage. Est-ce possible ? Si oui, des conseils sur la façon d'y parvenir seraient géniaux !

Ou peut-être est-il possible de modifier directement le Manifeste en utilisant Gradle ? Toute autre solution pour exécuter la même application avec ContentProvider sur un seul appareil est toujours la bienvenue.

223voto

Loop Points 1682

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

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

Mon objectif est de lancer la version Debug ainsi que la version Release sur le même appareil en utilisant le même ContentProvider.


Dans le fichier build.gradle de votre application, définissez un suffixe pour la version Debug :

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

Dans le fichier AndroidManifest.xml, définissez la propriété android:authorities de votre ContentProvider :


Dans votre code, définissez la propriété AUTHORITY qui peut être utilisée partout où nécessaire dans votre implémentation :

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

Conseil : Avant c'était BuildConfig.PACKAGE_NAME

C'est tout! Cela fonctionnera à merveille. 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 Beta 0.9.2
  • Plugin Gradle 0.14.1
  • Gradle 2.1

Fondamentalement, 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 la classe BuildConfig.java
  • utilisez resValue pour y accéder depuis les ressources par exemple @string/votre_valeur

Comme alternative pour les ressources, vous pouvez créer des répertoires buildType ou flavor séparés et remplacer des XMLs ou des valeurs à l'intérieur d'eux. 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

    your.syncadapter.type.debug
    com.example.app.provider

Dans votre fichier authenticator.xml, utilisez la ressource spécifiée dans le fichier build.gradle


Dans votre fichier syncadapter.xml, utilisez à nouveau la même ressource et @string/authorities aussi

Conseil : L'autocomplétion (Ctrl+Espace) ne fonctionne pas pour ces ressources générées, vous devez donc les taper manuellement

39voto

Cyril Mottier Points 1037

Nouveau conseil sur le nouveau système de construction Android : Renommage de l'autorité ContentProvider

Je suppose que vous avez tous entendu parler du nouveau système de construction Gradle pour Android. Soyons honnêtes, ce nouveau système de construction est un énorme pas en avant par rapport à l'ancien. Il n'est pas encore finalisé (à l'heure où j'écris 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 mes projets vers ce nouveau système de construction et j'ai rencontré quelques problèmes en raison du manque de support dans certaines situations particulières. L'un de ces problèmes concerne le support du renommage de l'autorité ContentProvider

Le nouveau système de construction Android 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 maintenant 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 // Écoutez les conseils de +Jeff Gilfelt :)
       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. Logiquement, nous devons renommer l'autorité en fonction du type de construction actuel… mais ce n'est pas pris en charge par le système de construction Gradle (pour l'instant? ... Je suis sûr que cela sera bientôt corrigé). Voici donc une solution :

Tout d'abord, nous devons déplacer la déclaration ContentProvider du manifeste Android vers le type de construction approprié. Pour cela, nous aurons simplement :

src/debug/AndroidManifest.xml

src/release/AndroidManifest.xml

Assurez-vous de supprimer la déclaration ContentProvider du AndroidManifest.xml dans src/main/ car Gradle ne sait pas comment fusionner les 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 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

23voto

ChristianMelchior Points 494

Alors que l'exemple de Cyril fonctionne très bien si vous n'avez que quelques types de build, cela devient rapidement compliqué si vous avez de nombreux types de build et/ou des saveurs de produits car vous devez maintenir plusieurs 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é le support de ".res-auto" dans les autorités du ContentProvider, qui se développent en le nom de package actuel et éliminent le besoin de maintenir différents AndroidManifest.xml.

/**
 * Version 1.1.
 *
 * Ajouter le support pour installer plusieurs variantes de la même application qui ont un
 * fournisseur de contenu. Faites cela en remplaçant les occurrences de ".res-auto" dans
 * android:authorities par le nom du package actuel (qui devrait être unique)
 *
 * V1.0 : Version initiale
 * V1.1 : Support de ".res-auto" dans les chaînes ajouté,
 *        par exemple utiliser ".res-auto.chemin.vers.le.fournisseur"
 *
 */
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

    // Mettre à jour tous les fournisseurs de contenu
    xml.application.provider.each { provider ->
        def newAuthorities = provider.attribute(ns.authorities).replaceAll('.res-auto', variantPackageName)
        provider.attributes().put(ns.authorities, newAuthorities)
    }

    // Enregistrer le AndroidManifest modifié dans le répertoire de build
    saveXML(pathToManifest, xml)

    // S'assurer également que toutes les chaînes avec ".res-auto" sont étendues automatiquement
    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)
}

// Traitement ultérieur d'AndroidManifest.xml pour prendre en charge les autorités des fournisseurs
// à travers les variantes de build.
android.applicationVariants.all { variant ->
    variant.processManifest.doLast {
        overrideProviderAuthority(variant)
    }
}

Le code d'exemple peut être trouvé ici : https://gist.github.com/cmelchior/6988275

20voto

rciovati Points 5991

Depuis la version du plugin 0.8.3 (en réalité 0.8.1 mais cela ne fonctionnait pas correctement) vous pouvez définir des ressources dans le fichier de build, ce qui pourrait être une solution plus propre car vous n'avez pas besoin de créer des fichiers de chaînes ni de dossiers de débogage/version.

build.gradle

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

AndroidManifest.xml

13voto

Liberty Points 216

Je ne sais pas si quelqu'un l'a mentionné. En fait après le plugin Android Gradle 0.10 +, le fusions du manifeste offrira un support officiel pour cette fonctionnalité : http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger

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

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X