拥有付费版和免费版 Android 应用的最佳方式
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3711967/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best way to have paid and free version of an Android app
提问by DaBeeeenster
I already have a free app in the Android Market, but I want to add a paid-for version with better features. I can't upload the same up with some changed constants to unlock those features as the Market tells me I already have an app with that package name in the market.
我已经在 Android Market 上有一个免费的应用程序,但我想添加一个具有更好功能的付费版本。我无法通过一些更改的常量上传相同的内容来解锁这些功能,因为市场告诉我我已经在市场上有一个具有该包名称的应用程序。
What's the cleanest way of doing this?
这样做最干净的方法是什么?
采纳答案by Daniel Szmulewicz
The Android SDK formally addresses the issue of a shared or common codebase with something called a library project.
Android SDK 通过称为库项目的东西正式解决了共享或公共代码库的问题。
http://developer.android.com/tools/projects/index.html
http://developer.android.com/tools/projects/index.html
Basically, the shared code is defined as a library project, then a paid and a free version are simply two different projects in your eclipse workbench, both referencing aforementioned library project.
基本上,共享代码被定义为一个库项目,然后付费和免费版本只是 eclipse 工作台中的两个不同项目,都引用了上述库项目。
At build-time, the library project gets merged with either of your releases, which is what you want.
在构建时,库项目与您的任一版本合并,这正是您想要的。
The Android SDK example source code contains a project called TicTacToe that will help you get started with library projects usage.
Android SDK 示例源代码包含一个名为 TicTacToe 的项目,可帮助您开始使用库项目。
Good luck.
祝你好运。
Daniel
丹尼尔
回答by Levente Dobson
Several approaches exist, but you usually don't see the drawbacks until you try them for yourself. Here are my experiences:
存在多种方法,但您通常在亲自尝试之前看不到缺点。以下是我的经验:
Unlocker app. This is very easy to implement, create a new app that acts as a licence: your original app should check if the unlocker app's signature matches that of your app (if yes the unlocker is available on the device, otherwise no); your unlocker should prompt for downloading your free app if not installed, otherwise start it and remove its launcher icon.
Pros:easy to implement and there is only one codebase to maintain.
Cons:it is said that users are sometimes confused by this model, they simply don't understand why they have two launcher icons, or what have they just downloaded. Removing a launcher icon is cumbersome, changes are only visible if the device is rebooted. Also, it seems you will not be able to use Google's licensing API (LVL), because your free app cannot make licensing requests on behalf of your paid unlocker app. Any workaround for this latter leads to bad user experience.In-app purchase. This is easy to implement if you have IAP in your code anyway, otherwise it will take quite some time to get things right.
Pros:there is only one codebase to maintain and purchasing flow for the user is quite convenient.
Cons:users are said to be concerned about whether their purchase is 'persisent', meaning they are confused whether they could still use the pro features if they later installed the app to another device or reinstalled it.Free and paid versions with shared library project. This should not be a hard thing to code and seems like a logical decision to have a single codebase containing most of your app logic and maintain only the differences.
Pros:users are less confused with this model, but they are probably concerned about whether their preferences will be lost if they start using the paid version.
Cons:Java/Eclipse in my experience is not really friendly when it comes to libraries. It will take some time to set up the projects correctly, but it will still be confusing how the whole thing works, what to put in which manifest, what happens with resources, etc. You will face build issues with misconfigured projects, build issues with resources not being found (library project will not 'see' already referenced resources, so you'll have to edit the references one-by-one). Moreover asset files are not supported in this model meaning you will have to do some tricks with symlinking, copying or other magic that will just add to the terrible mess your "single codebase" project has already become. And when your project finally builds you just have to keep fingers crossed for the whole thing to work as expected, be prepared for missing images and hidden errors to look for. And of course you will need to provide your users a convenient way to migrate their preferences into the paid version upon first launch.Free and paid versions with separate codebases and source control. At first glance this also seems a good idea since a decent source control system could lift weight off your shoulders but...
Pros:same as 3.
Cons:you will be maintaning two different codebases and nothing else but a merging/branching hell that is to be expected here. App package names should be different, so you'll probably need to differentiate every single file you have, to keep things clean. And of course you will need to provide your users a convenient way to migrate their preferences into the paid version upon first launch.Free and paid versions with a script that derives one from the other. It sounds like a dirty hack to accomplish, but you know for sure that it works.
Pros:same as 3 plus you only maintain a single codebase.
Cons:creating the scripts takes a little time (make sure you rename folders, replace package, application and project names) and you'll have to be careful to update your scripts from time-to-time if necessary (this won't happen if there aren't too many differences between the free and the paid versions). And of course you will need to provide your users a convenient way to migrate their preferences into the paid version upon first launch.
解锁应用程序。这很容易实现,创建一个用作许可证的新应用程序:您的原始应用程序应检查解锁器应用程序的签名是否与您的应用程序的签名匹配(如果是,则解锁器在设备上可用,否则不可用);如果未安装,您的解锁器应提示下载您的免费应用程序,否则启动它并删除其启动器图标。
优点:易于实现,只需维护一个代码库。
缺点:据说用户有时会被这个模型弄糊涂,他们根本不明白为什么他们有两个启动器图标,或者他们刚刚下载了什么。删除启动器图标很麻烦,只有重新启动设备才能看到更改。此外,您似乎无法使用 Google 的许可 API (LVL),因为您的免费应用无法代表您的付费解锁应用发出许可请求。后者的任何解决方法都会导致糟糕的用户体验。在应用程序内购买。如果您的代码中有 IAP,这很容易实现,否则将需要相当长的时间才能把事情做好。
优点:只有一个代码库需要维护,用户购买流程非常方便。
缺点:据说用户担心他们的购买是否“持久”,这意味着如果他们稍后将应用程序安装到另一台设备或重新安装,他们是否仍然可以使用专业版功能感到困惑。带有共享库项目的免费和付费版本。这不应该是一件很难编码的事情,而且似乎是一个合乎逻辑的决定,即拥有一个包含大部分应用程序逻辑的单一代码库并仅保留差异。
优点:用户对这种模式的混淆较少,但他们可能会担心如果开始使用付费版本,他们的偏好是否会丢失。
缺点:根据我的经验,Java/Eclipse 在库方面并不是很友好。正确设置项目需要一些时间,但仍然会混淆整个事情是如何工作的,在哪个清单中放置什么,资源会发生什么,等等。您将面临配置错误的项目的构建问题,构建问题未找到资源(图书馆项目不会“看到”已引用的资源,因此您必须逐一编辑引用)。此外,此模型不支持资产文件,这意味着您必须使用符号链接、复制或其他魔法来做一些技巧,这些技巧只会增加您的“单一代码库”项目已经变成的可怕混乱。当您的项目最终构建时,您只需要保持手指交叉即可让整个事情按预期工作,准备好寻找丢失的图像和隐藏的错误。当然,您需要为您的用户提供一种方便的方式,以便在首次发布时将他们的偏好迁移到付费版本中。免费和付费版本具有单独的代码库和源代码控制。乍一看,这似乎也是个好主意,因为一个体面的源代码控制系统可以减轻您的负担,但是...
优点:与 3 相同。
缺点:您将维护两个不同的代码库,除了合并/分支地狱之外别无其他在这里是可以预料的。应用程序包名称应该不同,因此您可能需要区分您拥有的每个文件,以保持整洁。当然,您需要为您的用户提供一种方便的方式,以便在首次发布时将他们的偏好迁移到付费版本中。免费和付费版本的脚本从另一个中派生出来。这听起来像一个肮脏的黑客来完成,但你肯定知道它是有效的。
优点:与 3 相同,而且您只维护一个代码库。
缺点:创建脚本需要一点时间(确保重命名文件夹、替换包、应用程序和项目名称),并且如果需要,您必须小心地不时更新脚本(这不会发生)如果免费和付费版本之间没有太多差异)。当然,您需要为您的用户提供一种方便的方式,以便在首次发布时将他们的偏好迁移到付费版本中。
No solution is perfect, but the above can hopefully point you in the right direction if you are just about to start implementing one of the possibilities.
没有任何解决方案是完美的,但如果您即将开始实施其中一种可能性,以上内容有望为您指明正确的方向。
回答by Lahiru Chandima
With Gradle build system, you now can have different product flavors, each having its own package name. Following is an example gradle script having free and pro flavors of the same app.
使用 Gradle 构建系统,您现在可以拥有不同的产品风格,每个风格都有自己的包名称。以下是具有相同应用程序的免费和专业风格的示例 gradle 脚本。
apply plugin: 'com.android.application'
android {
compileSdkVersion 19
buildToolsVersion "19.1"
defaultConfig {
applicationId "com.example.my.app"
}
productFlavors {
free {
applicationId "com.example.my.app"
minSdkVersion 15
targetSdkVersion 23
versionCode 12
versionName '12'
}
pro {
applicationId "com.example.my.app.pro"
minSdkVersion 15
targetSdkVersion 23
versionCode 4
versionName '4'
}
}
R
class will still be generated in the package name specified in the AndroidManifest.xml
so you don't need to change a single line of code when switching flavors.
R
class 仍然会在 中指定的包名中生成,AndroidManifest.xml
因此您在切换口味时无需更改任何一行代码。
You can switch the flavor from Build Variants
pane which is accessible from left bottom corner of Android Studio. Also, when you want to generate a signed APK, android studio will ask you the flavor you want to build the APK.
您可以从Build Variants
可从 Android Studio 左下角访问的窗格中切换风格。此外,当您想生成签名的 APK 时,android studio 会询问您要构建 APK 的风格。
Also, you can have different resources for each flavor. For an example, you can create a directory pro
in your src
directory. The directory structure should be similar to the main
directory. (Eg: if you want to have a different launcher icon for pro version, you can place it in src\pro\res\drawable
. This will replace the free icon located in src\main\res\drawable
, when you have switched to pro
flavor).
此外,您可以为每种口味拥有不同的资源。例如,您可以pro
在您的src
目录中创建一个目录。目录结构应与main
目录类似。(例如:如果您想为专业版使用不同的启动器图标,您可以将它放在 中src\pro\res\drawable
。src\main\res\drawable
当您切换到pro
风味时,这将替换位于 中的免费图标)。
If you create a strings.xml
in pro
resource directory described above, the main strings.xml
and pro strings.xml
will get merged to get a final strings.xml
when building in pro
flavor. If a certain string key exists in both free and pro xml, the string value will be taken from pro xml.
如果您strings.xml
在pro
上述资源目录中创建了一个in资源目录,则 mainstrings.xml
和 prostrings.xml
将strings.xml
在构建pro
风味时合并以获得最终版本。如果在 free 和 pro xml 中都存在某个字符串键,则将从 pro xml 中获取字符串值。
If you need to check whether current version is pro or free in code, you can use a method like following.
如果您需要在代码中检查当前版本是 pro 还是 free ,您可以使用如下方法。
public boolean isPro() {
return context.getPackageName().equals("com.example.my.app.pro");
}
For more information, Refer this
有关更多信息,请参阅此
回答by user462990
One source code two apps (Eclipse)
一个源代码两个应用程序(Eclipse)
There is a recurring problem of managing an app with two forms of presentation in the market, maybe with only one bit difference between the two ( paidFor = true; ) Maybe the icon is different too.
在市场上管理具有两种呈现形式的应用程序时经常出现的问题,也许两者之间只有一点点差异(paidFor = true;)也许图标也不同。
This approach uses the description of Package names explained by Mihai at http://blog.javia.org/android-package-name/which underlines the distinction between the package name used to manage the source code and the package name used to publish the apk on the Android Market. In this example the two published package names are com.acme.superprogram and com.acme.superprogrampaid, the Java source code package name is com.acme.superprogram.
这种方法使用了 Mihai 在http://blog.javia.org/android-package-name/解释的包名称的描述,它强调了用于管理源代码的包名称和用于发布代码的包名称之间的区别安卓市场上的apk。本例中发布的两个包名是com.acme.superprogram和com.acme.superprogrampaid,Java源代码包名是com.acme.superprogram。
In the manifest the Activities are listed and named as .ActivityName. Mike Wallace pointed out in his recent presentation that the preceding dot is important and it can be replaced by a fully qualified package. For example “com.acme.superprogram.DialogManager” can replace “.DialogManager” in the manifest.xml text.
在清单中,活动被列出并命名为 .ActivityName。Mike Wallace 在他最近的演讲中指出,前面的点很重要,可以用完全限定的包代替。例如,“com.acme.superprogram.DialogManager”可以替换 manifest.xml 文本中的“.DialogManager”。
Step 1 is to replace all the activity android:name entries with these fully qualified package names, using the java source code management package name (com.acme.superprogram).
第 1 步是使用 java 源代码管理包名称 (com.acme.superprogram) 将所有活动 android:name 条目替换为这些完全限定的包名称。
Then the Manifest Package name can be changed...
然后可以更改清单包名称...
In Eclipse this forces a recompile and a new R.java is created in the gen folder. Here is where it gets a bit tricky; there are two folders com.acme.superprogram and com.acme.superprogrampaid, only one has a R.java. Simply copy the R.java into the other folder so that the program can resolve the R.layout.xyz items.
在 Eclipse 中,这会强制重新编译并在 gen 文件夹中创建一个新的 R.java。这是有点棘手的地方;有两个文件夹 com.acme.superprogram 和 com.acme.superprogrampaid,只有一个有 R.java。只需将 R.java 复制到另一个文件夹中,以便程序可以解析 R.layout.xyz 项目。
When you change the package in the
当您更改包中的
I have tried it on a couple of apps. I have both running together on the emulator, my 2.1 phone and they are both on the Android Market.
我已经在几个应用程序上尝试过。我在模拟器和我的 2.1 手机上同时运行,它们都在 Android Market 上。
回答by Jimmy Kane
Just in case you have also a wear module there needs extra work to be done.
以防万一您还有磨损模块,需要做额外的工作。
Here are some example Gradle files for packaging a wear module with flavours and buildtypes.
以下是一些示例 Gradle 文件,用于打包具有风格和构建类型的磨损模块。
Module mobile build.gradle
模块移动 build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "com.example.app"
minSdkVersion 15
targetSdkVersion 23
versionCode 85
versionName "2.5.2"
}
buildTypes {
debug {
applicationIdSuffix ".debug"
embedMicroApp = true
minifyEnabled false
}
release {
embedMicroApp = true
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
zipAlignEnabled true
}
}
productFlavors {
free{
applicationId "com.example.app"
}
pro{
applicationId "com.example.app.pro"
}
}
}
configurations {
freeDebugWearApp
proDebugWearApp
freeReleaseWearApp
proReleaseWearApp
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
freeDebugWearApp project(path: ':wear', configuration: 'freeDebug')
proDebugWearApp project(path: ':wear', configuration: 'proDebug')
freeReleaseWearApp project(path: ':wear', configuration: 'freeRelease')
proReleaseWearApp project(path: ':wear', configuration: 'proRelease')
}
Module Wear build.gradle
模块磨损 build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
publishNonDefault true
defaultConfig {
applicationId "com.example.app"
minSdkVersion 20
targetSdkVersion 23
versionCode 85
versionName "2.5.2"
}
buildTypes {
debug {
applicationIdSuffix ".debug"
minifyEnabled false
}
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
zipAlignEnabled true
}
}
productFlavors {
free {
applicationId "com.example.app"
}
pro {
applicationId "com.example.app.pro"
}
}
}
dependencies {
...
}
回答by Eric Leschinski
I had the same problem, what I settled on is making two android apps, one called my_epic_app_free, and the other my_epic_app_paid. What I would do is only make changes to the paid version, then I have a separate java console program, all it does is access all the .java files directly from disk, copy them into memory, fiddle with the package names and manifest lines, then paste them directly into the free app. I have this on a button push on the desktop so when I'm done developing on the paid version, I press the button, then compile the free version. I have both the paid app and free app communicate to eachother, the user can even install both of them, and the free version sees the paid, and unlocks to the paid version.
我遇到了同样的问题,我决定制作两个 android 应用程序,一个名为 my_epic_app_free,另一个名为 my_epic_app_paid。我要做的只是对付费版本进行更改,然后我有一个单独的 java 控制台程序,它所做的就是直接从磁盘访问所有 .java 文件,将它们复制到内存中,修改包名称和清单行,然后将它们直接粘贴到免费应用程序中。我在桌面上按下按钮,所以当我完成付费版本的开发时,我按下按钮,然后编译免费版本。我有付费应用和免费应用相互通信,用户甚至可以安装它们,免费版本看到付费,并解锁到付费版本。
Another idea is to make the paid app just a bare bone program which contains a keyfile. The paid app, if run, will automatically run the free version, and the free version will behave as the paid version.
另一个想法是让付费应用程序只是一个包含密钥文件的简单程序。付费应用程序,如果运行,将自动运行免费版本,免费版本将作为付费版本运行。
The free app checks the presence of the paid app's key file, and if it exists, then it would unlock the free app. This prevents code duplication. The package names still have to be different but ideally the paid app would not need to be changed often.
免费应用程序检查付费应用程序的密钥文件是否存在,如果存在,则将解锁免费应用程序。这可以防止代码重复。软件包名称仍然必须不同,但理想情况下,付费应用程序不需要经常更改。
Benefit: No user data/settings are lost when "upgrading" to the paid version.
优点:“升级”到付费版本时不会丢失用户数据/设置。
Drawback: If the user installs the paid version first, they will have to be directed to install the free version which is a hassle, why do they have to install two apps for the paid version?
缺点:如果用户先安装付费版本,就必须被引导安装免费版本,很麻烦,为什么他们必须为付费版本安装两个应用程序?
回答by Neil Townsend
If you don't want to use library projects, and are happy with the risks that a couple of people have mentionned in comments, then @user426990 has provided an excellent answer, in theory. However, eclipse seems to wipe the entire contents of the gen directory when doing a fresh build for export (which I find it hard to argue with as a general principle).
如果您不想使用库项目,并且对一些人在评论中提到的风险感到满意,那么@user426990 理论上提供了一个很好的答案。但是,在为导出进行全新构建时,eclipse 似乎会擦除 gen 目录的全部内容(我发现作为一般原则很难与之争论)。
An alternative solution, based on the same principle, is as follows, assuming that you have written com.acme.superprogram and you wish to create com.acme.superprogrampaid
基于相同原理的替代解决方案如下,假设您已经编写了 com.acme.superprogram 并且您希望创建 com.acme.superprogrampaid
Ensure that your manifest points to the activities, services and so on by full name. As per @user426990's answer ".CoolActivity" must be listed as com.acme.superprogram.CoolActivity
Create a new class MyR in your code (com.activity.superprogram, with the rest of the code) as follows:
package com.acme.superprogram; import com.acme.superprogram.R; public final class MyR { public final static R.attr attr = new R.attr(); public final static R.color color = new R.color(); public final static R.dimen dimen = new R.dimen(); public final static R.layout layout = new R.layout(); public final static R.id id = new R.id(); public final static R.string string = new R.string(); public final static R.drawable drawable = new R.drawable(); public final static R.raw raw = new R.raw(); public final static R.style style = new R.style(); public final static R.xml xml = new R.xml(); }
You will need to vary the exact content to reflect the resources you use! For example, you may not need the xml line and may need another one. Look at the real R.java file in gen/com/acme/superprogram and you will need one line per class. You may need to subclass.
Now (a) remove all "import com.acme.superprogram.R" lines from your code and (b) replace all "R." references to "MyR.". This way all your references to R are indirected via one place. the downside is that they all get warnings about not being static enough. You have three options with these warnings: you can suppress them, you can ignore them, or you can make a more complete version of MyR, with a line for each entry in R which you use:
package com.acme.superprogram; import com.acme.superprogram.R; public final class MyR { final static class attr { final static int XXXX = R.attr.XXXX // And so on ...
As per @user426990, you can now change the package in the manifest from "com.acme.superprogram" to "com.acme.superprogrampaid"; you probably also want to change the name, launch icon and the key variables at this point.
Change the import line in MyR to import com.acme.superprogrampaid.R
确保您的清单以全名指向活动、服务等。根据@ user426990 的回答“.CoolActivity”必须列为 com.acme.superprogram.CoolActivity
在您的代码(com.activity.superprogram,以及其余代码)中创建一个新类 MyR,如下所示:
package com.acme.superprogram; import com.acme.superprogram.R; public final class MyR { public final static R.attr attr = new R.attr(); public final static R.color color = new R.color(); public final static R.dimen dimen = new R.dimen(); public final static R.layout layout = new R.layout(); public final static R.id id = new R.id(); public final static R.string string = new R.string(); public final static R.drawable drawable = new R.drawable(); public final static R.raw raw = new R.raw(); public final static R.style style = new R.style(); public final static R.xml xml = new R.xml(); }
您需要更改确切的内容以反映您使用的资源!例如,您可能不需要 xml 行,而可能需要另一行。查看 gen/com/acme/superprogram 中真正的 R.java 文件,每个类需要一行。您可能需要子类化。
现在 (a) 从您的代码中删除所有“import com.acme.superprogram.R”行,并且 (b) 替换所有“R”。对“MyR”的引用。这样,您对 R 的所有引用都通过一个地方间接引用。缺点是它们都会收到关于不够静态的警告。对于这些警告,您有三个选项:您可以取消它们,您可以忽略它们,或者您可以制作更完整的 MyR 版本,为您使用的 R 中的每个条目添加一行:
package com.acme.superprogram; import com.acme.superprogram.R; public final class MyR { final static class attr { final static int XXXX = R.attr.XXXX // And so on ...
根据@user426990,您现在可以将清单中的包从“com.acme.superprogram”更改为“com.acme.superprogrampaid”;此时您可能还想更改名称、启动图标和关键变量。
将 MyR 中的 import 行更改为 import com.acme.superprogrampaid.R
And away you go.
你走吧。
ps. Remember to change the file name in the final export dialog box ...
附:记得在最终导出对话框中更改文件名...
回答by Metalhead1247
Changing the package name will help.
Just change the package nameof your paid app using eclipse and put it in market.
更改包名称会有所帮助。
只需使用 eclipse更改付费应用程序的包名称并将其投放市场即可。
That will solve your problem.
那将解决您的问题。
回答by Rene
Why not make two different Eclipse projects? It is not so difficult to fix the few things referring to the package name by hand. Take care to change the import statements and the manifest file. You need to code twice anyway. Of course, it is a nuisance to fix code in both places.
为什么不制作两个不同的 Eclipse 项目呢?手动修复与包名称相关的少数事情并不难。注意更改导入语句和清单文件。无论如何,您需要编码两次。当然,在这两个地方修复代码是一件很麻烦的事情。
The proper solution would be in the market. It should allow two applications with the same package names, and ask to replace the one when the other is to be installed. Upon upload, it should check if the package name is really from the same software vendor.
正确的解决方案是在市场上。它应该允许两个具有相同包名的应用程序,并在安装另一个时要求替换一个。上传时,它应该检查包名称是否真的来自同一个软件供应商。
Rene
雷内