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

还是可以使用Gradle直接修改Manifest?始终欢迎任何其他有关如何在一个设备上与ContentProvider运行同一应用程序的解决方案。


对于那些有兴趣跟踪此用例的上游支持的人员:AOSP错误报告。当前的“官方”立场是使用清单覆盖解决方案
desseim 2014年

Answers:


226

现有的答案都没有一个令我满意,但是自由很接近。所以这就是我的做法。目前,我正在与以下人员合作:

  • Android Studio Beta 0.8.2
  • Gradle插件0.12+
  • 摇篮1.12

我的目标是在同一设备上使用同一版本运行Debug版本。ReleaseContentProvider


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

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

在您的AndroidManifest.xml文件的set 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 Beta 0.9.2
  • Gradle插件0.14.1
  • 摇篮2.1

基本上,如果您需要为不同的构建自定义一些值,则可以从build.gradle文件中进行:

  • 使用buildConfigFieldBuildConfig.java类访问它
  • 使用resValue从资源中访问它,例如@ string / your_value

作为资源的替代方法,您可以创建单独的buildType或flavor目录,并覆盖其中的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 / genic.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
最好的答案恕我直言。不错的简短示例。
2014年

是的,这是到目前为止我所看到的最好的解决方法。非常感谢您的分享!仍然存在与此无关的另一个问题,因为我需要更新preferences.xml文件中的显式Intent以使用新的程序包名称。code.google.com/p/android/issues/detail?id=57460
Bernd S

@BerndS我对您的解决方案问题发表了评论。您需要了解,通过替换applicationId或设置后缀来更改applicationId不会影响Java包。它只是您的应用程序的标识符,已与Java包分离。见我回答另一个问题stackoverflow.com/questions/24178007/...
达米安Petla

1
@JJD您链接到的修改无需任何自定义构建脚本即可起作用。如果要将$ {applicationId}占位符用于sync_adapter.xml,authenticator.xml,则必须自定义build.gradle脚本。我看到您已经在build.gradle脚本中做了很多事情,因此您对这个想法感到满意。您是否按照我的回答中的说明进行操作,仍然无法正常工作?
Rob Meeuwisse 2014年

1
我已经更新了同步器操作方法的答案
Damian Petla 2014年

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:

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

唯一的问题是,如果两个APK都使用相同的权限公开了ContentProvider,则您将无法同时安装这两个APK。从逻辑上讲,我们需要根据当前的构建类型重命名权限...……但是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


1
对于无法运行gradle的人,可导致sintaxy错误。下面是答案:stackoverflow.com/questions/20678118/...
雷南弗兰卡

23

尽管只有少量构建类型,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.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 2013年

2
FileWriter至少在我的Mac OS上对utf-8文件造成麻烦。我将相关行更改为:def writer = new OutputStreamWriter(new FileOutputStream(pathToFile),“ UTF-8”)
Reza Mohammadi

真的很棒,谢谢!我做了一些小的更改以防止格式化的字符串损坏。gist.github.com/paour/8475929
Pierre-Luc Paour 2014年

这非常有帮助,但是我遇到了一个问题,即在干净之后它无法生成,因为在processManifest阶段的build文件夹中没有values.xml文件。直到processResources阶段才存在该功能,此时修改清单已经为时过晚,因此要在清单文件和值文件中都替换.res-auto,我认为您需要2个函数,其中一个由variant调用。 processManifest.doLast,另一个由variant.processResources.doLast调用。
Niall 2014年

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/...
皮埃尔-吕克·Paour

感谢您的澄清。
rciovati

1
这在android:searchSuggestAuthority的searchable.xml中也非常有用,因为您不能在其中使用$ {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>元素来说,这似乎并不是一个完整的解决方案,这些元素需要引用规则,因为这些都不是清单的一部分(但是现有的合并策略确实适用于这些文件,与清单不同)。
皮埃尔·卢克(Paul-Luc Paour)2014年

1
您不必为此使用口味,它也适用于构建类型。另外,很高兴提到您可以使用BuildConfig.PACKAGE_NAME来获取对包的静态引用。这对于需要在运行时知道权限来查询内容提供者的内容提供者很有用。
Matt Wolfe 2014年

1
对于android:authorities,也应更新为使用$ {applicationId}而不是$ {packageName}
Bernd S

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 /文件,例如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 /目录中的占位符。


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>

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;

4

基于@ChristianMelchior的样本,这是我的解决方案,它解决了先前解决方案中的两个问题:

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

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

这个新的解决方案通过创建新任务,使Gradle的工作方式更加有效,并通过定义输入和输出文件来实现增量构建。

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

  2. 这个要点build_extras.gradle文件添加到您的项目中,并通过在块上方添加一些内容从主引用它build.gradleapply 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 Library模块。我可以从命令行进行构建,但是当我尝试从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 2014年

1
解决了我自己的问题。Gradle似乎使用解析路径System.getProperty("user.dir"),当Android Studio版本调用时会返回不同的结果。解决方案是使用相对于项目目录的路径,该路径以返回gradle.startParameter.getProjectDir()。也可以在Paour的链接要点中查看我的评论。
user1978019 2014年

当心,资源型机关只能在Android 2.2.1及更高版本:github.com/android/platform_frameworks_base/commit/...
皮埃尔-吕克·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 2013年

2

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

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

我使用3种不同的口味,因此我按照kevinrschultz所说的每种口味用内容提供商创建了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>


0

我的解决方案是在中使用占位符替换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解析方法更优雅的方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.