Create Free/Paid versions of Application from same code
所以我要下载我的申请的发布时间。我们计划发布两个版本,一个免费的基于广告的播放解锁版本和一个付费完全解锁版本。我设置了代码,我可以在启动时设置一个标志来启用/禁用广告并锁定/解锁所有功能。因此字面上只有一行代码在这些版本之间执行不同。
为了发布两个单独的应用程序,它们需要不同的软件包名称,所以我的问题是:是否有一种简单的方法来重构我的应用程序的软件包名称? Eclipse的重构工具无法解析生成的R文件或布局和清单文件中的任何XML引用。我试图使用原始资源创建一个新项目,但我无法引用资产和资源,我希望避免重复我的任何代码和资产。手动重构它不是一个巨大的痛苦,但我觉得必须有一个更好的方法来做到这一点。有人有一个优雅的解决方案吗?
编辑/回答:
对于我的情况,我发现只使用Project - > Android Tools - > Rename Application Package是完全可以接受的。我不知道这存在,我觉得现在发布这个就是个白痴。感谢大家的回答和评论,请随意投票。
在Android Studio中使用build.gradle非常简单。阅读productFlavors。这是一个非常有用的功能。只需在build.gradle中添加以下行:
1 2 3 4 5 6 7 8 9 10 11 12 | 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' } } |
在这个例子中,我添加了两种产品口味:第一种用于精简版,第二种用于完整版。每个版本都有自己的versionCode和versionName(适用于Google Play出版物)。
在代码中只需检查BuildConfig.FLAVOR:
1 2 3 4 | if (BuildConfig.FLAVOR =="lite") { // add some ads or restrict functionallity } |
要在设备上运行和测试,请使用Android Studio中的"Build Variants"选项卡在版本之间切换:
可能是批量发布Android应用程序的副本。
Android库项目将很好地为您完成此任务。您将最终得到1个库项目,然后是每个版本的项目(免费/完整),其中包含不同的资源(如应用程序图标和不同的清单),这是包名称的变化。
希望有所帮助。它对我来说效果很好。
Gradle允许使用生成的BuildConfig.java将一些数据传递给代码。
1 2 3 4 5 6 7 8 9 10 11 | 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" } |
最好的方法是使用"Android Studio" - > gradle.build - > [productFlavors +从模板生成清单文件]。这种组合允许从一个来源为不同的应用程序市场构建免费/付费版本和一系列版本。
这是模板化清单文件的一部分:
1 2 3 4 5 6 7 8 9 10 11 | <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"> <intent-filter> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> |
这是java文件的模板"ProductInfo.template":ProductInfo.java
1 2 3 4 5 6 7 | 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}; } |
此清单由gradle.build脚本使用productFlavors和processManifest任务钩子处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | 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") } } |
这是将任务分离为处理文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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) } } |
更新:为代码重用目的创建的processTemplateFile。
对于想要使用Denis解决方案的每个人:
在新的gradle版本中,
1 2 3 4 5 6 7 8 9 10 11 12 | productFlavors { lite { applicationId = 'com.project.test.app' versionCode 1 versionName '1.0.0' } pro { applicationId = 'com.project.testpro.app' versionCode 1 versionName '1.0.0' } } |
如果你想要另一个应用程序名称,根据风味,你也可以添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | productFlavors { lite { applicationId = 'com.project.test.app' resValue"string","app_name","test lite" versionCode 1 versionName '1.0.0' } pro { applicationId = 'com.project.testpro.app' resValue"string","app_name","test pro" versionCode 1 versionName '1.0.0' } } |
我正在尝试的一种方法是使用活动的完全限定名称,只更改包属性。它避免了任何真正的重构(1个文件副本,1个文本子)。
这几乎可以工作,但是生成的R类没有被选中,因为这个包被拉出了AndroidManifest.xml,所以最终在新的包中。
我认为通过插入分发包名称的Ant规则(在-pre-build中)构建AndroidManifest.xml应该是相当直接的,然后(在 - 预编译中)将生成的资源放入默认(Java)包中。
希望这可以帮助,
Phil Lello