有付费和免费版本的Android应用程序的最佳途径
我已经在Android电子市场中有一个免费的应用程序,但我想添加一个付费版本更好的function。 我不能上传一些改变的常量来解锁这些function,因为市场告诉我,我已经有一个在市场上的软件包名称的应用程序。
什么是最干净的方式呢?
Android SDK正式解决了一个被称为库项目的共享或通用代码库的问题。
http://developer.android.com/tools/projects/index.html
基本上,共享代码被定义为一个库项目,然后付费和免费版本只是你的eclipse工作台中的两个不同的项目,都参考上述库项目。
在构build时,库项目将与您的任一版本合并,这就是您想要的。
Android SDK示例源代码包含一个名为TicTacToe的项目,它将帮助您开始使用库项目。
祝你好运。
丹尼尔
有几种方法存在,但是你通常在你自己尝试之前不会看到缺点。 这是我的经验:
-
解锁程序 这是很容易实现,创build一个新的应用程序作为一个许可证:您的原始应用程序应该检查,如果解锁应用程序的签名匹配您的应用程序(如果是的解锁器是可用的设备,否则); 如果未安装,您的解锁器应提示下载免费应用程序,否则请启动它并删除其启动器图标。
优点:易于实现,只有一个代码库可以维护。
缺点:据说用户有时会被这种模式困惑,他们根本不明白为什么他们有两个启动器图标,或者他们刚刚下载了什么。 删除启动器图标很麻烦,只有在设备重启后才能看到更改。 此外,似乎您将无法使用Google的授权API(LVL),因为您的免费应用程序无法代表您的付费解锁应用程序发出许可请求。 对后者的任何解决方法都会导致不良的用户体验。 -
应用内购买。 如果你的代码中有IAP,这很容易实现,否则需要相当长的一段时间才能把事情做好。
优点:只有一个代码库可以维护,用户购买stream程非常方便。
缺点:用户被称为是否购买“持续”,这意味着他们是否仍然可以使用专业function,如果他们稍后将该应用程序安装到另一个设备或重新安装它会感到困惑。 -
免费和付费版本共享库项目。 这不应该是一个艰难的事情来编码,似乎是一个逻辑的决定,有一个单一的代码库,其中包含大部分的应用程序逻辑,只保留差异。
优点:用户对这种模式不太困惑,但他们可能会担心,如果开始使用付费版本,他们的偏好是否会丢失。
缺点:在我的经验中,Java / Eclipse对图书馆来说并不友好。 这需要一定的时间来正确地设置项目,但它仍然是混淆整个事情如何工作,如何放在哪个清单,什么资源发生等。您将面临与错误configuration的项目build设问题,build立问题与没有find资源(库项目不会“看到”已经引用的资源,所以你将不得不一一编辑引用)。 此外,这个模型中不支持资产文件,这意味着你将不得不做一些技巧,符号链接,复制或其他魔术,只会增加你的“单个代码库”项目已经成为可怕的混乱。 而当你的项目最终build立起来时,你只需要保持手指交叉就可以按照预期工作,为失踪图像和隐藏的错误做好准备。 当然,您将需要为用户提供一种便捷的方式,在初次启动时将其偏好迁移到付费版本。 -
免费和付费版本,具有独立的代码库和源代码pipe理。 乍一看这似乎也是一个好主意,因为一个体面的源代码pipe理系统可以减轻你肩上的重量,但…
优点:与3相同。
缺点:你将会是两个不同的代码库的主要工具,除此之外还有一个合并/分支的地狱。 应用程序包名称应该不同,所以您可能需要区分每个文件,以保持干净。 当然,您将需要为用户提供一种便捷的方式,在初次启动时将其偏好迁移到付费版本。 -
免费和付费版本的脚本,从另一个派生。 这听起来像是一个肮脏的黑客,但你肯定知道它的工作原理。
优点:与3相同,只保留一个代码库。
缺点:创build脚本需要一点时间(确保您重命名文件夹,replace软件包,应用程序和项目名称),并且必须小心地根据需要随时更新您的脚本(这不会发生如果没有太多的免费和付费版本之间的差异)。 当然,您将需要为用户提供一种便捷的方式,在初次启动时将其偏好迁移到付费版本。
没有解决scheme是完美的,但是如果你正准备开始实施其中的一种可能性,上面的方法可以指导你正确的方向。
使用Gradle构build系统,您现在可以拥有不同的产品风格,每个风格都有自己的包名称。 以下是具有相同应用程序的免费和亲口味的示例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
类仍将在AndroidManifest.xml
指定的包名中生成,因此切换flavor时不需要更改一行代码。
您可以从Android Studio的左下angular访问的“ Build Variants
窗格中切换。 另外,当你想生成一个签名APK时,android studio会问你想要构buildAPK的味道。
另外,您可以为每种风味提供不同的资源。 举个例子,你可以在你的src
目录下创build一个目录pro
。 目录结构应该与main
目录类似。 (例如:如果你想为pro版本提供一个不同的启动器图标,你可以把它放在src\pro\res\drawable
src\main\res\drawable
,当你切换到src\main\res\drawable
时, pro
味)。
如果你在上面描述的pro
资源目录中创build一个strings.xml
文件,那么主要的strings.xml
和pro strings.xml
会被合并,以便在pro
构build最终的strings.xml
。 如果free和pro xml中都存在某个string键值,则string值将从pro xml中获取。
如果您需要检查当前版本是否是代码中的pro或free,则可以使用类似以下的方法。
public boolean isPro() { return context.getPackageName().equals("com.example.my.app.pro"); }
欲了解更多信息,请参阅此
一个源代码两个应用程序(Eclipse)
在市场上有两种呈现方式来pipe理应用程序的问题经常出现,也许两者之间只有一点差异(paidFor = true;)也许图标也不同。
这种方法使用了Mihai在http://blog.javia.org/android-package-name/中解释的软件包名称的描述,它强调了用于pipe理源代码的软件包名称和用于发布apk的Android电子市场。; 在这个例子中,两个发布的包名称是com.acme.superprogram和com.acme.superprogrampaid,Java源代码包的名称是com.acme.superprogram。
在清单中,活动列出并命名为.ActivityName。 迈克·华莱士(Mike Wallace)在最近的演讲中指出,前面的点很重要,可以用完全合格的包来替代。 例如,“com.acme.superprogram.DialogManager”可以replacemanifest.xml文本中的“.DialogManager”。
第1步是使用java源代码pipe理包名称(com.acme.superprogram),将这些完全限定的包名称replace为所有活动android:名称条目。
然后,Manifest包名称可以更改…
在Eclipse中强制重新编译,并在gen文件夹中创build一个新的R.java。 这是一个棘手的地方。 有两个文件夹com.acme.superprogram和com.acme.superprogrampaid,只有一个有R.java。 只需将R.java复制到另一个文件夹中,以便程序可以parsingR.layout.xyz项目。
当你改变包中的
我已经尝试了几个应用程序。 我都在模拟器上运行,我的2.1手机和他们都在Android Market上。
我有同样的问题,我决定做两个android应用程序,一个叫做my_epic_app_free,另一个是my_epic_app_paid。 我要做的只是对付费版本进行更改,然后我有一个单独的java控制台程序,它所做的只是直接从磁盘访问所有.java文件,将它们复制到内存中,摆弄包名称和清单行,然后直接粘贴到免费的应用程序。 我有一个button在桌面上的button,所以当我完成付费版本的开发,我按下button,然后编译免费版本。 我有付费应用程序和免费应用程序互相沟通,用户甚至可以安装他们两个,免费版本看付费,并解锁到付费版本。
另一个想法是使付费应用程序只是一个裸骨程序,其中包含一个密钥文件。 付费应用程序,如果运行,将自动运行免费版本,免费版本将performance为付费版本。
免费的应用程序检查付费应用程序的密钥文件的存在,如果它存在,那么它将解锁免费的应用程序。 这可以防止代码重复。 软件包名称仍然不同,但理想情况下付费应用程序不需要经常更改。
好处:当“升级”到付费版本时,用户数据/设置不会丢失。
缺点:如果用户首先安装付费版本,他们将不得不被安装免费版本,这是一个麻烦,为什么他们必须安装两个付费版本的应用程序?
以防万一你也有磨损模块,需要做额外的工作。
下面是一些示例Gradle文件,用于打包磨损模块的风味和构buildtypes。
模块移动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') }
模块磨损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 { ... }
如果你不想使用图书馆项目,并且对一些人在评论中提到的风险感到高兴,那么@ user426990理论上提供了一个很好的答案。 然而,eclipse似乎在进行新build出口时会擦除gen目录的全部内容(我发现很难将其作为一般原则进行争论)。
另一种解决scheme,基于同样的原理,如下所示,假设你已经写了com.acme.superprogram,并且你想创buildcom.acme.superprogrampaid
-
确保您的清单通过全名指向活动,服务等。 根据@ user426990的答案“.CoolActivity”必须列为com.acme.superprogram.CoolActivity
-
在代码中创build一个新的类MyR(com.activity.superprogram,其余代码),如下所示:
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)replace所有的“R. 引用“MyR”。 通过这种方式,所有对R的引用都是通过一个地方来实现的。 不利的一面是,他们都得到了关于不够静态的警告。 您有三个选项可以处理这些警告:您可以禁止它们,您可以忽略它们,或者您可以创build一个更完整的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”; 您可能还想在此处更改名称,启动图标和关键variables。
-
更改MyR中的导入行以导入com.acme.superprogrampaid.R
而你走了。
PS。 请记住在最终导出对话框中更改文件名称…
更改软件包名称将有所帮助。
只需使用eclipse更改付费应用的软件包名称 ,然后将其放入市场。
这将解决您的问题。
为什么不制作两个不同的Eclipse项目? 手工修复包装名称的问题并不难。 请注意更改导入语句和清单文件。 无论如何,你需要编写两次。 当然,在两个地方修复代码是一件麻烦事。
适当的解决scheme将在市场上。 它应该允许两个具有相同包名的应用程序,并要求在另一个应用程序被安装时replace它。 上传时,应检查软件包名称是否来自同一软件供应商。
刘若英
- 如何使用adb命令在没有SD卡的设备上推送文件
- strings.xml中的错误:invalid symbol'continue'
- Android NDK的字节顺序
- Android:将位图转换为inputstream
- android – 我怎样才能使button闪光?
- LinearLayout:layout_gravity =“bottom”在水平LinearLayout上不起作用
- 具有自定义标记的android Maps API v2
- AndroidManifest.xml中的用户权限和权限标签之间的区别
- 为什么一些Android手机导致我们的应用程序抛出java.lang.UnsatisfiedLinkError?