Использование типов сборки в Gradle для запуска того же приложения, которое использует ContentProvider на одном устройстве

124

Я настроил Gradle, чтобы добавить суффикс имени пакета в мое приложение отладки, чтобы иметь версию выпуска, которую я использую, и версию отладки на одном телефоне. Я ссылался на это: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

Мой файл build.gradle выглядит так:

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

Все работает нормально, пока я не начну использовать ContentProvider в своем приложении. Я получил:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

Я понимаю, что это происходит потому, что два приложения (выпуск и отладка) регистрируют один и тот же орган ContentProvider.

Я вижу одну возможность решить эту проблему. Если я правильно понимаю, у вас должна быть возможность указать разные файлы для использования при сборке. Затем я должен иметь возможность помещать разные права доступа в разные файлы ресурсов (и из манифеста устанавливать права доступа как строковый ресурс) и указывать Gradle использовать другой ресурс для сборки отладки. Это возможно? Если да, то любые подсказки о том, как этого добиться, были бы потрясающими!

Или, может быть, можно напрямую изменить Manifest с помощью Gradle? Всегда приветствуется любое другое решение о том, как запустить одно и то же приложение с ContentProvider на одном устройстве.

MantasV
источник
Для тех, кто заинтересован в отслеживании восходящей поддержки для этого варианта использования: отчет об ошибке AOSP . «Официальная» текущая позиция - использовать манифестное приоритетное решение .
desseim

Ответы:

226

Ни один из существующих ответов меня не удовлетворил, однако Liberty была близка. Вот как я это делаю. В первую очередь на данный момент я работаю с:

  • Android Studio Beta 0.8.2
  • Плагин Gradle 0.12. +
  • Gradle 1.12

Моя цель - запустить Debugверсию вместе с Releaseверсией на одном устройстве, используя то же самое ContentProvider.


В build.gradle вашего приложения установите суффикс для сборки Debug:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

В файле AndroidManifest.xml установите android:authoritiesсвойство вашего ContentProvider:

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

В свойстве кодового набора, AUTHORITYкоторое можно использовать везде, где это необходимо в вашей реализации:

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

Совет: раньше это былоBuildConfig.PACKAGE_NAME

Это оно! Это будет работать как шарм. Продолжайте читать, если вы используете SyncAdapter!


Обновление для SyncAdapter (14.11.2014)

Еще раз начну с моей текущей настройки:

  • Бета-версия Android Studio 0.9.2
  • Плагин Gradle 0.14.1
  • Gradle 2.1

В принципе, если вам нужно настроить некоторые значения для разных сборок, вы можете сделать это из файла build.gradle:

  • используйте buildConfigField для доступа к нему из BuildConfig.javaкласса
  • используйте resValue для доступа к нему из ресурсов, например @ string / your_value

В качестве альтернативы для ресурсов вы можете создать отдельные каталоги buildType или разновидностей и переопределить XML-файлы или значения в них. Однако я не собираюсь использовать его в примере ниже.

пример


В файл build.gradle добавьте следующее:

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

Вы увидите результаты в классе BuildConfig.java

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

и в 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>

В вашем Authenticator.xml используйте ресурс, указанный в файле 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"
/>

В вашем syncadapter.xml использовать один и тот же ресурс снова и @ струнные / власти слишком

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

Совет: автозаполнение (Ctrl + Пробел) не работает для этих сгенерированных ресурсов, поэтому вам придется вводить их вручную

Дамиан Петла
источник
7
Лучший ответ ИМХО. Хороший короткий и простой пример.
rekire
Да, это лучший обходной путь, который я когда-либо видел. Спасибо вам большое за обмен! По-прежнему есть еще одна проблема, не связанная с этим, поскольку мне нужно обновить явное намерение в файле preferences.xml, чтобы использовать новое имя пакета. code.google.com/p/android/issues/detail?id=57460
Bernd S,
@BerndS Я оставил комментарий к вашей проблеме с решением. Вы должны понимать, что изменение applicationId путем его замены или установки суффикса не влияет на пакеты java. Это просто идентификатор вашего приложения, отделенный от пакетов Java. См. Мой ответ на другой вопрос stackoverflow.com/questions/24178007/…
Дамиан Петла
1
@JJD Изменения, на которые вы ссылаетесь, будут работать без какого-либо специального скрипта сборки. Если вы хотите использовать заполнители $ {applicationId} для sync_adapter.xml, authentication.xml, вы должны настроить свой скрипт build.gradle. Я вижу, что вы уже многое сделали в своем скрипте build.gradle, так что идея вас устраивает. Вы следовали инструкциям в моем ответе, но он по-прежнему не работает?
Rob Meeuwisse
1
Я обновил свой ответ инструкциями для syncadapter
Дамиан Петла
39

Совет по новой системе сборки Android: переименование центра ContentProvider

Думаю, все вы слышали о новой системе сборки Android на основе Gradle. Давайте будем честными, эта новая система сборки - огромный шаг вперед по сравнению с предыдущей. Это еще не окончательная версия (на момент написания последней версии была 0.4.2), но вы уже можете безопасно использовать ее в большинстве своих проектов.

Я лично переключил большую часть своего проекта на эту новую систему сборки, и у меня возникли некоторые проблемы из-за отсутствия поддержки в некоторых конкретных ситуациях. Одним из них является поддержка переименования полномочий ContentProvider.

Новая система, созданная для Android, позволяет вам работать с различными типами приложений, просто изменяя имя пакета во время сборки. Одним из основных преимуществ этого улучшения является то, что теперь вы можете одновременно установить две разные версии вашего приложения на одном устройстве. Например:

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

Используя такую ​​конфигурацию Gradle, вы можете собрать два разных APK:

• APK-файл отладки с именем пакета com.cyrilmottier.android.app.debug. • APK-файл выпуска с именем пакета com.cyrilmottier.android.app.

Единственная проблема заключается в том, что вы не сможете установить два APK одновременно, если они оба предоставляют ContentProvider с одинаковыми полномочиями. Довольно логично, что нам нужно переименовать авторитет в зависимости от текущего типа сборки ... но это не поддерживается системой сборки Gradle (пока? ... Я уверен, что это скоро будет исправлено). Итак, вот способ:

Сначала нам нужно переместить объявление ContentProvider в манифесте Android-провайдера в соответствующий тип сборки. Для этого у нас просто будет:

SRC / отлаживать / 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 / выпуск / 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>

Обязательно удалите объявление ContentProvider из AndroidManifest.xml в src / main /, потому что Gradle не знает, как объединить ContentProvider с тем же именем, но с разными полномочиями.

Наконец, нам может потребоваться доступ к полномочиям в коде. Это легко сделать с помощью файла BuildConfig и метода 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
       }
   }
}

Благодаря этому обходному пути вы сможете использовать BuildConfig.PROVIDER_AUTHORITY в своем ProviderContract и одновременно устанавливать две разные версии вашего приложения.


Первоначально в Google+: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ

Сирил Моттье
источник
1
Для тех, кто не может запускать gradle, возникает ошибка синтаксиса. Вот ответ: stackoverflow.com/questions/20678118/…
Ренан Франка
23

Хотя пример Кирилла отлично работает, если у вас есть только несколько типов сборки, он быстро усложняется, если у вас много типов сборки и / или разновидностей продукта, поскольку вам нужно поддерживать множество разных AndroidManifest.xml.

Наш проект состоит из 3 различных типов сборки и 6 разновидностей, всего 18 вариантов сборки, поэтому вместо этого мы добавили поддержку ".res-auto" в органах управления ContentProvider, которые расширяются до текущего имени пакета и устраняют необходимость поддерживать разные файлы 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)
    }
}

Пример кода можно найти здесь: https://gist.github.com/cmelchior/6988275

Кристиан Мельхиор
источник
Я также перешел на использование чего-то очень похожего для своего проекта, потому что у меня была такая же проблема с вариантами сборки. На данный момент этот подход работает очень хорошо.
MantasV 05
2
FileWriter создает проблемы с файлами UTF-8, по крайней мере, на моей Mac OS. Я изменил соответствующую строку на: def writer = new OutputStreamWriter (new FileOutputStream (pathToFile), «UTF-8»)
Реза Мохаммади
Это действительно здорово, спасибо! Я сделал небольшое изменение, чтобы предотвратить поломку отформатированных строк. gist.github.com/paour/8475929
Пьер-Люк Паур
Это было очень полезно, но я столкнулся с проблемой, когда он не собирался после очистки, потому что не было файла values.xml в папке сборки на этапе processManifest. Этого не существует до этапа processResources, на котором уже слишком поздно изменять манифест, поэтому для замены .res-auto как в файле манифеста, так и в файлах значений, я думаю, вам понадобятся 2 функции, одна из которых вызывается по варианту. processManifest.doLast, другой вызывается вариантом .processResources.doLast.
Найл
20

Поскольку версия плагина 0.8.3 (на самом деле 0.8.1, но она не работала должным образом), вы можете определять ресурсы в файле сборки, чтобы это могло быть более чистым решением, потому что вам не нужно создавать файлы строк или дополнительную отладку / выпуск папки.

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
источник
2
Остерегайтесь, авторитеты на основе ресурсов работают только на Android 2.2.1 и новее: github.com/android/platform_frameworks_base/commit/…
Пьер-Люк Паур,
Спасибо за разъяснения.
rciovati
1
это также очень полезно в searchchable.xml для android: searchSuggestAuthority, потому что там вы не можете использовать $ {applicationId}
user114676,
13

Не знаю, упоминал ли об этом кто-нибудь. Фактически после плагина android gradle 0.10+ слияние манифестов обеспечит официальную поддержку этой функции: http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger

В AndroidManifest.xml вы можете использовать $ {packageName} следующим образом:

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

И в вашем build.gradle вы можете иметь:

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

См. Полный пример здесь: https://code.google.com/p/anymemo/source/browse/AndroidManifest.xml#152

и здесь: https://code.google.com/p/anymemo/source/browse/build.gradle#41

свобода
источник
Это отличная новость, но не похоже, что это полное решение в случае элементов <searchable>, которые должны ссылаться на авторство, поскольку они не являются частью манифеста (но существующие стратегии слияния действительно работают для этих файлов, в отличие от Манифеста).
Пьер-Люк Паур
1
Для этого необязательно использовать ароматизаторы, он также работает с типами сборки. Также было бы неплохо упомянуть, что вы можете использовать BuildConfig.PACKAGE_NAME для получения статической ссылки на ваш пакет. Это полезно для поставщиков содержимого, которым необходимо знать во время выполнения полномочия для запроса поставщика содержимого.
Мэтт Вулф
1
Также следует обновить, чтобы использовать $ {applicationId} вместо $ {packageName} для android: rules
Бернд С.
8

Используйте ${applicationId}заполнители в xml и BuildConfig.APPLICATION_IDв коде.

Вам нужно будет расширить скрипт сборки, чтобы включить заполнители в XML-файлах, кроме манифеста. Вы можете использовать исходный каталог для каждого варианта сборки, чтобы предоставить разные версии файлов xml, но обслуживание очень быстро станет обременительным.

AndroidManifest.xml

Вы можете использовать заполнитель applicationId из коробки в манифесте. Объявите своего провайдера так:

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

Обратите внимание на ${applicationId}бит. Он заменяется во время сборки фактическим applicationId для строящегося варианта сборки.

В коде

Ваш ContentProvider должен создать в коде авторитетную строку. Он может использовать класс BuildConfig.

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

Обратите внимание на BuildConfig.APPLICATION_IDбит. Это сгенерированный класс с фактическим идентификатором applicationId для строящегося варианта сборки.

res / xml / files, например syncadapter.xml, accountauthenticator.xml

Если вы хотите использовать адаптер синхронизации, вам необходимо будет предоставить метаданные для ContentProvider и AccountManager в файлах xml в каталоге res / xml /. Заполнитель applicationId здесь не поддерживается. Но вы можете самостоятельно расширить скрипт сборки, чтобы взломать его.

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

Снова обратите внимание на расширение ${applicationId}. Это работает, только если вы добавите приведенный ниже скрипт gradle в корень своего модуля и примените его из build.gradle.

build.gradle

Примените дополнительный сценарий сборки из сценария модуля build.gradle. Хорошее место ниже плагина Android Gradle.

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

android {
    compileSdkVersion 21
    // etc.

наращивание processApplicationId.gradle

Ниже приведен рабочий источник сценария сборки res / xml / placeholder. Лучшая документированная версия доступна на github . Улучшения и расширения приветствуются.

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

На мой взгляд, нет необходимости добавлять поддержку заполнителей для строк ресурсов. По крайней мере, для вышеуказанного варианта использования это не требуется. Однако вы можете легко изменить сценарий, чтобы не только заменить заполнители в каталоге res / xml /, но и в каталоге res / values ​​/.

Роб Meeuwisse
источник
6

Я бы предпочел смесь Cyril и rciovati. Думаю проще, у вас всего две модификации.

В build.gradleвыглядит следующим образом :

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

И 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
источник
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>

Код:

public static final String CONTENT_AUTHORITY = BuildConfig.CONTENT_AUTHORITY;
public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
maros136
источник
4

На основе образца @ChristianMelchior вот мое решение, которое устраняет две проблемы в предыдущих решениях:

  • решения, которые изменяют values.xml в каталоге сборки, вызывают полное перестроение ресурсов (включая APT всех чертежей)

  • по неизвестной причине IntelliJ (и, вероятно, Android Studio) не обрабатывает ресурсы надежно, в результате чего сборка содержит незамещенные .res-autoполномочия поставщика.

Это новое решение делает вещи более похожими на Gradle, создавая новую задачу и допускает инкрементные сборки, определяя входные и выходные файлы.

  1. создать файл (в этом примере я поместил его в variantsкаталог), отформатированный как ресурсный xml-файл, который содержит строковые ресурсы. Они будут объединены с ресурсами приложения, и любое вхождение .res-autoв значениях будет заменено на имя пакета варианта, например<string name="search_provider">.res-auto.MySearchProvider</string>

  2. добавьте build_extras.gradleфайл из этой сущности в свой проект и ссылайтесь на него из основного build.gradle, добавив apply from: './build_extras.gradle'где-нибудь над androidблоком

  3. убедитесь, что вы установили имя пакета по умолчанию, добавив его в android.defaultConfigблокbuild.gradle

  4. in AndroidManifest.xmlи другие файлы конфигурации (например, xml/searchable.xmlдля поставщиков поиска с автозаполнением), укажите поставщика (например @string/search_provider)

  5. если вам нужно получить такое же имя, вы можете использовать BuildConfig.PACKAGE_NAMEпеременную, напримерBuildConfig.PACKAGE_NAME + ".MySearchProvider"

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


Обновление: этот метод работает только на Android 2.2.1 и новее. Для более ранних платформ см. Этот ответ , который имеет свой собственный набор проблем, поскольку новое слияние манифестов все еще очень грубое ...

Пьер-Люк Паур
источник
Куда вы помещаете каталог вариантов? У меня есть один большой проект Android Studio, который зависит от нескольких модулей Android - моего основного приложения и нескольких модулей библиотеки Android. Я могу строить из командной строки, но когда я пытаюсь создать из Android Studio, он ищет variants/res-auto-values.xmlотносительно /Applications/Android Studio.app/bin/. т.е. я не получаю FileNotFoundException для /Applications/Android Studio.app/bin/variants/res-auto-values.xml. Я работаю на Mac. Это отличное решение, но мне бы хотелось, чтобы оно работало в среде IDE для других членов команды.
user1978019 05
1
Исправил мою проблему. Похоже, что Gradle разрешает использование путей System.getProperty("user.dir"), что возвращает другой результат при вызове сборкой Android Studio. Решение состоит в том, чтобы использовать путь относительно каталога проекта, который возвращается с gradle.startParameter.getProjectDir(). См. Также мой комментарий в связанной сущности Paour.
user1978019 06
Остерегайтесь, органы власти на основе ресурсов работают только на Android 2.2.1 и новее: github.com/android/platform_frameworks_base/commit/…
Пьер-Люк Паур,
2

К сожалению, текущая версия (0.4.1) плагина для Android, похоже, не дает хорошего решения для этого. У меня не было времени , чтобы попробовать это еще, но можно обойти эту проблему будет использовать строковый ресурс @string/provider_authority, и использование , что в манифесте: android:authority="@string/provider_authority". Затем у вас есть res/values/provider.xmlв папке res каждого типа сборки, которая должна переопределить полномочия, в вашем случае это будетsrc/debug/res

Я изучал возможность создания xml-файла на лету, но, опять же, в текущей версии плагина, похоже, нет хороших ловушек для него. Я бы порекомендовал отправить запрос функции, я могу представить, что больше людей столкнутся с этой же проблемой.

Маркус Форселл Старе
источник
Привет, Маркус, спасибо за ответ. Предлагаемое вами решение - единственное, о чем я могу думать на данный момент. Но моя проблема в том, что я не знаю, как добиться этого с помощью Gradle.
MantasV
2

Ответ в этом посте мне подходит.

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

Я использую 3 разных вкуса, поэтому я создаю 3 манифеста с поставщиком контента в каждом аромате, как сказал Кевинршульц:

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

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

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

Ваш основной манифест не включает поставщиков:

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

И ваш манифест в каждом вашем вкусе, включая поставщика.

Свободно:

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

Выплачено:

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

Другой:

<?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
источник
0

Почему бы просто не добавить это?

type.packageNameSuffix = ". $ type.name"

guydemossyrock
источник
0

Мое решение - использовать замену заполнителя в AndroidManifest.xml. Он также обрабатывает packageNameSuffixатрибуты, поэтому вы можете иметь debugи releaseлюбые другие пользовательские сборки на одном устройстве.

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

У меня тоже есть это, gistесли вы хотите увидеть, изменится ли он позже.

Я обнаружил, что это более элегантный подход, чем подходы с использованием нескольких ресурсов и синтаксического анализа XML.

Саад Фарук
источник