Використання типів збірки в 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.

Я бачу одну можливість вирішити це. Якщо я правильно розумію, ви повинні мати можливість вказати різні файли, які слід використовувати під час створення. Тоді я маю змогу помістити різні повноваження в різні файли ресурсів (і з Manifest встановити повноваження як рядовий ресурс) і сказати Gradle використовувати різний ресурс для збирання налагодження. Це можливо? Якщо так, то будь-які натяки на те, як цього досягти, були б приголомшливими!

Чи, можливо, можна безпосередньо змінити Manifest за допомогою Gradle? Будь-яке інше рішення про те, як запустити один і той же додаток із ContentProvider на одному пристрої, завжди вітається.


Для тих, хто зацікавлений у відстеженні підтримки вище за потоком цього випадку: звіт про помилки AOSP . "Офіційна" поточна позиція полягає у використанні явного переважаючого рішення .
дессеїм

Відповіді:


226

Жодна з існуючих відповідей мене не влаштовувала, проте Свобода була близькою. Так це я роблю. Перш за все в даний момент, з яким я працюю:

  • Android Studio Beta 0.8.2
  • Плагін Gradle 0,12. +
  • Градле 1.12

Моя мета - запустити Debugверсію разом із Releaseверсією на одному пристрої, використовуючи той самий ContentProvider.


У build.gradle суфіксу набору додатків для збирання налагодження:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

В наборі файлів AndroidManifest.xmlandroid: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 Beta 0.9.2
  • Плагін Gradle 0.14.1
  • Градле 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 + Space) не працює для цих згенерованих ресурсів, тому вам доведеться вводити їх вручну


7
Найкраща відповідь ІМХО. Хороший короткий і простий приклад.
rekire

Так, це найкращий спосіб, який я бачив досі. Дуже дякую вам за обмін! Ще одна проблема не пов’язана з цим, оскільки мені потрібно оновити явний намір у файлі preferences.xml, щоб використовувати нову назву пакета. code.google.com/p/android/isissue/detail?id=57460
Bernd S

@BerndS Я опублікував коментар до вашої проблеми з рішенням. Вам потрібно зрозуміти, що зміна applicationId шляхом його заміни або встановлення суфіксу не впливає на пакети java. Це просто ідентифікатор вашої програми, і він відокремлюється від пакетів Java. Дивіться мою відповідь на інше запитання stackoverflow.com/questions/24178007/…
Damian Petla

1
@JJD Модифікації, на які ви посилаєтесь, працюватимуть без будь-якого спеціального сценарію збірки. Якщо ви хочете використовувати $ {applicationId} заповнювачі для sync_adapter.xml, authentator.xml, ви повинні налаштувати свій сценарій build.gradle. Я бачу, що ви вже багато зробили в своєму сценарії build.gradle, щоб вам було зручно з ідеєю. Ви дотримувались вказівок у моїй відповіді, і це все ще не спрацювало?
Роб Meeuwisse

1
Я оновив свою відповідь із способом синхронізатора
Damian Petla

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 (все ж? ... Я впевнений, що це буде виправлено незабаром). Тож ось такий шлях:

Спочатку нам потрібно перенести заявку провайдера Android manifest ContentProvider у відповідний тип збірки. Для цього нам просто доведеться:

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>

Обов’язково видаліть декларацію ContentProvider з AndroidManifest.xml в src / main /, оскільки Gradle не знає, як об'єднати ContentProviders з тим самим іменем, але іншим органом.

Нарешті, нам може знадобитися доступ до повноважень у коді. Це можна зробити досить легко, використовуючи файл 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

2
FileWriter створює проблеми з файлами utf-8, принаймні на моїй Mac OS. Я змінив відповідний рядок на: def write = новий OutputStreamWriter (новий FileOutputStream (pathToFile), "UTF-8")
Reza Mohammadi

Це справді чудово, дякую! Я вніс невелику зміну, щоб запобігти поломці відформатованих рядків. gist.github.com/paour/8475929
П'єр-Люк Паур

Це було дуже корисно, але я зіткнувся з проблемою, коли він не збирався би бути створений після чистого, оскільки в папці build на етапі ProcessManifest не було файлу values.xml. Цього не існує до етапу processResources, в який момент вже занадто пізно змінювати маніфест, щоб замінити .res-auto як у файлах маніфестів, так і значень, я думаю, вам знадобляться 2 функції, одна викликана за варіантом. processManifest.doLast, інший називається variant.processResources.doLast.
Niall

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>

2
Остерігайтеся, що влада, що базується на ресурсах, працює лише на Android 2.2.1 та пізніших версіях: github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour

Дякуємо за роз’яснення.
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: vlasti
Бернд S

8

Використовуйте ${applicationId}заповнювачі в xml та BuildConfig.APPLICATION_IDкоді.

Вам потрібно буде розширити скрипт збірки, щоб включити заповнювачі у файли xml, окрім маніфесту. Ви можете використовувати каталог джерел для кожного варіанта збірки, щоб надати різні версії файлів xml, але обслуговування стане громіздким дуже швидко.

AndroidManifest.xml

Ви можете використовувати заповнення заповнення додаткаId поза полем у маніфесті. Заявіть провайдера так:

<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 / файли, наприклад, syncadapter.xml, accounttauthenticator.xml

Якщо ви хочете використовувати адаптер синхронізації, вам потрібно буде надати метадані для ContentProvider та AccountManager у файлах xml у каталозі res / xml /. Тут не підтримується заповнювач додаткаId. Але ви можете розширити сценарій збирання самостійно, щоб зламати його.

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

build-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 ​​/.


6

Я б більше віддав перевагу суміші між Кирилом та ричовати. Я думаю, що простіше, у вас є лише дві модифікації.

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

5

gradle.build

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.example.awsomeapp"
        minSdkVersion 9
        targetSdkVersion 23
        versionCode 1
        versionName "1.0.0"
    }

    productFlavors
    {
        prod {
            applicationId = "com.example.awsomeapp"
        }

        demo {
            applicationId = "com.example.awsomeapp.demo"
            versionName = defaultConfig.versionName + ".DEMO"
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
            debuggable false
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }

        debug {
            applicationIdSuffix ".debug"
            versionNameSuffix = ".DEBUG"
            debuggable true
        }
    }

    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            // rename the apk
            def file = output.outputFile;
            def newName;
            newName = file.name.replace(".apk", "-" + defaultConfig.versionName + ".apk");
            newName = newName.replace(project.name, "awsomeapp");
            output.outputFile = new File(file.parent, newName);
        }

        //Generate values Content Authority and Account Type used in Sync Adapter, Content Provider, Authenticator
        def valueAccountType = applicationId + '.account'
        def valueContentAuthority = applicationId + '.authority'

        //generate fields in Resource string file generated.xml
        resValue "string", "content_authority", valueContentAuthority
        resValue "string", "account_type", valueAccountType

        //generate fields in BuildConfig class
        buildConfigField "String", "ACCOUNT_TYPE", '"'+valueAccountType+'"'
        buildConfigField "String", "CONTENT_AUTHORITY", '"'+valueContentAuthority+'"'

        //replace field ${valueContentAuthority} in AndroidManifest.xml
        mergedFlavor.manifestPlaceholders = [ valueContentAuthority: valueContentAuthority ]
    }
}

authentator.xml

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="@string/account_type"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:smallIcon="@drawable/ic_launcher" />

sync_adapter.xml

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
              android:contentAuthority="@string/content_authority"
              android:accountType="@string/account_type"
              android:userVisible="true"
              android:allowParallelSyncs="false"
              android:isAlwaysSyncable="true"
              android:supportsUploading="true"/>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0.0" package="com.example.awsomeapp">

    <uses-permission android:name="android.permission.GET_ACCOUNTS"/><!-- SyncAdapter and GCM requires a Google account. -->
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>

    <!-- GCM Creates a custom permission so only this app can receive its messages. -->
    <permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
    <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>

    <application....
    .......

        <!-- Stub Authenticator --> 
        <service 
                android:name="com.example.awsomeapp.service.authenticator.CAuthenticatorService"
                android:exported="true">
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator"/>
            </intent-filter>
            <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator"/>
        </service>
        <!--  -->

        <!-- Sync Adapter -->
        <service
                android:name="com.example.awsomeapp.service.sync.CSyncService"
                android:exported="true"
                android:process=":sync">
            <intent-filter>
                <action android:name="android.content.SyncAdapter"/>
            </intent-filter>
            <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/sync_adapter" />
        </service>
        <!--  -->

        <!-- Content Provider -->
        <provider android:authorities="${valueContentAuthority}"
            android:exported="false" 
            android:name="com.example.awsomeapp.database.contentprovider.CProvider">
        </provider>
        <!--  --> 
    </application>
</manifest>

Код:

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

4

На основі вибірки @ChristianMelchior, ось моє рішення, яке виправляє дві проблеми в попередніх рішеннях:

  • рішення, що змінюють values.xml в каталозі збірки, спричиняють повну перебудову ресурсів (включаючи aapt всіх ресурсів)

  • з незрозумілої причини 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. в 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 для інших членів команди.
користувач1978019

1
Виправлена ​​моя проблема. Здається, Gradle вирішує шляхи за допомогою System.getProperty("user.dir"), що повертає інший результат, коли викликається збірка Android Studio. Рішення полягає у використанні шляху відносно каталогу проектів, до якого повертається gradle.startParameter.getProjectDir(). Дивіться також мій коментар у пов'язаній суті Паура.
user1978019

Остерігайтеся, що влада, що базується на ресурсах, працює лише на Android 2.2.1 та пізніших версіях: github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour


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 маніфести з постачальником вмісту в кожному ароматі, як сказав kevinrschultz:

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>


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

У мене це на a gist теж є, якщо ви хочете побачити, чи розвинеться воно пізніше.

Я вважав, що це більш елегантний підхід, ніж численні підходи та аналіз XML.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.