Usar tipos de compilación en Gradle para ejecutar la misma aplicación que usa ContentProvider en un dispositivo

124

Configuré Gradle para agregar el sufijo del nombre del paquete a mi aplicación de depuración para poder lanzar la versión que estoy usando y la versión de depuración en un teléfono. Estaba haciendo referencia a esto: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

Mi archivo build.gradle se ve así:

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

Todo funciona bien hasta que empiezo a usar un ContentProvider en mi aplicación. Yo obtengo:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

Entiendo que esto sucede porque dos aplicaciones (lanzamiento y depuración) están registrando la misma autoridad de ContentProvider.

Veo una posibilidad para resolver esto. Si entiendo correctamente, deberías poder especificar diferentes archivos para usar cuando construyas. Entonces debería poder poner diferentes autoridades en diferentes archivos de recursos (y desde Manifest establecer la autoridad como recurso de cadena) y decirle a Gradle que use diferentes recursos para la compilación de depuración. ¿Es eso posible? En caso afirmativo, ¡cualquier sugerencia sobre cómo lograrlo sería increíble!

¿O tal vez es posible modificar directamente el Manifiesto usando Gradle? Cualquier otra solución sobre cómo ejecutar la misma aplicación con ContentProvider en un dispositivo siempre es bienvenida.

MantasV
fuente
Para aquellos interesados ​​en rastrear el soporte ascendente para este caso de uso: informe de error de AOSP . La postura actual "oficial" es utilizar la solución de anulación de manifiesto .
desseim

Respuestas:

226

Ninguna de las respuestas existentes me satisfizo, sin embargo, Liberty estaba cerca. Así es como lo estoy haciendo. En primer lugar, en este momento estoy trabajando con:

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

Mi objetivo es ejecutar la Debugversión junto con la Releaseversión en el mismo dispositivo usando el mismo ContentProvider.


En build.gradle del sufijo del conjunto de aplicaciones para la compilación de depuración:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

En la propiedad de conjunto de archivos AndroidManifest.xmlandroid:authorities de su ContentProvider:

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

En su propiedad de conjunto de códigosAUTHORITY que se puede usar donde sea necesario en su implementación:

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

Consejo: Antes eraBuildConfig.PACKAGE_NAME

¡Eso es! Funcionará como un encanto. ¡Sigue leyendo si usas SyncAdapter!


Actualización para SyncAdapter (14.11.2014)

Una vez más, comenzaré con mi configuración actual:

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

Básicamente, si necesita personalizar algunos valores para diferentes compilaciones, puede hacerlo desde el archivo build.gradle:

  • use buildConfigField para acceder desde la BuildConfig.javaclase
  • use resValue para acceder a él desde recursos, por ejemplo, @ string / your_value

Como alternativa para los recursos, puede crear directorios buildType o de sabor separados y anular XML o valores dentro de ellos. Sin embargo, no lo voy a usar en el siguiente ejemplo.

Ejemplo


En el archivo build.gradle agregue lo siguiente:

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

Verá resultados en la clase BuildConfig.java

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

y en 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>

En su authenticator.xml use el recurso especificado en el archivo 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"
/>

En su syncadapter.xml use el mismo recurso nuevamente y @ string / autoridades también

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

Consejo: el autocompletado (Ctrl + Espacio) no funciona para estos recursos generados, por lo que debe escribirlos manualmente

Damian Petla
fuente
77
La mejor respuesta en mi humilde opinión. Buen ejemplo corto y simple.
rekire
Sí, esa es la mejor solución que he visto hasta ahora. ¡Muchas gracias por compartir! Todavía tengo otro problema no relacionado con esto, ya que necesito actualizar una intención explícita en un archivo preferencias.xml para usar el nuevo nombre del paquete. code.google.com/p/android/issues/detail?id=57460
Bernd S
@BerndS He publicado un comentario sobre su problema con la solución. Debe comprender que cambiar el ID de la aplicación al reemplazarlo o establecer el sufijo no afecta los paquetes de Java. Es solo un identificador de su aplicación y está desacoplado de los paquetes de Java. Vea mi respuesta a otra pregunta stackoverflow.com/questions/24178007/…
Damian Petla
1
@JJD Las modificaciones a las que se vincula funcionarían sin ningún script de compilación personalizado. Si desea utilizar marcadores de posición $ {applicationId} para sync_adapter.xml, authenticator.xml debe personalizar su script build.gradle. Veo que ya ha hecho mucho en su script build.gradle, por lo que se siente cómodo con la idea. ¿Seguiste las instrucciones en mi respuesta y todavía no funcionó?
Rob Meeuwisse
1
He actualizado mi respuesta con instrucciones para syncadapter
Damian Petla
39

Nuevo consejo de sistema de compilación de Android: cambio de nombre de autoridad ContentProvider

Supongo que todos ustedes han oído hablar del nuevo sistema de compilación basado en Android Gradle. Seamos honestos, este nuevo sistema de construcción es un gran paso adelante en comparación con el anterior. Todavía no es definitivo (a partir de este escrito, la última versión es 0.4.2) pero ya puede usarlo de manera segura en la mayoría de sus proyectos.

Personalmente, cambié la mayor parte de mi proyecto a este nuevo sistema de compilación y tuve algunos problemas debido a la falta de soporte en algunas situaciones particulares. Uno de los cuales es el soporte para el cambio de nombre de autoridad ContentProvider

El nuevo sistema integrado de Android le permite lidiar con diferentes tipos de su aplicación simplemente modificando el nombre del paquete en el momento de la compilación. Una de las principales ventajas de esta mejora es que ahora puede tener dos versiones diferentes de su aplicación instaladas en el mismo dispositivo al mismo tiempo. Por ejemplo:

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

Utilizando dicha configuración de Gradle, puede ensamblar dos APK diferentes:

• Un APK de depuración con el nombre del paquete com.cyrilmottier.android.app.debug • Un APK de lanzamiento con el nombre del paquete com.cyrilmottier.android.app

El único problema con eso es que no podrá instalar los dos APK al mismo tiempo si ambos exponen un ContentProvider con las mismas autoridades. Lógicamente, necesitamos cambiar el nombre de la autoridad según el tipo de compilación actual ... pero esto no es compatible con el sistema de compilación de Gradle (¿todavía? ... estoy seguro de que se solucionará pronto). Así que aquí hay un camino a seguir:

Primero, debemos mover la declaración ContentProvider del manifiesto de Android del proveedor al tipo de compilación apropiado. Para hacer eso simplemente tendremos:

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>

Asegúrese de eliminar la declaración ContentProvider del AndroidManifest.xml en src / main / porque Gradle no sabe cómo fusionar ContentProviders con el mismo nombre pero con una autoridad diferente.

Finalmente, es posible que necesitemos acceder a la autoridad en el código. Esto se puede hacer fácilmente usando el archivo BuildConfig y el método 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
       }
   }
}

Gracias a esta solución, podrá usar BuildConfig.PROVIDER_AUTHORITY en su ProviderContract e instalar dos versiones diferentes de su aplicación al mismo tiempo.


Originalmente en Google+: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ

Cyril Mottier
fuente
1
Para alguien que no puede ejecutar gradle, porque el error de sintaxis. Aquí está la respuesta: stackoverflow.com/questions/20678118/…
Renan Franca
23

Si bien el ejemplo de Cyril funciona muy bien si solo tiene unos pocos tipos de compilación, se complica rápidamente si tiene muchos tipos de compilación y / o sabores de productos, ya que necesita mantener muchos AndroidManifest.xml diferentes.

Nuestro proyecto consta de 3 tipos de compilación diferentes y 6 sabores con un total de 18 variantes de compilación, por lo que agregamos soporte para ".res-auto" en las autoridades de ContentProvider, que se expanden al nombre del paquete actual y eliminan la necesidad de mantener diferentes 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)
    }
}

El código de ejemplo se puede encontrar aquí: https://gist.github.com/cmelchior/6988275

Christian Melchior
fuente
Cambié a usar algo muy similar para mi proyecto también, porque tuve el mismo problema con los sabores de compilación. Este enfoque funciona muy bien por ahora.
MantasV
2
FileWriter causa problemas en los archivos utf-8, al menos en mi Mac OS. Cambié la línea relacionada a: def writer = new OutputStreamWriter (new FileOutputStream (pathToFile), "UTF-8")
Reza Mohammadi
Esto es realmente genial, gracias! He realizado un pequeño cambio para evitar roturas con cadenas formateadas. gist.github.com/paour/8475929
Pierre-Luc Paour
Esto fue muy útil, pero me encontré con un problema en el que no se compilaría después de una limpieza porque no había un archivo values.xml en la carpeta de compilación en la etapa processManifest. Eso no existe hasta la etapa processResources, en cuyo punto es demasiado tarde para modificar el manifiesto, por lo que para reemplazar .res-auto en los archivos de manifiesto y valores, creo que necesitaría 2 funciones, una llamada por variante. processManifest.doLast, el otro llamado por variant.processResources.doLast.
Niall
20

Desde la versión del complemento 0.8.3 (en realidad 0.8.1 pero no funcionaba correctamente), puede definir recursos dentro del archivo de compilación, por lo que podría ser una solución más limpia porque no necesita crear archivos de cadenas ni depuración / lanzamiento adicional carpetas

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
fuente
2
Cuidado, las autoridades basadas en recursos solo funcionan en Android 2.2.1 y versiones
Pierre-Luc Paour
Gracias por la aclaración.
rciovati
1
esto también es muy útil en searchable.xml para Android: searchSuggestAuthority, porque allí no puede usar $ {applicationId}
user114676
13

No sé si alguien lo menciona. En realidad, después del complemento Android Gradle 0.10+, la fusión de manifiesto proporcionará el soporte oficial para esta función: http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger

En AndroidManifest.xml, puede usar $ {packageName} de esta manera:

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

Y en su build.gradle puede tener:

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

Vea el ejemplo completo aquí: https://code.google.com/p/anymemo/source/browse/AndroidManifest.xml#152

y aquí: https://code.google.com/p/anymemo/source/browse/build.gradle#41

Libertad
fuente
Esta es una gran noticia, pero no parece que sea una solución completa en el caso de elementos <searchable> que necesitan hacer referencia a la autoridad, ya que estos no son parte del manifiesto (pero las estrategias de fusión existentes funcionan para estos archivos, a diferencia del manifiesto).
Pierre-Luc Paour
1
No tiene que usar sabores para esto, también funciona con tipos de compilación. Además, sería bueno mencionar que puede usar BuildConfig.PACKAGE_NAME para obtener una referencia estática a su paquete. Esto es útil para proveedores de contenido donde la autoridad necesita ser conocida en tiempo de ejecución para consultar al proveedor de contenido.
Matt Wolfe
1
También debe actualizarse para usar $ {applicationId} en lugar de $ {packageName} para Android: autoridades
Bernd S
8

Use ${applicationId}marcadores de posición en xml y BuildConfig.APPLICATION_IDen código.

Deberá extender el script de compilación para habilitar marcadores de posición en archivos xml que no sean el manifiesto. Puede usar un directorio de origen por variante de compilación para proporcionar diferentes versiones de los archivos xml, pero el mantenimiento se volverá engorroso muy rápidamente.

AndroidManifest.xml

Puede usar el marcador de posición applicationId de fábrica en el manifiesto. Declare así a su proveedor:

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

Tenga en cuenta el ${applicationId}bit. Esto se reemplaza en el momento de la compilación con el ID de aplicación real para la variante de compilación que se está construyendo.

En codigo

Su ContentProvider necesita construir la cadena de autoridad en el código. Puede usar la clase BuildConfig.

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

Tenga en cuenta el BuildConfig.APPLICATION_IDbit. Es una clase generada con el ID de aplicación real para la variante de compilación que se está construyendo.

res / xml / files, por ejemplo, syncadapter.xml, accountauthenticator.xml

Si desea utilizar un adaptador de sincronización, deberá proporcionar metadatos para ContentProvider y AccountManager en archivos xml en el directorio res / xml /. El marcador de posición applicationId no es compatible aquí. Pero puede extender el script de compilación usted mismo para hackearlo.

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

De nuevo, tenga en cuenta el ${applicationId}. Esto solo funciona si agrega la secuencia de comandos de Gradle a la raíz de su módulo y la aplica desde build.gradle.

build.gradle

Aplique el script de compilación adicional desde el script del módulo build.gradle. Un buen lugar está debajo del complemento de gradle de Android.

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

android {
    compileSdkVersion 21
    // etc.

build-processApplicationId.gradle

A continuación se muestra la fuente de trabajo para un script de compilación res / xml / placeholder. Una versión mejor documentada está disponible en github . Las mejoras y extensiones son bienvenidas.

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

En mi opinión, no es necesario agregar soporte de marcador de posición para las cadenas de recursos. Para el caso de uso anterior, al menos no es necesario. Sin embargo, podría cambiar fácilmente el script para reemplazar no solo los marcadores de posición en el directorio res / xml /, sino también en el directorio res / values ​​/.

Rob Meeuwisse
fuente
6

Prefiero una mezcla entre Cyril y rciovati. Creo que es más simple, solo tienes dos modificaciones.

El se build.gradleparece a:

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

Y el 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
fuente
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>

Código:

public static final String CONTENT_AUTHORITY = BuildConfig.CONTENT_AUTHORITY;
public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
maros136
fuente
4

Basado en la muestra de @ChristianMelchior, aquí está mi solución, que soluciona dos problemas en las soluciones anteriores:

  • Las soluciones que cambian values.xml en el directorio de compilación provocan una reconstrucción completa de los recursos (incluido un conjunto de todos los elementos extraíbles)

  • por una razón desconocida, IntelliJ (y probablemente Android Studio) no procesan de manera confiable los recursos, lo que hace que la compilación contenga .res-autoautoridades de proveedor no reemplazadas

Esta nueva solución hace las cosas más a la manera de Gradle al crear una nueva tarea y permite compilaciones incrementales al definir archivos de entrada y salida.

  1. crear un archivo (en el ejemplo lo puse en un variantsdirectorio), formateado como un archivo xml de recursos, que contiene recursos de cadena. Estos se fusionarán en los recursos de la aplicación, y cualquier aparición .res-autoen los valores se reemplazará con el nombre del paquete de la variante, por ejemplo<string name="search_provider">.res-auto.MySearchProvider</string>

  2. agregue el build_extras.gradlearchivo de esta esencia a su proyecto y haga referencia desde el principal build.gradleagregando en apply from: './build_extras.gradle'algún lugar sobre el androidbloque

  3. asegúrese de establecer un nombre de paquete predeterminado agregándolo al android.defaultConfigbloque debuild.gradle

  4. en AndroidManifest.xmly otros archivos de configuración (como xml/searchable.xmlpara proveedores de búsqueda de autocompletado), haga referencia al proveedor (por ejemplo @string/search_provider)

  5. si necesita obtener el mismo nombre, puede usar la BuildConfig.PACKAGE_NAMEvariable, por ejemploBuildConfig.PACKAGE_NAME + ".MySearchProvider"

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


Actualización: este método solo funciona en Android 2.2.1 y versiones posteriores. Para plataformas anteriores, vea esta respuesta , que tiene su propio conjunto de problemas, ya que la nueva fusión de manifiesto aún es muy aproximada ...

Pierre-Luc Paour
fuente
¿Dónde estás poniendo tu directorio de variantes? Tengo un gran proyecto de Android Studio que depende de varios módulos de Android: mi aplicación principal y varios módulos de la Biblioteca de Android. Puedo construir desde la línea de comandos, pero cuando trato de construir desde Android Studio, se busca en variants/res-auto-values.xmlrelación con /Applications/Android Studio.app/bin/. es decir, no recibo FileNotFoundException para /Applications/Android Studio.app/bin/variants/res-auto-values.xml. Estoy corriendo en una Mac. Esta es una gran solución, pero me encantaría que funcione en el IDE para los otros miembros del equipo.
user1978019
1
Solucioné mi propio problema. Gradle parece resolver rutas utilizando System.getProperty("user.dir"), lo que devuelve un resultado diferente cuando se invoca por la compilación de Android Studio. La solución es usar la ruta relativa al directorio del proyecto, que se devuelve con gradle.startParameter.getProjectDir(). Vea mi comentario en la esencia vinculada de Paour también.
user1978019
Cuidado, las autoridades basadas en recursos solo funcionan en Android 2.2.1 y versiones
Pierre-Luc Paour
2

Desafortunadamente, la versión actual (0.4.1) del complemento de Android no parece proporcionar una buena solución para esto. No he tenido tiempo para probar esto todavía, pero una posible solución a este problema sería el uso de un recurso de cadena @string/provider_authority, y el uso que en el manifiesto: android:authority="@string/provider_authority". Luego tiene un res/values/provider.xmlen la carpeta res de cada tipo de compilación que debería anular la autoridad, en su caso esto seríasrc/debug/res

He investigado la posibilidad de generar el archivo xml sobre la marcha, pero de nuevo, no parece haber buenos enganches en la versión actual del complemento. Sin embargo, recomiendo poner una solicitud de función, puedo imaginar que más personas se encontrarán con este mismo problema.

Marcus Forsell Stahre
fuente
Hola Marcus, gracias por tu respuesta. Su solución sugerida es la única que puedo pensar en mí por ahora. Pero mi problema es que no sé cómo lograrlo con Gradle.
MantasV
2

La respuesta en esta publicación me funciona.

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

Utilizo 3 sabores diferentes, así que creo 3 manifiestos con el proveedor de contenido en cada sabor, como dijo kevinrschultz:

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

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

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

Su manifiesto principal no incluye proveedores:

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

Y su manifiesto en cada sabor, incluido el proveedor.

Gratis:

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

Pagado:

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

Otro:

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

¿Por qué no solo agregar esto?

type.packageNameSuffix = ". $ type.name"

Guydemossyrock
fuente
0

Mi solución es usar el reemplazo de marcador de posición en AndroidManifest.xml. También maneja packageNameSuffixatributos para que pueda tener debugy releasetambién cualquier otra compilación personalizada en el mismo 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')
}

Lo tengo en un gist si quieres ver si evoluciona más tarde.

Me pareció un enfoque más elegante que los múltiples recursos y los enfoques de análisis XML.

Saad Farooq
fuente