Using build types in Gradle to run same app that uses ContentProvider on one device

后端 未结 14 948
小蘑菇
小蘑菇 2020-11-28 00:22

I have set up Gradle to add package name suffix to my debug app so I could have release version that I\'m using and debug version on one phone. I was referencing this: http:

相关标签:
14条回答
  • 2020-11-28 00:54

    None of existing answers satisfied me, however Liberty was close. So this is how am I doing it. First of all at the moment I am working with:

    • Android Studio Beta 0.8.2
    • Gradle plugin 0.12.+
    • Gradle 1.12

    My goal is to run Debug version along with Release version on the same device using the same ContentProvider.


    In build.gradle of your app set suffix for Debug build:

    buildTypes {
        debug {
            applicationIdSuffix ".debug"
        }
    }
    

    In AndroidManifest.xml file set android:authorities property of your ContentProvider:

    <provider
        android:name="com.example.app.YourProvider"
        android:authorities="${applicationId}.provider"
        android:enabled="true"
        android:exported="false" >
    </provider>
    

    In your code set AUTHORITY property that can be used wherever needed in your implementation:

    public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
    

    Tip: Before it was BuildConfig.PACKAGE_NAME

    That's it! It will work like a charm. Keep reading if you use SyncAdapter!


    Update for SyncAdapter (14.11.2014)

    Once again I will start with my current setup:

    • Android Studio Beta 0.9.2
    • Gradle plugin 0.14.1
    • Gradle 2.1

    Basically, if you need to customise some values for different builds you can do it from the build.gradle file:

    • use buildConfigField to access it from the BuildConfig.java class
    • use resValue to access it from resources e.g. @string/your_value

    As an alternative for resources, you can create separate buildType or flavour directories and override XMLs or values within them. However, I am not going to use it in example below.

    Example


    In build.gradle file add the following:

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

    You will see results in BuildConfig.java class

    public static final String ACCOUNT_TYPE = "your.syncadapter.type.debug";
    

    and in 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>
    

    In your authenticator.xml use resource specified in build.gradle file

    <?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"
    />
    

    In your syncadapter.xml use the same resource again and @string/authorities too

    <?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"
            />
    

    Tip: autocompletion(Ctrl+Space) does not work for these generated resource so you have to type them manually

    0 讨论(0)
  • 2020-11-28 00:54

    Based on the sample by @ChristianMelchior, here's my solution, which fixes two issues in the previous solutions:

    • solutions that change values.xml in the build directory cause a full rebuild of resources (including aapt of all drawables)

    • for an unknown reason, IntelliJ (and probably Android Studio) do not reliably process the resources, causing the build to contain un-replaced .res-auto provider authorities

    This new solution does things more the Gradle way by creating a new task and allows for incremental builds by defining input and output files.

    1. create a file (in the example I put it in a variants directory), formatted like a resource xml file, which contains string resources. These will be merged into the app's resources, and any occurrence of .res-auto in the values will be replaced with the variant's package name, for example <string name="search_provider">.res-auto.MySearchProvider</string>

    2. add the build_extras.gradle file from this gist to your project and reference it from the main build.gradle by adding apply from: './build_extras.gradle' somewhere above the android block

    3. make sure you set a default package name by adding it to the android.defaultConfig block of build.gradle

    4. in AndroidManifest.xml and other configuration files (such as xml/searchable.xml for auto-completion search providers), reference the provider (for example @string/search_provider)

    5. if you need to get the same name, you can use the BuildConfig.PACKAGE_NAME variable, for example BuildConfig.PACKAGE_NAME + ".MySearchProvider"

    https://gist.github.com/paour/9189462


    Update: this method only works on Android 2.2.1 and later. For earlier platforms, see this answer, which has its own set of problems, since the new manifest merger is still very rough around the edges…

    0 讨论(0)
  • 2020-11-28 00:57

    I don't know if anybody mention it. Actually after android gradle plugin 0.10+, the manifest merger will provide the official support for this function: http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger

    In AndroidManifest.xml, you can use ${packageName} like this:

    <provider
        android:name=".provider.DatabasesProvider"
        android:authorities="${packageName}.databasesprovider"
        android:exported="true"
        android:multiprocess="true" />
    

    And in your build.gradle you can have:

    productFlavors {
        free {
            packageName "org.pkg1"
        }
        pro {
            packageName "org.pkg2"
        }
    }
    

    See full example here: https://code.google.com/p/anymemo/source/browse/AndroidManifest.xml#152

    and here: https://code.google.com/p/anymemo/source/browse/build.gradle#41

    0 讨论(0)
  • 2020-11-28 00:58

    While Cyril's example works great if you only have a few build types, it quickly gets complicated if you have many build types and/or product flavors as you need to maintain lots of different AndroidManifest.xml's.

    Our project consists of 3 different build types and 6 flavors totaling 18 build variants, so instead we added support for ".res-auto" in ContentProvider authorities, which expand to the current packagename and removes the need to maintain different 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)
        }
    }
    

    Example code can be found here: https://gist.github.com/cmelchior/6988275

    0 讨论(0)
  • 2020-11-28 00:58

    Unfortunately, the current version (0.4.1) of the android plugin doesn't seem to provide a good solution for this. I haven't had time to try this yet, but a possible workaround for this problem would be to use a string resource @string/provider_authority, and use that in the manifest: android:authority="@string/provider_authority". You then have a res/values/provider.xml in the res folder of each build type that should override the authority, in your case this would be src/debug/res

    I've looked into generating the xml file on the fly, but again, there doesn't seem to be any good hooks for it in the current version of the plugin. I'd recommend putting in a feature request though, I can imagine more people will run into this same issue.

    0 讨论(0)
  • 2020-11-28 00:58

    Why not just add this?

    type.packageNameSuffix = ".$type.name"

    0 讨论(0)
提交回复
热议问题