Utilizzo dei tipi di build in Gradle per eseguire la stessa app che utilizza ContentProvider su un dispositivo


124

Ho impostato Gradle per aggiungere il suffisso del nome del pacchetto alla mia app di debug in modo da poter avere la versione di rilascio che sto utilizzando e la versione di debug su un telefono. Mi riferivo a questo: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

Il mio file build.gradle ha questo aspetto:

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

Tutto funziona bene fino a quando non inizio a utilizzare un ContentProvider nella mia app. Ottengo:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

Capisco che ciò accade perché due app (rilascio e debug) stanno registrando la stessa autorità di ContentProvider.

Vedo una possibilità per risolvere questo problema. Se ho capito bene, dovresti essere in grado di specificare diversi file da utilizzare durante la creazione. Quindi dovrei essere in grado di mettere diverse autorità in diversi file di risorse (e dall'autorità del set Manifest come risorsa stringa) e dire a Gradle di utilizzare una risorsa diversa per la compilazione del debug. È possibile? Se sì, qualsiasi suggerimento su come ottenerlo sarebbe fantastico!

O forse è possibile modificare direttamente Manifest usando Gradle? Qualsiasi altra soluzione su come eseguire la stessa app con ContentProvider su un dispositivo è sempre la benvenuta.


Per coloro interessati a monitorare il supporto a monte per questo caso d'uso: segnalazione di bug AOSP . L'attuale posizione "ufficiale" è quella di utilizzare la soluzione di sostituzione manifest .
desseim

Risposte:


226

Nessuna delle risposte esistenti mi soddisfaceva, tuttavia Liberty era vicino. Quindi è così che lo sto facendo. Prima di tutto al momento sto lavorando con:

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

Il mio obiettivo è eseguire la Debugversione insieme alla Releaseversione sullo stesso dispositivo utilizzando lo stesso ContentProvider.


In build.gradle del suffisso del set di app per la build di debug:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

Nel file AndroidManifest.xml imposta la android:authoritiesproprietà del tuo ContentProvider:

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

Nella proprietà del set di codiciAUTHORITY che può essere utilizzata dove necessario nella tua implementazione:

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

Mancia: prima che lo fosseBuildConfig.PACKAGE_NAME

Questo è tutto! Funzionerà come un incantesimo. Continua a leggere se usi SyncAdapter!


Aggiornamento per SyncAdapter (14.11.2014)

Ancora una volta inizierò con la mia configurazione attuale:

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

Fondamentalmente, se hai bisogno di personalizzare alcuni valori per build diverse puoi farlo dal file build.gradle:

  • usa buildConfigField per accedervi dalla BuildConfig.javaclasse
  • usa resValue per accedervi dalle risorse, ad esempio @ string / your_value

In alternativa alle risorse, puoi creare directory buildType o flavour separate e sovrascrivere XML o valori al loro interno. Tuttavia, non lo userò nell'esempio seguente.

Esempio


Nel file build.gradle aggiungi quanto segue:

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

Vedrai i risultati nella classe BuildConfig.java

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

e in 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>

Nel tuo authenticator.xml usa la risorsa specificata nel file 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"
/>

Nella tua syncadapter.xml utilizzare la stessa risorsa ancora e @ stringa / autorità troppo

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

Suggerimento: il completamento automatico (Ctrl + Spazio) non funziona per queste risorse generate, quindi devi digitarle manualmente


7
La migliore risposta IMHO. Bel esempio breve e semplice.
rekire

Sì, questa è la migliore soluzione alternativa che ho visto finora. Grazie mille per la condivisione! Ho ancora un altro problema non correlato a questo poiché devo aggiornare un Intent esplicito in un file Preferences.xml per utilizzare il nuovo nome del pacchetto. code.google.com/p/android/issues/detail?id=57460
Bernd S

@ BerndS Ho pubblicato un commento al tuo problema con la soluzione. È necessario comprendere che la modifica di applicationId sostituendolo o impostando il suffisso non influisce sui pacchetti java. È solo un identificatore della tua app ed è disaccoppiato dai pacchetti java. Vedere la mia risposta a un'altra domanda stackoverflow.com/questions/24178007/...
Damian Petla

1
@JJD Le modifiche a cui ti colleghi funzionerebbero senza alcuno script di build personalizzato. Se desideri utilizzare i segnaposto $ {applicationId} per sync_adapter.xml, authenticator.xml devi personalizzare il tuo script build.gradle. Vedo che hai già fatto molto nel tuo script build.gradle, quindi ti senti a tuo agio con l'idea. Hai seguito le istruzioni nella mia risposta e ancora non ha funzionato?
Rob Meeuwisse

1
Ho aggiornato la mia risposta con how-to per syncadapter
Damian Petla

39

Nuovo suggerimento per il sistema di build Android: ridenominazione dell'autorità di ContentProvider

Immagino che tutti voi abbiate sentito parlare del nuovo sistema di build basato su Android Gradle. Siamo onesti, questo nuovo sistema di build è un enorme passo avanti rispetto al precedente. Non è ancora definitivo (al momento della stesura di questo articolo, l'ultima versione è la 0.4.2) ma puoi già usarla in sicurezza nella maggior parte dei tuoi progetti.

Personalmente ho passato la maggior parte del mio progetto a questo nuovo sistema di build e ho avuto alcuni problemi a causa della mancanza di supporto in alcune situazioni particolari. Uno dei quali è il supporto per la ridenominazione dell'autorità di ContentProvider

Il nuovo sistema basato su Android ti consente di gestire diversi tipi di app semplicemente modificando il nome del pacchetto in fase di compilazione. Uno dei principali vantaggi di questo miglioramento è che ora puoi avere due diverse versioni della tua app installate contemporaneamente sullo stesso dispositivo. Per esempio:

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

Utilizzando una tale configurazione Gradle, puoi assemblare due diversi APK:

• Un APK di debug con il nome del pacchetto com.cyrilmottier.android.app.debug • Un APK di rilascio con il nome del pacchetto com.cyrilmottier.android.app

L'unico problema è che non sarai in grado di installare i due APK contemporaneamente se entrambi espongono un ContentProvider con le stesse autorità. Logicamente dobbiamo rinominare l'autorità in base al tipo di build corrente ... ma questo non è supportato dal sistema di build Gradle (ancora? ... sono sicuro che verrà risolto presto). Quindi ecco una strada da percorrere:

Innanzitutto è necessario spostare la dichiarazione ContentProvider del manifesto Android del provider nel tipo di build appropriato. Per farlo dovremo semplicemente:

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>

Assicurati di rimuovere la dichiarazione ContentProvider da AndroidManifest.xml in src / main / perché Gradle non sa come unire ContentProvider con lo stesso nome ma un'autorità diversa.

Infine potremmo aver bisogno di accedere all'autorità nel codice. Questo può essere fatto abbastanza facilmente usando il file BuildConfig e il metodo 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
       }
   }
}

Grazie a questa soluzione alternativa sarai in grado di utilizzare BuildConfig.PROVIDER_AUTHORITY nel tuo ProviderContract e installare due diverse versioni della tua app contemporaneamente.


Originale su Google+: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ


1
Per qualcuno che non può eseguire gradle, a causa di un errore sintattico. Ecco la risposta: stackoverflow.com/questions/20678118/…
Renan Franca

23

Mentre l'esempio di Cyril funziona alla grande se hai solo pochi tipi di build, diventa rapidamente complicato se hai molti tipi di build e / o versioni di prodotto poiché devi mantenere molti AndroidManifest.xml diversi.

Il nostro progetto consiste in 3 diversi tipi di build e 6 versioni per un totale di 18 varianti di build, quindi abbiamo aggiunto il supporto per ".res-auto" nelle autorità di ContentProvider, che si espandono all'attuale packagename e rimuovono la necessità di mantenere diversi 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)
    }
}

Il codice di esempio può essere trovato qui: https://gist.github.com/cmelchior/6988275


Sono passato a usare qualcosa di molto simile anche per il mio progetto, perché avevo lo stesso problema con le versioni di build. Questo approccio funziona molto bene per ora.
MantasV

2
FileWriter crea problemi con i file utf-8, almeno sul mio Mac OS. Ho cambiato la riga correlata in: def writer = new OutputStreamWriter (new FileOutputStream (pathToFile), "UTF-8")
Reza Mohammadi

Questo è davvero fantastico, grazie! Ho apportato una piccola modifica per evitare rotture con stringhe formattate. gist.github.com/paour/8475929
Pierre-Luc Paour

Questo è stato molto utile, ma ho riscontrato un problema in cui non si creava dopo una pulizia perché non c'era il file values.xml nella cartella di build nella fase processManifest. Questo non esiste fino alla fase processResources, a quel punto è troppo tardi per modificare il manifest, quindi per sostituire .res-auto sia nel file manifest che in quello dei valori, penso che avresti bisogno di 2 funzioni, una chiamata per variante. processManifest.doLast, l'altro chiamato da variant.processResources.doLast.
Niall

20

Poiché la versione del plugin 0.8.3 (in realtà 0.8.1 ma non funzionava correttamente) è possibile definire le risorse all'interno del file di build in modo che questa possa essere una soluzione più pulita perché non è necessario creare file di stringhe né debug / rilascio aggiuntivi cartelle.

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
Attenzione, le autorità basate sulle risorse funzionano solo su Android 2.2.1 e
versioni

Grazie per il chiarimento.
rciovati

1
questo è anche molto utile nel searchable.xml per Android: searchSuggestAuthority, perché lì non puoi usare $ {applicationId}
user114676

13

Non so se qualcuno ne parla. In realtà dopo il plugin gradle Android 0.10+, la fusione manifest fornirà il supporto ufficiale per questa funzione: http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger

In AndroidManifest.xml, puoi usare $ {packageName} in questo modo:

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

E nel tuo build.gradle puoi avere:

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

Guarda l'esempio completo qui: https://code.google.com/p/anymemo/source/browse/AndroidManifest.xml#152

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


Questa è un'ottima notizia, ma non sembra che sia una soluzione completa nel caso di elementi <searchable> che devono fare riferimento all'autorità, poiché questi non fanno parte del manifest (ma le strategie di unione esistenti funzionano per questi file, a differenza del manifest).
Pierre-Luc Paour

1
Non devi usare i gusti per questo, funziona anche con i tipi di build. Inoltre, sarebbe bello menzionare che puoi usare BuildConfig.PACKAGE_NAME per ottenere un riferimento statico al tuo pacchetto. Ciò è utile per i fornitori di contenuti in cui l'autorità deve essere nota in fase di esecuzione per interrogare il fornitore di contenuti.
Matt Wolfe,

1
Dovrebbe anche essere aggiornato per utilizzare $ {applicationId} invece di $ {packageName} per Android: autorità
Bernd S

8

Usa ${applicationId}segnaposto in xml e BuildConfig.APPLICATION_IDnel codice.

Sarà necessario estendere lo script di compilazione per abilitare i segnaposto nei file xml diversi dal manifest. È possibile utilizzare una directory di origine per variante di build per fornire versioni diverse dei file xml, ma la manutenzione diventerà ingombrante molto rapidamente.

AndroidManifest.xml

È possibile utilizzare il segnaposto applicationId fuori dagli schemi nel manifest. Dichiara il tuo provider in questo modo:

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

Nota il ${applicationId}bit. Questo viene sostituito in fase di compilazione con l'effettivo applicationId per la variante di build in fase di compilazione.

In codice

Il tuo ContentProvider deve costruire la stringa di autorità nel codice. Può utilizzare la classe BuildConfig.

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

Nota il BuildConfig.APPLICATION_IDbit. È una classe generata con l'effettivo applicationId per la variante di build in costruzione.

res / xml / files, ad esempio syncadapter.xml, accountauthenticator.xml

Se si desidera utilizzare un adattatore di sincronizzazione, sarà necessario fornire metadati per ContentProvider e AccountManager nei file xml nella directory res / xml /. Il segnaposto applicationId non è supportato qui. Ma puoi estendere lo script di build da solo per hackerarlo.

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

Di nuovo, nota il file ${applicationId}. Funziona solo se aggiungi lo script gradle sottostante alla radice del tuo modulo e lo applichi da build.gradle.

build.gradle

Applicare lo script di build aggiuntivo dallo script build.gradle del modulo. Un buon posto è sotto il plugin gradle per Android.

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

android {
    compileSdkVersion 21
    // etc.

accumulo processApplicationId.gradle

Di seguito è riportato l'origine di lavoro per uno script di compilazione res / xml / segnaposto. Una versione meglio documentata è disponibile su GitHub . Sono ben accetti miglioramenti ed estensioni.

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

A mio parere non è necessario aggiungere il supporto segnaposto per le stringhe di risorse. Almeno per il caso d'uso di cui sopra non è necessario. Tuttavia potresti facilmente cambiare lo script non solo per sostituire i segnaposto nella directory res / xml /, ma anche nella directory res / values ​​/.


6

Preferirei piuttosto un misto tra Cirillo e rciovati. Penso che sia più semplice, hai solo due modifiche.

Gli build.gradlesembra:

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

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

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

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>

Codice:

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

4

Sulla base dell'esempio di @ChristianMelchior, ecco la mia soluzione, che risolve due problemi nelle soluzioni precedenti:

  • le soluzioni che modificano values.xml nella directory build causano una ricostruzione completa delle risorse (incluso l'adattamento di tutti i drawables)

  • per un motivo sconosciuto, IntelliJ (e probabilmente Android Studio) non elaborano in modo affidabile le risorse, facendo sì che la build contenga .res-autoautorità del provider non sostituite

Questa nuova soluzione fa le cose più nel modo Gradle creando una nuova attività e consente build incrementali definendo file di input e output.

  1. creare un file (nell'esempio lo metto in una variantsdirectory), formattato come un file xml di risorse, che contiene risorse di stringa. Questi verranno uniti alle risorse dell'app e qualsiasi occorrenza di .res-autonei valori verrà sostituita con il nome del pacchetto della variante, ad esempio<string name="search_provider">.res-auto.MySearchProvider</string>

  2. aggiungi il build_extras.gradlefile da questo gist al tuo progetto e fai riferimento ad esso dal main build.gradleaggiungendo da apply from: './build_extras.gradle'qualche parte sopra il androidblocco

  3. assicurati di impostare un nome di pacchetto predefinito aggiungendolo al android.defaultConfigblocco dibuild.gradle

  4. in AndroidManifest.xmle altri file di configurazione (come xml/searchable.xmlper i provider di ricerca di completamento automatico), fare riferimento al provider (ad esempio @string/search_provider)

  5. se è necessario ottenere lo stesso nome, è possibile utilizzare la BuildConfig.PACKAGE_NAMEvariabile, ad esempioBuildConfig.PACKAGE_NAME + ".MySearchProvider"

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


Aggiornamento: questo metodo funziona solo su Android 2.2.1 e versioni successive. Per le piattaforme precedenti, vedi questa risposta , che ha una sua serie di problemi, poiché la nuova fusione manifest è ancora molto approssimativa ...


Dove metti la tua directory delle varianti? Ho un grande progetto Android Studio che dipende da diversi moduli Android: la mia app principale e diversi moduli della libreria Android. Posso compilare dalla riga di comando, ma quando provo a creare dall'interno di Android Studio cerca variants/res-auto-values.xmlrelativo a /Applications/Android Studio.app/bin/. cioè non ricevo FileNotFoundException per /Applications/Android Studio.app/bin/variants/res-auto-values.xml. Sto correndo su un Mac. Questa è un'ottima soluzione, ma mi piacerebbe che funzionasse nell'IDE per gli altri membri del team.
user1978019

1
Risolto il mio problema. Gradle sembra risolvere i percorsi utilizzando System.getProperty("user.dir"), che restituisce un risultato diverso quando viene richiamato dalla build di Android Studio. La soluzione è utilizzare il percorso relativo alla directory del progetto, che viene restituito con gradle.startParameter.getProjectDir(). Vedi anche il mio commento nella sintesi collegata di Paour.
user1978019

Attenzione, le autorità basate sulle risorse funzionano solo su Android 2.2.1 e
versioni


2

Sfortunatamente, la versione corrente (0.4.1) del plugin Android non sembra fornire una buona soluzione per questo. Non ho avuto il tempo di provare questo ancora, ma una possibile soluzione per questo problema sarebbe quella di utilizzare una risorsa di stringa @string/provider_authority, e l'uso che nel manifesto: android:authority="@string/provider_authority". Hai quindi un res/values/provider.xmlnella cartella res di ogni tipo di build che dovrebbe sovrascrivere l'autorità, nel tuo caso sarebbesrc/debug/res

Ho cercato di generare il file xml al volo, ma ancora una volta, non sembra esserci alcun buon hook per esso nella versione corrente del plugin. Consiglierei di inserire una richiesta di funzionalità, posso immaginare che più persone si imbatteranno in questo stesso problema.


Ciao Marcus, grazie per la tua risposta. La tua soluzione suggerita è l'unica che riesco a pensare a me stesso per ora. Ma il mio problema è che non so come ottenerlo con Gradle.
MantasV

2

La risposta in questo post funziona per me.

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

Uso 3 gusti diversi, quindi creo 3 manifest con il fornitore di contenuti in ogni gusto come ha detto kevinrschultz:

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

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

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

Il tuo Manifest principale non include provider:

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

E il tuo manifesto in ogni tuo gusto, incluso il fornitore.

Gratuito:

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

Pagato:

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

Altro:

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

Perché non aggiungere semplicemente questo?

type.packageNameSuffix = ". $ type.name"


0

La mia soluzione è utilizzare la sostituzione del segnaposto in AndroidManifest.xml. Gestisce anche gli packageNameSuffixattributi in modo che tu possa avere debuge releasecosì come qualsiasi altra build personalizzata sullo stesso dispositivo.

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

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

Ce l'ho gistanch'io se vuoi vedere se si evolve in seguito.

Ho trovato un approccio più elegante rispetto alle risorse multiple e agli approcci di analisi XML.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.