在 Gradle 中使用构建类型在一台设备上运行使用 ContentProvider 的相同应用程序

Posted

技术标签:

【中文标题】在 Gradle 中使用构建类型在一台设备上运行使用 ContentProvider 的相同应用程序【英文标题】:Using build types in Gradle to run same app that uses ContentProvider on one device 【发布时间】:2013-05-22 13:37:11 【问题描述】:

我已设置 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 使用不同的资源进行调试构建。那可能吗?如果是,那么任何关于如何实现这一目标的提示都会很棒!

或者也许可以使用 Gradle 直接修改 Manifest?任何其他关于如何在一台设备上运行与 ContentProvider 相同的应用程序的解决方案都是受欢迎的。

【问题讨论】:

对于那些有兴趣跟踪此用例的上游支持的人:AOSP bug report。 “官方”目前的立场是使用the manifest overriding solution。 【参考方案1】:

现有的答案都没有让我满意,但 Liberty 已经很接近了。所以这就是我的做法。 首先,目前我正在使用:

Android Studio 测试版 0.8.2 Gradle 插件 0.12.+ Gradle 1.12

我的目标是在使用相同ContentProvider的同一设备上运行Debug版本和Release版本。


在您的应用集后缀的 build.gradle 中用于调试构建:

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 文件中完成:

使用 buildConfigFieldBuildConfig.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 中再次使用相同的资源,并且 @string/authorities

<?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)不适用于这些生成的资源,因此您必须手动输入它们

【讨论】:

最好的答案恕我直言。不错的简短示例。 是的,这是迄今为止我见过的最好的解决方法。非常感谢您的分享!还有另一个与此无关的问题,因为我需要更新preferences.xml 文件中的显式Intent 以使用新的包名称。 code.google.com/p/android/issues/detail?id=57460 @BerndS 我已对您的解决方案问题发表了评论。您需要了解通过替换或设置后缀来更改 applicationId 不会影响 java 包。它只是您的应用程序的标识符,它与 java 包分离。查看我对另一个问题的回答***.com/questions/24178007/… @JJD 您链接到的修改将在没有任何自定义构建脚本的情况下工作。如果您想为 sync_adapter.xml、authenticator.xml 使用 $applicationId 占位符,您必须自定义您的 build.gradle 脚本。我看到您已经在 build.gradle 脚本中做了很多工作,所以您对这个想法感到满意。您是否按照我的answer 中的说明进行操作,仍然无法正常工作? 我已经用 syncadapter 的操作方法更新了我的答案【参考方案2】:

新的 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:

• 带有 com.cyrilmottier.android.app.debug 包名称的调试 APK • 带有 com.cyrilmottier.android.app 包名称的发布 APK

唯一的问题是,如果两个 APK 都公开具有相同权限的 ContentProvider,您将无法同时安装它们。从逻辑上讲,我们需要根据当前的构建类型重命名权限……但这不受 Gradle 构建系统的支持(但是?……我相信它很快就会修复)。所以这里有一个方法:

首先,我们需要将提供程序 Android 清单 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>

确保从 src/main/ 中的 AndroidManifest.xml 中删除 ContentProvider 声明,因为 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
       
   

借助此解决方法,您将能够在 ProviderContract 中使用 BuildConfig.PROVIDER_AUTHORITY 并同时安装您的应用的两个不同版本。


最初在 Google+ 上: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ

【讨论】:

对于无法运行 gradle 的人,由于 sintaxy 错误。这是答案:***.com/questions/20678118/…【参考方案3】:

虽然如果您只有几种构建类型,Cyril 的示例效果很好,但如果您有多种构建类型和/或产品风格,那么它很快就会变得复杂,因为您需要维护许多不同的 AndroidManifest.xml。

我们的项目包含 3 种不同的构建类型和 6 种风格,总共 18 种构建变体,因此我们在 ContentProvider 权限中添加了对“.res-auto”的支持,它扩展到当前的包名并消除了维护不同 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.findAllit.name() == 'string'.eachitem ->
        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

【讨论】:

我也切换到在我的项目中使用非常相似的东西,因为我在构建风格方面遇到了同样的问题。这种方法目前效果很好。 FileWriter 在 utf-8 文件上出现问题,至少在我的 Mac OS 上是这样。我将相关行更改为: def writer = new OutputStreamWriter(new FileOutputStream(pathToFile), "UTF-8") 这真的很棒,谢谢!我做了一个小改动,以防止格式化字符串损坏。 gist.github.com/paour/8475929 这很有帮助,但我遇到了一个问题,即它在清理后无法构建,因为在 processManifest 阶段的构建文件夹中没有 values.xml 文件。直到 processResources 阶段才存在,此时修改清单为时已晚,因此要在清单和值文件中替换 .res-auto ,我认为您需要 2 个函数,一个由变体调用。 processManifest.doLast,另一个由variant.processResources.doLast调用。【参考方案4】:

由于插件版本 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>

【讨论】:

请注意,基于资源的权限仅适用于 Android 2.2.1 及更高版本:github.com/android/platform_frameworks_base/commit/… 感谢您的澄清。 这在 android:searchSuggestAuthority 的 searchable.xml 中也非常有用,因为你不能使用 $applicationId【参考方案5】:

不知道有没有人提到。其实在android gradle plugin 0.10+之后,manifest 合并会提供官方对这个功能的支持: 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

【讨论】:

这是个好消息,但对于需要引用权限的 元素来说,这似乎不是一个完整的解决方案,因为它们不是清单的一部分(但存在与 Manifest 不同,合并策略确实适用于这些文件)。 您不必为此使用风味,它也适用于构建类型。另外,很高兴提到您可以使用 BuildConfig.PACKAGE_NAME 来获取对您的包的静态引用。这对于需要在运行时知道权限以查询内容提供者的内容提供者很有用。 还应该更新为使用 $applicationId 而不是 $packageName 用于 android:authorities【参考方案6】:

在 xml 中使用 $applicationId 占位符,在代码中使用 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/ 文件,例如syncadapter.xml、accountauthenticator.xml

如果您想使用同步适配器,您需要在 res/xml/ 目录中的 xml 文件中为 ContentProvider 和 AccountManager 提供元数据。此处不支持 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.

build-processApplicationId.gradle

以下是 res/xml/ 占位符构建脚本的工作源代码。 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/ 目录中的占位符。

【讨论】:

【参考方案7】:

我更喜欢 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>

【讨论】:

【参考方案8】:

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 ]
    

身份验证器.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;

【讨论】:

【参考方案9】:

根据@ChristianMelchior 的示例,这是我的解决方案,它修复了之前解决方案中的两个问题:

在构建目录中更改 values.xml 的解决方案会导致资源完全重建(包括所有可绘制对象的 aapt)

由于未知原因,IntelliJ(可能还有 Android Studio)无法可靠地处理资源,导致构建包含未替换的 .res-auto 提供者权限

这个新的解决方案通过创建一个新任务来做更多的 Gradle 方式,并允许通过定义输入和输出文件来进行增量构建。

    创建一个文件(在示例中我将其放在variants 目录中),其格式类似于资源 xml 文件,其中包含字符串资源。这些将被合并到应用程序的资源中,并且值中出现的任何 .res-auto 都将替换为变体的包名称,例如 &lt;string name="search_provider"&gt;.res-auto.MySearchProvider&lt;/string&gt;

    build_extras.gradle 文件从this gist 添加到您的项目,并通过在android 块上方的某处添加apply from: './build_extras.gradle' 从主build.gradle 引用它

    确保通过将默认包名称添加到 build.gradleandroid.defaultConfig 块中来设置默认包名称

    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 及更高版本。对于早期的平台,请参阅this answer,它有自己的一系列问题,因为新的清单合并仍然非常粗糙......

【讨论】:

您将变体目录放在哪里?我有一个大型 Android Studio 项目,它依赖于几个 Android 模块——我的主应用程序和几个 Android 库模块。我可以从命令行构建,但是当我尝试从 Android Studio 内部构建时,它会查找相对于 /Applications/Android Studio.app/bin/variants/res-auto-values.xml。即我没有得到 /Applications/Android Studio.app/bin/variants/res-auto-values.xml 的 FileNotFoundException。我在mac上运行。这是一个很好的解决方案,但我很乐意让它在 IDE 中为团队的其他成员工作。 修复了我自己的问题。 Gradle 似乎使用 System.getProperty("user.dir") 解析路径,当被 Android Studio 构建调用时,它会返回不同的结果。解决方案是使用相对于项目目录的路径,返回gradle.startParameter.getProjectDir()。也可以在 Paour 的链接要点中查看我的评论。 请注意,基于资源的权限仅适用于 Android 2.2.1 及更高版本:github.com/android/platform_frameworks_base/commit/…【参考方案10】:

我用 Github 示例项目写了一篇博文,该项目以与 Cyril 略有不同的方式解决了这个问题(以及其他类似问题)。

http://brad-android.blogspot.com/2013/08/android-gradle-building-unique-build.html

【讨论】:

【参考方案11】:

不幸的是,android 插件的当前版本(0.4.1)似乎没有为此提供好的解决方案。我还没有时间尝试这个,但是解决这个问题的一个可能的解决方法是使用字符串资源@string/provider_authority,并在清单中使用它:android:authority="@string/provider_authority"。然后,您在每个构建类型的 res 文件夹中都有一个 res/values/provider.xml,应该覆盖权限,在您的情况下,这将是 src/debug/res

我已经研究过即时生成 xml 文件,但同样,在当前版本的插件中似乎没有任何好的钩子。不过,我建议您提出功能请求,我可以想象会有更多人遇到同样的问题。

【讨论】:

嗨,马库斯,感谢您的回复。您建议的解决方案是我现在唯一能想到的解决方案。但我的问题是,我不知道如何使用 Gradle 来实现。【参考方案12】:

这篇文章中的答案对我有用。

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>

【讨论】:

【参考方案13】:

为什么不直接添加这个?

type.packageNameSuffix = ".$type.name"

【讨论】:

【参考方案14】:

我的解决方案是在AndroidManifest.xml 中使用占位符替换。它还处理 packageNameSuffix 属性,因此您可以在同一设备上拥有 debugrelease 以及任何其他自定义构建。

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 解析方法更优雅的方法。

【讨论】:

以上是关于在 Gradle 中使用构建类型在一台设备上运行使用 ContentProvider 的相同应用程序的主要内容,如果未能解决你的问题,请参考以下文章

“App not not installed”在一台设备上

xmpp 存在仅显示在一台设备上

iOS 不会在一台设备上请求 AASA 文件,但适用于所有其他设备

Firebase 通知仅出现在一台设备上

4.4Android Studio在命令行运行Gradle

4.4Android Studio在命令行运行Gradle