Verwenden von Build-Typen in Gradle, um dieselbe App auszuführen, die ContentProvider auf einem Gerät verwendet

124

Ich habe Gradle so eingerichtet, dass meiner Debug-App das Paketnamensuffix hinzugefügt wird, damit ich die von mir verwendete Release-Version und die Debug-Version auf einem Telefon haben kann. Ich habe darauf verwiesen: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

Meine build.gradle-Datei sieht folgendermaßen aus:

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

Alles funktioniert einwandfrei, bis ich einen ContentProvider in meiner App verwende. Ich bekomme:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

Ich verstehe, dass dies geschieht, weil zwei Apps (Release und Debug) dieselbe ContentProvider-Berechtigung registrieren.

Ich sehe eine Möglichkeit, dies zu lösen. Wenn ich das richtig verstehe, sollten Sie in der Lage sein, verschiedene Dateien anzugeben, die beim Erstellen verwendet werden sollen. Dann sollte ich in der Lage sein, verschiedene Berechtigungen in verschiedene Ressourcendateien einzufügen (und von Manifest die Berechtigung als Zeichenfolgenressource festzulegen) und Gradle anzuweisen, unterschiedliche Ressourcen für die Debug-Erstellung zu verwenden. Ist das möglich? Wenn ja, dann wären alle Hinweise, wie man das erreicht, großartig!

Oder ist es möglich, Manifest mit Gradle direkt zu ändern? Jede andere Lösung zum Ausführen derselben App mit ContentProvider auf einem Gerät ist immer willkommen.

MantasV
quelle
Für diejenigen, die die Upstream-Unterstützung für diesen Anwendungsfall verfolgen möchten : AOSP-Fehlerbericht . Die "offizielle" derzeitige Haltung besteht darin, die offensichtliche übergeordnete Lösung zu verwenden .
Desseim

Antworten:

226

Keine der vorhandenen Antworten befriedigte mich, aber Liberty war nah dran. So mache ich das also. Zunächst arbeite ich im Moment mit:

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

Mein Ziel ist es, die DebugVersion zusammen mit der ReleaseVersion auf demselben Gerät mit demselben auszuführen ContentProvider.


In build.gradle Ihres App-Set-Suffixes für Debug-Build:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

In AndroidManifest.xml Dateiset android:authoritiesEigenschaft Ihres ContentProvider:

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

In Ihrer Code- Set- AUTHORITYEigenschaft, die überall dort verwendet werden kann, wo sie in Ihrer Implementierung benötigt wird:

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

Tipp: VorherBuildConfig.PACKAGE_NAME

Das ist es! Es wird wie ein Zauber wirken. Lesen Sie weiter, wenn Sie SyncAdapter verwenden!


Update für SyncAdapter (14.11.2014)

Ich werde noch einmal mit meinem aktuellen Setup beginnen:

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

Wenn Sie einige Werte für verschiedene Builds anpassen müssen, können Sie dies grundsätzlich über die Datei build.gradle tun:

  • Verwenden Sie buildConfigField , um von der BuildConfig.javaKlasse aus darauf zuzugreifen
  • Verwenden Sie resValue , um von Ressourcen aus darauf zuzugreifen, z. B. @ string / your_value

Als Alternative für Ressourcen können Sie separate BuildType- oder Flavour-Verzeichnisse erstellen und darin enthaltene XMLs oder Werte überschreiben. Ich werde es jedoch im folgenden Beispiel nicht verwenden.

Beispiel


In build.gradle Datei fügen Sie die folgende:

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

Sie sehen Ergebnisse in der BuildConfig.java- Klasse

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

und in build / generate / res / generate / debug / values ​​/ generate.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>

In Ihrer authenticator.xml Ressourcennutzung angegeben in build.gradle Datei

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

In Ihrem syncadapter.xml die gleiche Ressource wieder verwenden und @ Zeichenfolge / Behörden zu

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

Tipp: Die automatische Vervollständigung (Strg + Leertaste) funktioniert für diese generierten Ressourcen nicht. Sie müssen sie daher manuell eingeben

Damian Petla
quelle
7
Die beste Antwort IMHO. Schönes kurzes und einfaches Beispiel.
rekire
Ja, das ist die beste Problemumgehung, die ich bisher gesehen habe. Vielen Dank für das Teilen! Ich habe noch ein anderes Problem, das nichts damit zu tun hat, da ich eine explizite Absicht in einer Datei "settings.xml" aktualisieren muss, um den neuen Paketnamen zu verwenden. code.google.com/p/android/issues/detail?id=57460
Bernd S
@BerndS Ich habe einen Kommentar zu Ihrem Problem mit der Lösung gepostet. Sie müssen verstehen, dass das Ändern der applicationId durch Ersetzen oder Festlegen des Suffix keine Auswirkungen auf Java-Pakete hat. Es ist nur eine Kennung Ihrer App und von den Java-Paketen entkoppelt. Siehe meine Antwort auf eine andere Frage stackoverflow.com/questions/24178007/…
Damian Petla
1
@JJD Die Änderungen, auf die Sie verlinken, würden ohne ein benutzerdefiniertes Build-Skript funktionieren. Wenn Sie $ {applicationId} Platzhalter für sync_adapter.xml, authentulator.xml verwenden möchten, müssen Sie Ihr build.gradle-Skript anpassen. Ich sehe, dass Sie in Ihrem build.gradle-Skript bereits viel getan haben, damit Sie mit der Idee vertraut sind. Haben Sie die Anweisungen in meiner Antwort befolgt und hat es immer noch nicht funktioniert?
Rob Meeuwisse
1
Ich habe meine Antwort mit Anleitungen für Syncadapter
Damian Petla
39

Neuer Tipp für das Android-Build-System: Umbenennen der ContentProvider-Berechtigung

Ich denke, Sie alle haben von dem neuen Android Gradle-basierten Build-System gehört. Seien wir ehrlich, dieses neue Build-System ist im Vergleich zum vorherigen ein großer Fortschritt. Es ist noch nicht endgültig (zum jetzigen Zeitpunkt ist die neueste Version 0.4.2), aber Sie können es bereits in den meisten Ihrer Projekte sicher verwenden.

Ich habe den größten Teil meines Projekts persönlich auf dieses neue Build-System umgestellt und hatte einige Probleme, da in bestimmten Situationen keine Unterstützung vorhanden war. Eine davon ist die Unterstützung für das Umbenennen von ContentProvider-Berechtigungen

Mit dem neuen Android-System können Sie mit verschiedenen Arten Ihrer App umgehen, indem Sie einfach den Paketnamen zum Zeitpunkt der Erstellung ändern. Einer der Hauptvorteile dieser Verbesserung besteht darin, dass Sie jetzt zwei verschiedene Versionen Ihrer App gleichzeitig auf demselben Gerät installieren können. Zum Beispiel:

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

Mit einer solchen Gradle-Konfiguration können Sie zwei verschiedene APKs zusammenstellen:

• Eine Debug-APK mit dem Paketnamen com.cyrilmottier.android.app.debug. • Eine Release-APK mit dem Paketnamen com.cyrilmottier.android.app

Das einzige Problem dabei ist, dass Sie die beiden APKs nicht gleichzeitig installieren können, wenn beide einen ContentProvider mit denselben Berechtigungen verfügbar machen. Ziemlich logisch müssen wir die Berechtigung abhängig vom aktuellen Build-Typ umbenennen ... aber dies wird vom Gradle-Build-System nicht unterstützt (noch? ... ich bin sicher, dass es bald behoben wird). Also hier ist ein Weg zu gehen:

Zuerst müssen wir die ContentProvider-Deklaration des Android-Manifests des Anbieters in den entsprechenden Build-Typ verschieben. Dazu haben wir einfach:

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>

Stellen Sie sicher, dass Sie die ContentProvider-Deklaration aus der AndroidManifest.xml in src / main / entfernen, da Gradle nicht weiß, wie ContentProvider mit demselben Namen, aber einer anderen Berechtigung zusammengeführt werden.

Schließlich müssen wir möglicherweise auf die Autorität im Code zugreifen. Dies kann ziemlich einfach mit der BuildConfig-Datei und der buildConfig-Methode durchgeführt werden:

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

Dank dieser Problemumgehung können Sie BuildConfig.PROVIDER_AUTHORITY in Ihrem ProviderContract verwenden und gleichzeitig zwei verschiedene Versionen Ihrer App installieren.


Ursprünglich auf Google+: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ

Cyril Mottier
quelle
1
Für jemanden, der Gradle nicht ausführen kann, aufgrund eines Sintaxiefehlers. Hier ist die Antwort: stackoverflow.com/questions/20678118/…
Renan Franca
23

Während Cyrils Beispiel hervorragend funktioniert, wenn Sie nur wenige Build-Typen haben, wird es schnell kompliziert, wenn Sie viele Build-Typen und / oder Produktvarianten haben, da Sie viele verschiedene AndroidManifest.xml-Dateien verwalten müssen.

Unser Projekt besteht aus 3 verschiedenen Build-Typen und 6 Varianten mit insgesamt 18 Build-Varianten. Stattdessen haben wir die Unterstützung für ".res-auto" in den ContentProvider-Berechtigungen hinzugefügt, die auf den aktuellen Paketnamen erweitert werden und die Pflege verschiedener AndroidManifest.xml überflüssig machen

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

Beispielcode finden Sie hier: https://gist.github.com/cmelchior/6988275

Christian Melchior
quelle
Ich habe auch für mein Projekt etwas sehr Ähnliches verwendet, weil ich das gleiche Problem mit Build-Aromen hatte. Dieser Ansatz funktioniert vorerst sehr gut.
MantasV
2
FileWriter macht Probleme mit utf-8-Dateien, zumindest unter meinem Mac OS. Ich habe die zugehörige Zeile geändert in: def writer = new OutputStreamWriter (neuer FileOutputStream (pathToFile), "UTF-8")
Reza Mohammadi
Das ist wirklich toll, danke! Ich habe eine kleine Änderung vorgenommen, um einen Bruch mit formatierten Zeichenfolgen zu verhindern. gist.github.com/paour/8475929
Pierre-Luc Paour
Dies war sehr hilfreich, aber ich stieß auf ein Problem, bei dem es nach einer Bereinigung nicht erstellt werden konnte, da sich in der Phase processManifest keine Datei values.xml im Erstellungsordner befand. Das gibt es erst in der Phase processResources. Zu diesem Zeitpunkt ist es zu spät, das Manifest zu ändern. Um also .res-auto sowohl in der Manifest- als auch in der Wertedatei zu ersetzen, benötigen Sie wahrscheinlich zwei Funktionen, eine davon wird nach Variante aufgerufen. processManifest.doLast, das andere, das von varian.processResources.doLast aufgerufen wird.
Niall
20

Seit der Plugin-Version 0.8.3 (eigentlich 0.8.1, aber es funktionierte nicht richtig) können Sie Ressourcen in der Build-Datei definieren, sodass dies eine sauberere Lösung sein kann, da Sie weder Zeichenfolgendateien noch zusätzliches Debug / Release erstellen müssen Ordner.

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
quelle
2
Achtung, ressourcenbasierte Behörden arbeiten nur mit Android 2.2.1 und höher: github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour
Danke für die Klarstellung.
Rciovati
1
Dies ist auch sehr nützlich in der searchable.xml für Android: searchSuggestAuthority, da Sie dort $ {applicationId}
user114676
13

Ich weiß nicht, ob es jemand erwähnt. Nach dem Android Gradle Plugin 0.10+ bietet der Manifest Merger die offizielle Unterstützung für diese Funktion: http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger

In AndroidManifest.xml können Sie $ {packageName} wie folgt verwenden:

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

Und in Ihrem build.gradle können Sie haben:

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

Das vollständige Beispiel finden Sie hier: https://code.google.com/p/anymemo/source/browse/AndroidManifest.xml#152

und hier: https://code.google.com/p/anymemo/source/browse/build.gradle#41

Freiheit
quelle
Dies sind großartige Neuigkeiten, aber es scheint keine vollständige Lösung für <searchable> -Elemente zu sein, die auf die Berechtigung verweisen müssen, da diese nicht Teil des Manifests sind (vorhandene Zusammenführungsstrategien funktionieren jedoch für diese Dateien). im Gegensatz zum Manifest).
Pierre-Luc Paour
1
Sie müssen dafür keine Aromen verwenden, es funktioniert auch mit Build-Typen. Es wäre auch schön zu erwähnen, dass Sie BuildConfig.PACKAGE_NAME verwenden können, um einen statischen Verweis auf Ihr Paket zu erhalten. Dies ist nützlich für Inhaltsanbieter, bei denen die Berechtigung zur Laufzeit bekannt sein muss, um den Inhaltsanbieter abzufragen.
Matt Wolfe
1
Sollte auch aktualisiert werden, um $ {applicationId} anstelle von $ {packageName} für Android zu verwenden: Behörden
Bernd S
8

Verwenden Sie ${applicationId}Platzhalter in XML und BuildConfig.APPLICATION_IDim Code.

Sie müssen das Build-Skript erweitern, um Platzhalter in anderen XML-Dateien als dem Manifest zu aktivieren. Sie können ein Quellverzeichnis pro Build-Variante verwenden, um verschiedene Versionen der XML-Dateien bereitzustellen, aber die Wartung wird sehr schnell umständlich.

AndroidManifest.xml

Sie können den Platzhalter applicationId standardmäßig im Manifest verwenden. Erklären Sie Ihren Provider folgendermaßen:

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

Beachten Sie das ${applicationId}Bit. Dies wird zur Erstellungszeit durch die tatsächliche applicationId für die Erstellungsvariante ersetzt, die erstellt wird.

In Code

Ihr ContentProvider muss die Berechtigungszeichenfolge im Code erstellen. Es kann die BuildConfig-Klasse verwenden.

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

Beachten Sie das BuildConfig.APPLICATION_IDBit. Es ist eine generierte Klasse mit der tatsächlichen applicationId für die Build-Variante, die erstellt wird.

res / xml / files, zB syncadapter.xml, accountauthenticator.xml

Wenn Sie einen Synchronisierungsadapter verwenden möchten, müssen Sie Metadaten für ContentProvider und AccountManager in XML-Dateien im Verzeichnis res / xml / bereitstellen. Der Platzhalter applicationId wird hier nicht unterstützt. Sie können das Build-Skript jedoch selbst erweitern, um es zu hacken.

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

Beachten Sie auch hier die ${applicationId}. Dies funktioniert nur, wenn Sie das folgende Gradle-Skript zum Stammverzeichnis Ihres Moduls hinzufügen und es über build.gradle anwenden.

build.gradle

Wenden Sie das zusätzliche Build-Skript aus dem Modul build.gradle an. Ein guter Platz ist unter dem Android Gradle Plugin.

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

android {
    compileSdkVersion 21
    // etc.

build-processApplicationId.gradle

Unten finden Sie eine Arbeitsquelle für ein Build-Skript für res / xml / placeholder. Eine besser dokumentierte Version ist auf github verfügbar . Verbesserungen und Erweiterungen sind willkommen.

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

Meiner Meinung nach muss keine Platzhalterunterstützung für Ressourcenzeichenfolgen hinzugefügt werden. Zumindest für den obigen Anwendungsfall wird es nicht benötigt. Sie können das Skript jedoch leicht ändern, um nicht nur Platzhalter im Verzeichnis res / xml /, sondern auch im Verzeichnis res / values ​​/ zu ersetzen.

Rob Meeuwisse
quelle
6

Ich würde lieber eine Mischung zwischen Cyril und Rciovati bevorzugen. Ich denke ist einfacher, Sie haben nur zwei Modifikationen.

Das build.gradlesieht aus wie:

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

Und die 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
quelle
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 ]
    }
}

authentator.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;
maros136
quelle
4

Basierend auf dem Beispiel von @ChristianMelchior ist hier meine Lösung, die zwei Probleme in den vorherigen Lösungen behebt:

  • Lösungen, die die Datei values.xml im Build-Verzeichnis ändern, führen zu einer vollständigen Neuerstellung der Ressourcen (einschließlich der Anpassung aller Drawables).

  • Aus einem unbekannten Grund verarbeiten IntelliJ (und wahrscheinlich Android Studio) die Ressourcen nicht zuverlässig, was dazu führt, dass der Build nicht ersetzte .res-autoAnbieterberechtigungen enthält

Diese neue Lösung erledigt die Aufgaben auf Gradle-Weise, indem sie eine neue Aufgabe erstellt, und ermöglicht inkrementelle Builds durch Definieren von Eingabe- und Ausgabedateien.

  1. Erstellen Sie eine Datei (in dem Beispiel, in dem ich sie in ein variantsVerzeichnis gestellt habe), die wie eine Ressourcen-XML-Datei formatiert ist und Zeichenfolgenressourcen enthält. Diese werden in die Ressourcen der App eingefügt, und jedes Auftreten .res-autoin den Werten wird beispielsweise durch den Paketnamen der Variante ersetzt<string name="search_provider">.res-auto.MySearchProvider</string>

  2. Fügen Sie die build_extras.gradleDatei aus diesem Kern zu Ihrem Projekt hinzu und referenzieren Sie sie von der Hauptdatei build.gradleaus, indem Sie sie apply from: './build_extras.gradle'irgendwo über dem androidBlock hinzufügen

  3. Stellen Sie sicher, dass Sie einen Standardpaketnamen festlegen, indem Sie ihn dem android.defaultConfigBlock von hinzufügenbuild.gradle

  4. Verweisen Sie in AndroidManifest.xmlund anderen Konfigurationsdateien (z. B. xml/searchable.xmlfür Anbieter mit automatischer Vervollständigung) auf den Anbieter (z. B. @string/search_provider).

  5. Wenn Sie denselben Namen benötigen, können Sie BuildConfig.PACKAGE_NAMEbeispielsweise die Variable verwendenBuildConfig.PACKAGE_NAME + ".MySearchProvider"

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


Update: Diese Methode funktioniert nur unter Android 2.2.1 und höher. Für frühere Plattformen siehe diese Antwort , die ihre eigenen Probleme hat, da die neue offensichtliche Fusion an den Rändern immer noch sehr rau ist…

Pierre-Luc Paour
quelle
Wo legen Sie Ihr Variantenverzeichnis ab? Ich habe ein großes Android Studio-Projekt, das von mehreren Android-Modulen abhängt - meine Haupt-App und mehrere Android Library-Module. Ich kann über die Befehlszeile erstellen, aber wenn ich versuche, in Android Studio zu erstellen, sucht es variants/res-auto-values.xmlrelativ zu /Applications/Android Studio.app/bin/. dh ich bekomme keine FileNotFoundException für /Applications/Android Studio.app/bin/variants/res-auto-values.xml. Ich laufe auf einem Mac. Dies ist eine großartige Lösung, aber ich würde sie gerne in der IDE für die anderen Mitglieder des Teams zum Laufen bringen.
user1978019
1
Mein eigenes Problem wurde behoben. Gradle scheint Pfade mithilfe von aufzulösen System.getProperty("user.dir"), was beim Aufrufen durch den Android Studio-Build ein anderes Ergebnis zurückgibt. Die Lösung besteht darin, den Pfad relativ zum Projektverzeichnis zu verwenden, mit dem zurückgegeben wird gradle.startParameter.getProjectDir(). Siehe meinen Kommentar auch in Paours verknüpftem Kern.
user1978019
Beachten Sie, dass ressourcenbasierte Behörden nur unter Android 2.2.1 und höher funktionieren: github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour
2

Leider scheint die aktuelle Version (0.4.1) des Android-Plugins keine gute Lösung dafür zu bieten. Ich hatte noch keine Zeit, dies zu versuchen, aber eine mögliche Problemumgehung für dieses Problem wäre die Verwendung einer Zeichenfolgenressource @string/provider_authorityund deren Verwendung im Manifest : android:authority="@string/provider_authority". Sie haben dann eineres/values/provider.xml im res-Ordner jedes Build-Typs einen, der die Berechtigung überschreiben soll. In Ihrem Fall wäre dies der Fallsrc/debug/res

Ich habe versucht, die XML-Datei im laufenden Betrieb zu generieren, aber auch in der aktuellen Version des Plugins scheint es keine guten Hooks dafür zu geben. Ich würde jedoch empfehlen, eine Funktionsanfrage einzureichen. Ich kann mir vorstellen, dass mehr Menschen auf dasselbe Problem stoßen werden.

Marcus Forsell Stahre
quelle
Hallo Marcus, danke für deine Antwort. Ihre vorgeschlagene Lösung ist die einzige, an die ich im Moment denken kann. Aber mein Problem ist, ich weiß nicht, wie ich das mit Gradle erreichen soll.
MantasV
2

Die Antwort in diesem Beitrag funktioniert für mich.

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

Ich benutze 3 verschiedene Geschmacksrichtungen, also erstelle ich 3 Manifest mit Inhaltsanbieter in jeder Geschmacksrichtung, wie Kevinrschultz sagte:

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

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

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

Ihr Hauptmanifest enthält keine Anbieter:

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

Und Ihr Manifest in jedem Geschmack einschließlich Anbieter.

Frei:

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

Bezahlt:

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

Andere:

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

Warum nicht einfach hinzufügen?

type.packageNameSuffix = ". $ type.name"

guydemossyrock
quelle
0

Meine Lösung besteht darin, Platzhalterersatz in zu verwenden AndroidManifest.xml. Es auch Griffe packageNameSuffixAttribute , so dass Sie haben können debugund releasesowie jede andere benutzerdefinierte auf demselben Gerät baut.

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

Ich habe es auf einem gist wenn Sie sehen wollen, ob es sich später entwickelt.

Ich fand, dass dies ein eleganterer Ansatz ist als die Ansätze für mehrere Ressourcen und XML-Analyse.

Saad Farooq
quelle