Create Free/Paid versions of Application from same code

后端 未结 7 1387
Happy的楠姐
Happy的楠姐 2020-11-30 18:36

So I\'m coming down to release-time for my application. We plan on releasing two versions, a free ad-based play-to-unlock version, and a paid fully unlocked version. I have

7条回答
  •  清歌不尽
    2020-11-30 18:58

    The best way is to use "Android Studio" -> gradle.build -> [productFlavors + generate manifest file from template]. This combination allows to build free/paid versions and bunch of editions for different app markets from one source.


    This is a part of templated manifest file:


    
    
        
            
                
                
            
        
    
    

    This is template "ProductInfo.template" for java file: ProductInfo.java


        package com.packagename.generated;
        import com.packagename.R;
        public class ProductInfo {
            public static final boolean mIsPaidVersion = {f:PAID}true{/f}{f:FREE}false{/f};
            public static final int mAppNameId = R.string.app_name_{f:PAID}paid{/f}{f:FREE}free{/f};
            public static final boolean mIsDebug = {$DEBUG};
        }
    

    This manifest is processed by gradle.build script with productFlavors and processManifest task hook:


    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    import org.gradle.api.DefaultTask
    import org.gradle.api.tasks.TaskAction  
    ...
    
    android {
        ...
        productFlavors {
            free {
                packageName 'com.example.product.free'
            }
            paid {
                packageName 'com.example.product.paid'
            }
        }
        ...
    }
    
    afterEvaluate { project ->
        android.applicationVariants.each { variant ->
    
            def flavor = variant.productFlavors[0].name
    
            tasks['prepare' + variant.name + 'Dependencies'].doLast {
                println "Generate java files..."
    
                //Copy templated and processed by build system manifest file to filtered_manifests forder
                def productInfoPath = "${projectDir}/some_sourcs_path/generated/"
                copy {
                    from(productInfoPath)
                    into(productInfoPath)
                    include('ProductInfo.template')
                    rename('ProductInfo.template', 'ProductInfo.java')
                }
    
                tasks.create(name: variant.name + 'ProcessProductInfoJavaFile', type: processTemplateFile) {
                    templateFilePath = productInfoPath + "ProductInfo.java"
                    flavorName = flavor
                    buildTypeName = variant.buildType.name
                }   
                tasks[variant.name + 'ProcessProductInfoJavaFile'].execute()
            }
    
            variant.processManifest.doLast {
                println "Customization manifest file..."
    
                // Copy templated and processed by build system manifest file to filtered_manifests forder
                copy {
                    from("${buildDir}/manifests") {
                        include "${variant.dirName}/AndroidManifest.xml"
                    }
                    into("${buildDir}/filtered_manifests")
                }
    
                tasks.create(name: variant.name + 'ProcessManifestFile', type: processTemplateFile) {
                    templateFilePath = "${buildDir}/filtered_manifests/${variant.dirName}/AndroidManifest.xml"
                    flavorName = flavor
                    buildTypeName = variant.buildType.name
                }
                tasks[variant.name + 'ProcessManifestFile'].execute()
    
            }
            variant.processResources.manifestFile = file("${buildDir}/filtered_manifests/${variant.dirName}/AndroidManifest.xml")
        }
    }
    

    This is separated task to process file


    class processTemplateFile extends DefaultTask {
        def String templateFilePath = ""
        def String flavorName = ""
        def String buildTypeName = ""
    
        @TaskAction
        void run() {
            println templateFilePath
    
            // Load file to memory
            def fileObj = project.file(templateFilePath)
            def content = fileObj.getText()
    
            // Flavor. Find "{f:}...{/f}" pattern and leave only "==flavor"
            def patternAttribute = Pattern.compile("\\{f:((?!${flavorName.toUpperCase()})).*?\\{/f\\}",Pattern.DOTALL);
            content = patternAttribute.matcher(content).replaceAll("");
    
            def pattern = Pattern.compile("\\{f:.*?\\}");
            content = pattern.matcher(content).replaceAll("");
            pattern = Pattern.compile("\\{/f\\}");
            content = pattern.matcher(content).replaceAll("");
    
            // Build. Find "{$DEBUG}" pattern and replace with "true"/"false"
            pattern = Pattern.compile("\\{\\\$DEBUG\\}", Pattern.DOTALL);
            if (buildTypeName == "debug"){ 
                content = pattern.matcher(content).replaceAll("true");
            }
            else{
                content = pattern.matcher(content).replaceAll("false");
            }
    
            // Save processed manifest file
            fileObj.write(content)
        }
    }
    

    Updated: processTemplateFile created for code reusing purposes.

提交回复
热议问题