从相同的代码创建应用程序的免费/付费版本

Lef*_*nia 53 android

所以我要下载我的申请的发布时间.我们计划发布两个版本,一个免费的基于广告的播放解锁版本和一个付费完全解锁版本.我设置了代码,我可以在启动时设置一个标志来启用/禁用广告并锁定/解锁所有功能.因此字面上只有一行代码在这些版本之间执行不同.

为了发布两个单独的应用程序,它们需要不同的包名称,所以我的问题是:是否有一种简单的方法来重构我的应用程序的包名称?Eclipse的重构工具无法解析生成的R文件或布局和清单文件中的任何XML引用.我试图使用原始资源创建一个新项目,但我无法引用资产和资源,我希望避免重复我的任何代码和资产.手动重构它不是一个巨大的痛苦,但我觉得必须有一个更好的方法来做到这一点.有人有一个优雅的解决方案吗?

编辑/回答:

对于我的情况,我发现只使用Project - > Android Tools - > Rename Application Package是完全可以接受的.我不知道这存在,我觉得现在发布这个就是个白痴.感谢大家的回答和评论,请随意投票.

Den*_*nis 34

在Android Studio中使用build.gradle非常简单.阅读productFlavors.这是一个非常有用的功能.只需在build.gradle中添加以下行:

productFlavors {
    lite {
        packageName = 'com.project.test.app'
        versionCode 1
        versionName '1.0.0'
    }
    pro {
        packageName = 'com.project.testpro.app'
        versionCode 1
        versionName '1.0.0'
    }
}
Run Code Online (Sandbox Code Playgroud)

在这个例子中,我添加了两种产品口味:第一种用于精简版,第二种用于完整版.每个版本都有自己的versionCode和versionName(适用于Google Play出版物).

在代码中只需检查BuildConfig.FLAVOR:

if (BuildConfig.FLAVOR == "lite") {
   // add some ads or restrict functionallity

}
Run Code Online (Sandbox Code Playgroud)

要在设备上运行和测试,请使用Android Studio中的"Build Variants"选项卡在版本之间切换: 在此输入图像描述

  • 如何消除付费版本对广告的依赖? (2认同)
  • "packageName"现在是"applicationId" (2认同)

mme*_*yer 17

可能是批量发布Android应用程序的副本.

Android库项目将很好地为您完成此任务.您将最终得到1个库项目,然后是每个版本的项目(免费/完整),其中包含不同的资源,如应用程序图标和不同的清单,这是包名称的变化.

希望有所帮助.它对我来说效果很好.


gal*_*lex 8

最好的方法是使用"Android Studio" - > gradle.build - > [productFlavors +从模板生成清单文件].这种组合允许从一个来源为不同的应用程序市场构建免费/付费版本和一系列版本.


这是模板化清单文件的一部分:


<manifest android:versionCode="1" android:versionName="1" package="com.example.product" xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@drawable/ic_launcher"  
    android:label="@string/{f:FREE}app_name_free{/f}{f:PAID}app_name_paid{/f}"
    android:name=".ApplicationMain" android:theme="@style/AppTheme">
    <activity android:label="@string/{f:FREE}app_name_free{/f}{f:PAID}app_name_paid{/f}" android:name=".ActivityMain">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>
</application>
Run Code Online (Sandbox Code Playgroud)

这是java文件的模板"ProductInfo.template":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};
    }
Run Code Online (Sandbox Code Playgroud)

此清单由gradle.build脚本使用productFlavorsprocessManifest任务钩子处理:


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

这是将任务分离为处理文件


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:<flavor_name>}...{/f}" pattern and leave only "<flavor_name>==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)
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:为代码重用目的创建的processTemplateFile.


gal*_*lex 8

Gradle允许使用生成的BuildConfig.java将一些数据传递给代码.

productFlavors {
    paid {
        packageName "com.simple.paid"
        buildConfigField 'boolean', 'PAID', 'true'
        buildConfigField "int", "THING_ONE", "1"
    }
    free {
        packageName "com.simple.free"
        buildConfigField 'boolean', 'PAID', 'false'
        buildConfigField "int", "THING_ONE", "0"
    }
Run Code Online (Sandbox Code Playgroud)

  • 请编辑您的其他答案,并在上面添加一个巨大的警告,并提供此答案的链接! (2认同)
  • 您也可以使用相同的方法来获取具有`resValue'bool','is_paid','true'的资源,然后像`@bool/is_paid`一样使用它. (2认同)