Использование типов сборки в Gradle для запуска того же приложения, которое использует ContentProvider на одном устройстве
Я настроил Gradle, чтобы добавить суффикс имени пакета в мое отладочное приложение, чтобы у меня была версия выпуска, которую я использую, и отладочная версия на одном телефоне. Я ссылался на это: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types
моя сборка.файл Gradle выглядит так:
...
android
{
...
buildTypes
{
debug
{
packageNameSuffix ".debug"
versionNameSuffix " debug"
}
}
}
все работает нормально, пока я не начну использовать ContentProvider в моем приложении. Я получаю:
Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]
Я понимаю, что это происходит потому что два приложения (release и debug) регистрируют одни и те же полномочия ContentProvider.
Я вижу одну возможность решить эту проблему. Если я правильно понимаю, вы должны быть в состоянии указать различные файлы для использования при построении. Затем я должен быть в состоянии поместить разные полномочия в разные файлы ресурсов (и из Манифеста установить полномочия как строковый ресурс) и сказать Gradle использовать другой ресурс для отладки сборки. Это возможно? Если да, то любые подсказки о том, как этого добиться, будут потрясающе!
или, может быть, можно напрямую изменить манифест с помощью Gradle? Любое другое решение о том, как запустить то же приложение с ContentProvider на одном устройстве всегда приветствуется.
14 ответов:
ни один из существующих ответов меня не удовлетворил, однако свобода была близка. Так вот как я это делаю. Прежде всего на данный момент я работаю с:
- Android Studio Beta 0.8.2
- Gradle плагин 0.12.+
- ш 1.12
мой цель бежать
Debug
версия вместе сRelease
версия на том же устройстве с использованием того жеContentProvider
.
на построить.gradle of ваше приложение устанавливает суффикс для отладки сборки:
buildTypes { debug { applicationIdSuffix ".debug" } }
на AndroidManifest.xml
новая система сборки 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 в конфигурация, вы можете собрать два различных APKs:
* отладка APK с com.сирильмотье.андроид.приложение.имя пакета отладки * Релиз APK с com.сирильмотье.андроид.имя пакета приложения
единственная проблема заключается в том, что вы не сможете установить два APKs одновременно, если они оба предоставляют 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
в то время как пример Кирилла отлично работает, если у вас есть только несколько типов сборки, он быстро усложняется, если у вас есть много типов сборки и/или вкусов продукта, поскольку вам нужно поддерживать много разных 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
поскольку плагин версии 0.8.3 (на самом деле 0.8.1, но он не работал должным образом) вы можете определить ресурсы в файле сборки, так что это может быть более чистым решением, потому что вам не нужно создавать файлы строк или дополнительные папки отладки/выпуска.
построить.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>
Я не знаю, если кто-то упомянул об этом. На самом деле после 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" />
и в сборке.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
использовать
${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"; // ... }
Примечание
Я бы предпочел смесь между Кириллом и rciovati. Я думаю, что проще, у вас есть только две модификации.
The
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>
основываясь на примере @ChristianMelchior, вот мое решение, которое устраняет две проблемы в предыдущих решениях:
решения, которые меняют значения.xml в каталоге сборки вызывает полное перестроение ресурсов (включая aapt всех чертежей)
по неизвестной причине IntelliJ( и, вероятно, Android Studio) не надежно обрабатывают ресурсы, в результате чего сборка содержит un-replaced
.res-auto
провайдер властиэто новое решение делает вещи более Gradle путем создания новой задачи и позволяет выполнять инкрементные сборки путем определения входных и выходных файлов.
создать файл (в примере я положил его в
variants
каталог), отформатированный как XML-файл ресурса, который содержит строковые ресурсы. Они будут объединены в ресурсы приложения, и любое появление.res-auto
в значениях будут заменены на вариант имя пакета, например<string name="search_provider">.res-auto.MySearchProvider</string>
добавить С в этом суть к вашему проекту и ссылаться на него из главного
build.gradle
добавлятьapply from: './build_extras.gradle'
где-то вышеandroid
блокубедитесь, что вы установили имя пакета по умолчанию, добавив его в
android.defaultConfig
блокbuild.gradle
на
AndroidManifest.xml
и другие файлы конфигурации (например,xml/searchable.xml
для поставщиков поиска автоматического завершения), обратитесь к поставщику (например@string/search_provider
)если вам нужно сделать то же имя, вы можете использовать
BuildConfig.PACKAGE_NAME
переменной, напримерBuildConfig.PACKAGE_NAME + ".MySearchProvider"
https://gist.github.com/paour/9189462
Обновление: этот метод работает только на Android 2.2.1 и выше. Для более ранних платформ, см. ответ, который имеет свой собственный набор проблем, поскольку в манифесте объединения еще очень грубый по краям…
gradle.строй
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 ] } }
аутентификатора.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;
Я написал блог с образцом проекта Github, который решает эту проблему (и другие подобные проблемы) несколько иначе, чем Кирилл.
http://brad-android.blogspot.com/2013/08/android-gradle-building-unique-build.html
к сожалению, текущая версия (0.4.1) плагина android, похоже, не обеспечивает хорошего решения для этого. У меня еще не было времени попробовать это, но возможным обходным путем для этой проблемы было бы использовать строковый ресурс
@string/provider_authority
, и использовать это в манифесте:android:authority="@string/provider_authority"
. Тогда у вас естьres/values/provider.xml
в папке res каждого типа сборки, который должен переопределить полномочия, в вашем случае это будетsrc/debug/res
Я изучил создание xml-файла на лету, но опять же, там, кажется, не будет никаких хороших крючков для него в текущей версии плагина. Я бы рекомендовал ввести запрос функции, хотя, я могу себе представить, что больше людей столкнутся с этой же проблемой.
ответ в этом посте работает для меня.
я использую 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>
мое решение заключается в использовании замены заполнителя в
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.