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

后端 未结 14 947
小蘑菇
小蘑菇 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:41

    Since the plugin version 0.8.3 (actually 0.8.1 but it wasn't working properly) you can define resources within the build file so this could be a cleaner solution because you don't need to create strings files nor additional debug/release folders.

    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>
    
    0 讨论(0)
  • 2020-11-28 00:44

    I've written a blogpost with Github sample project that tackles this problem (and other similar problems) in a slightly different way than Cyril's.

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

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

    I would rather prefer a mixture between Cyril and rciovati. I think is more simplier, you only have two modifications.

    The build.gradle looks like:

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

    And the 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>
    
    0 讨论(0)
  • 2020-11-28 00:48

    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>
    

    Code:

    public static final String CONTENT_AUTHORITY = BuildConfig.CONTENT_AUTHORITY;
    public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
    
    0 讨论(0)
  • 2020-11-28 00:49

    The answer in this post works for me.

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

    I use 3 different flavours so I create 3 manifest with content provider in each flavour as kevinrschultz said:

    productFlavors {
        free {
            packageName "your.package.name.free"
        }
    
        paid {
            packageName "your.package.name.paid"
        }
    
        other {
            packageName "your.package.name.other"
        }
    }
    

    Your main Manifest not include providers:

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

    And your manifest in your each flavour including provider.

    Free:

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

    Paid:

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

    Other:

    <?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 讨论(0)
  • 2020-11-28 00:51

    Use ${applicationId} placeholders in xml and BuildConfig.APPLICATION_ID in code.

    You will need to extend the build script to enable placeholders in xml files other than the manifest. You could use a source directory per build variant to provide different versions of the xml files but maintenance will become cumbersome very quickly.

    AndroidManifest.xml

    You can use the applicationId placeholder out of the box in the manifest. Declare your provider like this:

    <provider
        android:name=".provider.DatabaseProvider"
        android:authorities="${applicationId}.DatabaseProvider"
        android:exported="false" />
    

    Note the ${applicationId} bit. This is replaced at build time with the actual applicationId for the build variant that is being built.

    In code

    Your ContentProvider needs to construct the authority string in code. It can use the BuildConfig class.

    public class DatabaseContract {
        /** The authority for the database provider */
        public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".DatabaseProvider";
        // ...
    }
    

    Note the BuildConfig.APPLICATION_ID bit. It is a generated class with the actual applicationId for the build variant being built.

    res/xml/ files, e.g. syncadapter.xml, accountauthenticator.xml

    If you want to use a Sync Adapter you will need to provide meta-data for the ContentProvider and AccountManager in xml files in the res/xml/ directory. The applicationId placeholder is not supported here. But you can extend the build script yourself to hack it in.

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

    Again, note the ${applicationId}. This only works if you add the below gradle script to the root of your module and apply it from build.gradle.

    build.gradle

    Apply the extra build script from the module build.gradle script. A good place is below the Android gradle plugin.

    apply plugin: 'com.android.application'
    apply from: './build-processApplicationId.gradle'
    
    android {
        compileSdkVersion 21
        // etc.
    

    build-processApplicationId.gradle

    Below is working source for a res/xml/ placeholder build script. A better documented version is available on github. Improvements and extensions are welcome.

    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

    In my opinion there is no need to add placeholder support for resource strings. For the above use case at least it is not needed. However you could easily change the script to not only replace placeholders in the res/xml/ directory, but also in the res/values/ directory.

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