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

Man*_*asV 121 android gradle android-contentprovider android-gradle-plugin

我已经设置Gradle将包名称后缀添加到我的调试应用程序中,因此我可以使用我正在使用的发行版本并在一部手机上调试版本.我参考了这个:http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

我的build.gradle文件如下所示:

...
android
{
    ...
    buildTypes
    {
        debug
        {
            packageNameSuffix ".debug"
            versionNameSuffix " debug"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

一切正常,直到我开始在我的应用程序中使用ContentProvider.我明白了:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]
Run Code Online (Sandbox Code Playgroud)

我知道这是因为两个应用程序(发布和调试)正在注册相同的ContentProvider权限.

我认为有一种可能来解决这个问题.如果我理解正确,您应该能够指定在构建时使用的不同文件.然后我应该能够将不同的权限放在不同的资源文件中(并从Manifest设置权限作为字符串资源)并告诉Gradle使用不同的资源进行调试构建.那可能吗?如果是,那么关于如何实现这一点的任何提示都会很棒!

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

Dam*_*tla 221

现有的答案都没有让我满意,但是Liberty很接近.所以我就是这样做的.首先,我正在与之合作:

  • Android Studio Beta 0.8.2
  • Gradle插件0.12.+
  • Gradle 1.12

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


在Debug版本的app set set后缀的build.gradle中:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}
Run Code Online (Sandbox Code Playgroud)

在您的AndroidManifest.xml文件集android:authorities属性中ContentProvider:

<provider
    android:name="com.example.app.YourProvider"
    android:authorities="${applicationId}.provider"
    android:enabled="true"
    android:exported="false" >
</provider>
Run Code Online (Sandbox Code Playgroud)

在您的代码AUTHORITY属性中,可以在您的实现中的任何需要的地方使用:

public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
Run Code Online (Sandbox Code Playgroud)

提示:之前BuildConfig.PACKAGE_NAME

而已!它会像魅力一样工作.如果您使用SyncAdapter,请继续阅读!


SyncAdapter更新(14.11.2014)

我将再次从当前的设置开始:

  • Android Studio Beta 0.9.2
  • Gradle插件0.14.1
  • Gradle 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"'
    }
}
Run Code Online (Sandbox Code Playgroud)

您将在BuildConfig.java类中看到结果

public static final String ACCOUNT_TYPE = "your.syncadapter.type.debug";
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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"
/>
Run Code Online (Sandbox Code Playgroud)

在您的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"
        />
Run Code Online (Sandbox Code Playgroud)

提示:自动完成(Ctrl + Space)不适用于这些生成的资源,因此您必须手动键入它们

  • 恕我直言的最佳答案.简短而简单的例子. (7认同)

Cyr*_*ier 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"
       }
   }
}
Run Code Online (Sandbox Code Playgroud)

使用这样的Gradle配置,您可以组合两个不同的APK:

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

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

首先,我们需要将提供程序Android清单ContentProvider声明移动到适当的构建类型.为了做到这一点,我们将简单地:

SRC /调试/ AndroidManifest.xml中

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

   <application>

       <provider
           android:name=".provider.Provider1"
           android:authorities="com.cyrilmottier.android.app.debug.provider"
           android:exported="false" />

   </application>
</manifest>
Run Code Online (Sandbox Code Playgroud)

SRC /发行/ AndroidManifest.xml中

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

   <application>

       <provider
           android:name=".provider.Provider1"
           android:authorities="com.cyrilmottier.android.app.provider"
           android:exported="false" />

   </application>
</manifest>
Run Code Online (Sandbox Code Playgroud)

确保从src/main /中的AndroidManifest.xml中删除ContentProvider声明,因为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
       }
   }
}
Run Code Online (Sandbox Code Playgroud)

由于这种解决方法,您将能够在ProviderContract中使用BuildConfig.PROVIDER_AUTHORITY并同时安装两个不同版本的应用程序.


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


Chr*_*ior 23

虽然Cyril的例子很有用,如果你只有一些构建类型,如果你有许多构建类型和/或产品风格,你需要维护许多不同的AndroidManifest.xml,它会很快变得复杂.

我们的项目包含3种不同的构建类型和6种风格,总共18种构建变体,因此我们在ContentProvider权限中添加了对".res-auto"的支持,扩展到当前的packagename并且无需维护不同的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)
    }
}
Run Code Online (Sandbox Code Playgroud)

示例代码可以在这里找到:https://gist.github.com/cmelchior/6988275

  • FileWriter在utf-8文件上遇到麻烦,至少在我的Mac OS上是这样.我将相关行更改为:def writer = new OutputStreamWriter(new FileOutputStream(pathToFile),"UTF-8") (2认同)

rci*_*ati 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"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

  • 请注意,基于资源的权限仅适用于Android 2.2.1及更高版本:https://github.com/android/platform_frameworks_base/commit/cf244ada58539ce857ec041d7288d0271204fbb6 (2认同)

Lib*_*rty 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" />
Run Code Online (Sandbox Code Playgroud)

在build.gradle中,您可以:

productFlavors {
    free {
        packageName "org.pkg1"
    }
    pro {
        packageName "org.pkg2"
    }
}
Run Code Online (Sandbox Code Playgroud)

请参阅此处的完整示例:https: //code.google.com/p/anymemo/source/browse/AndroidManifest.xml#152

在这里:https: //code.google.com/p/anymemo/source/browse/build.gradle#41


Rob*_*sse 8

${applicationId}在xml和BuildConfig.APPLICATION_ID代码中使用占位符.

您需要扩展构建脚本以在清单以外的xml文件中启用占位符.您可以使用每个构建变体的源目录来提供不同版本的xml文件,但维护很快就会变得很麻烦.

AndroidManifest.xml中

您可以在清单中使用开箱即用的applicationId占位符.像这样声明你的提供者:

<provider
    android:name=".provider.DatabaseProvider"
    android:authorities="${applicationId}.DatabaseProvider"
    android:exported="false" />
Run Code Online (Sandbox Code Playgroud)

注意${applicationId}一下.这在构建时被替换为正在构建的构建变体的实际applicationId.

在代码中

您的ContentProvider需要在代码中构造权限字符串.它可以使用BuildConfig类.

public class DatabaseContract {
    /** The authority for the database provider */
    public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".DatabaseProvider";
    // ...
}
Run Code Online (Sandbox Code Playgroud)

注意BuildConfig.APPLICATION_ID一下.它是一个生成的类,其中包含正在构建的构建变体的实际applicationId.

res/xml/files,例如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" />
Run Code Online (Sandbox Code Playgroud)

再说一遍,注意${applicationId}.这仅适用于将以下gradle脚本添加到模块的根目录并从build.gradle应用它的情况.

的build.gradle

从模块build.gradle脚本应用额外的构建脚本.一个好地方在Android gradle插件下面.

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

android {
    compileSdkVersion 21
    // etc.
Run Code Online (Sandbox Code Playgroud)

集结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)
    }
}
Run Code Online (Sandbox Code Playgroud)

strings.xml中

在我看来,没有必要为资源字符串添加占位符支持.对于上述用例,至少不需要它.但是,您可以轻松地将脚本更改为不仅替换res/xml /目录中的占位符,还替换res/values /目录中的占位符.


ica*_*ell 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"
        }
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

而且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>
Run Code Online (Sandbox Code Playgroud)


mar*_*136 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 ]
    }
}
Run Code Online (Sandbox Code Playgroud)

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" />
Run Code Online (Sandbox Code Playgroud)

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"/>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

码:

public static final String CONTENT_AUTHORITY = BuildConfig.CONTENT_AUTHORITY;
public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
Run Code Online (Sandbox Code Playgroud)