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

所以我正在为我的申请发布时间。 我们计划发布两个版本,一个免费的基于广告的播放到解锁版本和一个付费完全解锁版本。 我已经设置了代码,可以在启动时简单设置一个标志来启用/禁用广告并locking/解锁所有function。 因此,从字面上看,只有一行代码在这些版本之间执行不同。

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

编辑/回答:

对于我的情况,我觉得只需使用Project – > Android Tools – > Rename Application Package是完全可以接受的。 我不知道这是存在的,我觉得现在发布这个白痴。 感谢大家的回答和评论,随时为此投票闭幕。

可能是Android应用批量发布的副本。

Android库项目将为您做到这一点很好。 最终你会得到一个库项目,然后是每个版本的一个项目(free / full),这些项目真的只包含应用程序图标和不同的清单等不同的资源,这些资源名称将会变化。

希望有所帮助。 它为我工作得很好。

在Android Studio中使用build.gradle非常简单。 阅读有关productFlavors 。 这是一个非常有用的function。 只需在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' } } 

在这个例子中,我增加了两个产品口味:第一个是lite版本,第二个是完整版本。 每个版本都有自己的versionCode和versionName(用于Google Play发布)。

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

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

要在设备上运行和testing,请使用Android Studio中的“Build Variants”选项卡切换以下版本: 在这里输入图像说明

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


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


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

这是用于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}; } 

该清单由具有productFlavorsprocessManifest任务钩子的gradle.build脚本进行处理:


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

这是处理文件的分离任务


 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创build代码重用的目的。

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

对于每个想使用丹尼斯解决scheme的人:
在新的gradle版本中packageName现在是applicationId ,不要忘记把productFlavors { ... }放在android { ... }

 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个文件副本,1个文本子)。

这几乎可以工作,但是生成的R类没有被提取出来,因为这个包被从AndroidManifest.xml中取出,所以最终在新的包中。

我认为通过插入分发包名称的Ant规则(in -pre-build)构buildAndroidManifest.xml应该相当简单,然后(在-pre-compile中)生成的资源到默认的(Java)包。

希望这可以帮助,

菲尔·莱罗

如果你想要另一个应用程序名称,根据风格,你也可以添加这个:

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