所以我正在申请发布时间。我们计划发布两个版本,一个免费的基于广告的播放解锁版本和一个付费完全解锁版本。我设置了代码,我可以在启动时设置一个标志来启用/禁用广告并锁定/解锁所有功能。因此,只有一行代码在这些版本之间执行不同。
为了发布两个独立的应用程序,它们需要不同的软件包名称,所以我的问题是:是否有一种简单的方法来重构我的应用程序的软件包名称? Eclipse的重构工具无法解析生成的R文件或布局和清单文件中的任何XML引用。我试图使用原始资源创建一个新项目,但我无法引用资产和资源,我希望避免重复我的任何代码和资产。手动重构它不是一个巨大的痛苦,但我觉得必须有一个更好的方法来做到这一点。有人有一个优雅的解决方案吗?
编辑/回答:
对于我的情况,我发现只使用Project - >是完全可以接受的。 Android工具 - >重命名应用程序包。我不知道这存在,我觉得现在发布这个就是个白痴。感谢大家的回答和评论,请随意投票。
答案 0 :(得分:33)
在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'
}
}
在这个例子中,我添加了两种产品口味:第一种用于精简版,第二种用于完整版。每个版本都有自己的versionCode和versionName(适用于Google Play出版物)。
在代码中只需检查BuildConfig.FLAVOR:
if (BuildConfig.FLAVOR == "lite") {
// add some ads or restrict functionallity
}
用于运行和测试设备使用" Build Variants" Android Studio中的标签可在版本之间切换:
答案 1 :(得分:17)
可能是Bulk Publishing of Android Apps的副本。
Android库项目将很好地为您完成此任务。你将最终得到1个图书馆项目,然后是每个版本的项目(免费/完整版),其中包含不同的资源,如应用程序图标和不同的清单,这是包名称的变化。
希望有所帮助。它对我有用。
答案 2 :(得分: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>
这是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};
}
此清单由gradle.build脚本处理,其中包含 productFlavors 和 processManifest 任务挂钩:
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。
答案 3 :(得分: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"
}
答案 4 :(得分:2)
对于想要使用Denis解决方案的每个人:
在新的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'
}
}
答案 5 :(得分:0)
我正在尝试的一种方法是使用活动的完全限定名称,只更改包属性。它避免了任何真正的重构(1个文件副本,1个文本子)。
这几乎可以正常工作,但是生成的R类没有被选中,因为这个包是从AndroidManifest.xml中提取的,所以最终会在新包中找到。
我认为通过插入分发包名称的Ant规则(在-pre-build中)构建AndroidManifest.xml应该是相当直接的,然后(在 - 预编译中)将生成的资源转换为默认值( Java)包。
希望这有帮助,
Phil Lello
答案 6 :(得分:0)
如果你想要另一个应用程序名称,根据风味,你也可以添加:
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'
}
}