【问题标题】:Best way to have paid and free version of an Android app [closed]拥有付费和免费版本的 Android 应用程序的最佳方式 [关闭]
【发布时间】:2010-09-14 18:50:14
【问题描述】:

我已经在 Android Market 中有一个免费的应用程序,但我想添加一个具有更好功能的付费版本。我无法通过一些更改的常量上传相同的内容来解锁这些功能,因为市场告诉我我已经在市场上有一个具有该包名称的应用程序。

这样做最干净的方法是什么?

【问题讨论】:

  • 可以在付费版本名称中添加“Pro”吗?
  • 用户界面是否会随着新功能而改变?这两个版本有什么不同?
  • 你试过改包名了吗?

标签: android


【解决方案1】:

Android SDK 通过一种称为库项目的东西正式解决了共享或公共代码库的问题。

http://developer.android.com/tools/projects/index.html

基本上,共享代码被定义为库项目,然后付费版和免费版只是 eclipse 工作台中的两个不同项目,都引用上述库项目。

在构建时,库项目会与您的任一版本合并,这正是您想要的。

Android SDK 示例源代码包含一个名为 TicTacToe 的项目,它将帮助您开始使用库项目。

祝你好运。

丹尼尔

【讨论】:

  • 我已经为我的应用程序的付费和免费版本使用了这个解决方案,问题是我已经失去了像以前那样调试源代码的能力。我已将库链接到源代码,并且可以在调试模式下看到源文件,但看不到变量值,有什么解决方法吗?
  • 如果免费版保存了一些数据,专业版可以读取这些数据吗? - 使用这种技术? (我的意思是没有任何特殊的文件权限或 sd 卡权限)
  • 库项目允许您共享代码,而不是数据。在两个应用程序之间共享数据完全是另一个主题。实施内容提供者可能是满足您需求的解决方案(除其他外)。
  • 链接不再有效。
  • 谢谢。应该是固定的。
【解决方案2】:

存在多种方法,但在您自己尝试之前,您通常不会看到缺点。以下是我的经验:

  1. 解锁应用程序。这很容易实现,创建一个充当许可证的新应用程序:您的原始应用程序应检查解锁器应用程序的签名是否与您的应用程序的签名匹配(如果是,则解锁器在设备上可用,否则否);如果未安装,您的解锁器应提示下载免费应用程序,否则启动它并删除其启动器图标。

    优点:易于实施,只需维护一个代码库。缺点: 据说用户有时会对这种模式感到困惑,他们根本不明白为什么他们有两个启动器图标,或者他们刚刚下载了什么。删除启动器图标很麻烦,只有重新启动设备才能看到更改。此外,您似乎无法使用 Google 的许可 API (LVL),因为您的免费应用程序无法代表您的付费解锁应用程序提出许可请求。后者的任何解决方法都会导致糟糕的用户体验。

  2. 应用内购买。如果您的代码中无论如何都包含 IAP,这很容易实现,否则需要相当长的时间才能将事情做好。

    优点:只有一个代码库需要维护和购买用户的流程非常方便。
    缺点:据说用户担心他们的购买是否“持久”,这意味着他们很困惑如果他们以后还能使用专业功能将应用安装到其他设备或重新安装。

  3. 带有共享库项目的免费和付费版本。这不应该是一件很难编码的事情,而且似乎是一个合乎逻辑的决定,即拥有一个包含大部分应用程序逻辑并只维护差异的代码库。

    优点:用户较少对此模型感到困惑,但他们可能担心如果开始使用付费版本,他们的偏好是否会丢失。
    缺点: 根据我的经验,Java/Eclipse 在涉及时并不是很友好到图书馆。正确设置项目需要一些时间,但它仍然会混淆整个事情的工作原理,在哪个清单中放置什么,资源会发生什么等。您将面临错误配置项目的构建问题,构建问题未找到资源(图书馆项目将不会“看到”已引用的资源,因此您必须逐个编辑引用)。此外,此模型不支持资产文件,这意味着您将不得不使用符号链接、复制或其他魔法来做一些技巧,这只会增加您的“单一代码库”项目已经成为的可怕混乱。当您的项目最终构建时,您只需要保持手指交叉以使整个事情按预期工作,并准备好寻找丢失的图像和隐藏的错误。当然,您需要为您的用户提供一种方便的方式,以便在首次启动时将他们的偏好迁移到付费版本中。

  4. 具有独立代码库和源代码控制的免费和付费版本。乍一看,这似乎也是一个好主意,因为体面的源代码控制系统可以减轻您的负担,但是...

    优点: 与 3 相同。
    缺点: 您将维护两个不同的代码库,除了合并/分支地狱之外别无其他。应用程序包名称应该不同,因此您可能需要区分您拥有的每个文件,以保持干净。当然,您需要为您的用户提供一种方便的方式,以便在首次启动时将他们的偏好迁移到付费版本中。

  5. 免费和付费版本,其中一个脚本派生自另一个。这听起来像是一个肮脏的 hack,但您肯定知道它是有效的。

    优点: 与 3 相同,而且您只维护一个代码库。
    缺点: 创建脚本需要一点时间(确保重命名文件夹、替换包、应用程序和项目名称),如果需要,您必须不时更新脚本(这如果免费版本和付费版本之间没有太多差异,则不会发生)。当然,您需要为您的用户提供一种方便的方式,以便在首次启动时将他们的偏好迁移到付费版本中。

没有完美的解决方案,但如果您即将开始实施其中一种可能性,上述内容有望为您指明正确的方向。

【讨论】:

  • 你能告诉我一些关于方法 1 的信息吗?我只是检查解锁器应用程序的包名称还是有更好的方法?谢谢
  • 不,仅检查包名称是不够的,因为攻击者可以轻松地为您的应用创建和安装虚假解锁应用。您需要使用相同的密钥对您的应用程序和解锁器应用程序进行签名,然后在您的应用程序中检查签名是否匹配。像这样的东西: String mainAppPkg = context.getPackageName(); String keyPkg = "com.example.your.unlocker.key.package.name"; int sigMatch = context.getPackageManager().checkSignatures(mainAppPkg, keyPkg); boolean isInPaidMode = sigMatch == PackageManager.SIGNATURE_MATCH;
  • 啊,酷。所以实现了这一点,我即将向 Play 商店发布我的应用程序的解锁密钥版本。密钥的唯一目的是免费版本可以找到它并删除广告。这是否违反了谷歌的条款?付费应用程序是否必须做一些事情,或者它可以只是免费应用程序的捐赠/删除广告。谢谢
  • 很多应用程序都使用这种模型,所以我非常怀疑这会不会对条款造成任何问题。在我看来,这应该不是问题。我知道这个模型出现的唯一问题是我在帖子中描述的:用户很困惑,没有微不足道的 LVL 支持,隐藏图标很脏而且很痛苦。
  • 对于选项 3、4 和 5,如何让用户购买付费版本?您是否有直接链接到应用商店中付费版本的“购买”按钮?我看到了一个建议的答案,它得到了 2 票反对,但没有解释。
【解决方案3】:

使用 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 类仍会在AndroidManifest.xml 中指定的包名中生成,因此您在切换风格时无需更改一行代码。

您可以从Build Variants 窗格切换风格,该窗格可从 Android Studio 的左下角访问。此外,当您要生成签名的 APK 时,android studio 会询问您要构建 APK 的风格。

此外,您可以为每种口味提供不同的资源。例如,您可以在src 目录中创建一个目录pro。目录结构应类似于main 目录。 (例如:如果您想为专业版设置不同的启动器图标,可以将其放在src\pro\res\drawable。当您切换到pro 风味时,这将替换位于src\main\res\drawable 的免费图标)。

如果您在上述pro 资源目录中创建strings.xml,则在构建pro 风格时,主strings.xml 和专业strings.xml 将合并以获得最终strings.xml。如果 free xml 和 pro xml 中都存在某个字符串 key,则该字符串值将从 pro xml 中获取。

如果您需要在代码中检查当前版本是专业版还是免费版,可以使用如下方法。

public boolean isPro() {
    return context.getPackageName().equals("com.example.my.app.pro");
}

更多信息,请参考this

【讨论】:

  • 这是迄今为止最好的解决方案,前提是使用 gradle build。
  • 我将从这个开始!看起来更优雅,不需要共享的东西
  • 如果我们有一个单独的穿戴模块或只有移动模块需要这个设置,是否也需要这个?
  • @JimmyKane 我还没有开发任何可穿戴模块,但似乎您可以在手持和可穿戴模块中找到自己的风格。所以,如果你也有免费和付费版本的磨损模块,你也应该在磨损模块中使用风味。看看这个答案。 stackoverflow.com/a/27405731/1015678
  • @LahiruChandima 我想通了。实际上,您需要两者才能更改包名称。如果代码库相同(版本等),您可以省略其余部分
【解决方案4】:

一个源代码两个应用程序(Eclipse)

在市场上管理具有两种呈现形式的应用程序时经常出现问题,可能两者之间只有一点差异(paidFor = true;)也许图标也不同。

这种方法使用了米海在http://blog.javia.org/android-package-name/ 解释的包名的描述,强调了用于管理源代码的包名和用于在 Android Market 上发布 apk 的包名之间的区别。本例中发布的两个包名是com.acme.superprogram和com.acme.superprogrampaid,Java源码包名是com.acme.superprogram。

在清单中,活动被列出并命名为 .ActivityName。 Mike Wallace 在他最近的演讲中指出,前面的点很重要,可以用完全合格的包来代替。例如“com.acme.superprogram.DialogManager”可以替换 manifest.xml 文本中的“.DialogManager”。

第 1 步是将所有活动 android:name 条目替换为这些完全限定的包名称,使用 java 源代码管理包名称 (com.acme.superprogram)。

然后可以更改清单包名称...

在 Eclipse 中,这会强制重新编译,并在 gen 文件夹中创建一个新的 R.java。这是有点棘手的地方;有两个文件夹 com.acme.superprogram 和 com.acme.superprogrampaid,只有一个有一个 R.java。只需将 R.java 复制到另一个文件夹,以便程序可以解析 R.layout.xyz 项。

当您更改包中的

我已经在几个应用程序上进行了尝试。我在模拟器、我的 2.1 手机上同时运行它们,它们都在 Android Market 上。

【讨论】:

  • 两个包之间只有一点点区别(一个paidFor 标志)不是很危险吗?您基本上是在免费版本中提供完整代码,代码开头有一个标志,即使代码被混淆,也很容易识别和更改?
  • 很好的答案,但遗憾的是,当我尝试导出时,gen 目录被擦除了 - 建议将其作为替代答案。
【解决方案5】:

以防万一您还有磨损模块,需要做额外的工作。

以下是一些用于打包磨损模块的示例 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')
}

模组磨损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 {

    ...

}

【讨论】:

    【解决方案6】:

    我遇到了同样的问题,我决定做两个安卓应用,一个叫 my_epic_app_free,另一个叫 my_epic_app_paid。我要做的只是更改付费版本,然后我有一个单独的 java 控制台程序,它所做的只是直接从磁盘访问所有 .java 文件,将它们复制到内存中,摆弄包名和清单行,然后将它们直接粘贴到免费应用程序中。我在桌面上按下按钮,所以当我完成付费版本的开发时,我按下按钮,然后编译免费版本。我有付费应用和免费应用相互通信,用户甚至可以安装它们,免费版本看到付费,并解锁到付费版本。

    另一个想法是让付费应用程序只是一个包含密钥文件的简单程序。付费应用程序如果运行,将自动运行免费版本,免费版本的行为与付费版本相同。

    免费应用程序检查付费应用程序的密钥文件是否存在,如果存在,它将解锁免费应用程序。这可以防止代码重复。软件包名称仍然必须不同,但理想情况下,付费应用不需要经常更改。

    好处:“升级”到付费版本时不会丢失用户数据/设置。

    缺点:如果用户先安装付费版,则必须引导他们安装免费版,这很麻烦,为什么他们必须为付费版安装两个应用程序?

    【讨论】:

      【解决方案7】:

      如果您不想使用库项目,并且对一些人在 cmets 中提到的风险感到满意,那么理论上 @user426990 提供了一个很好的答案。但是,在为导出进行新构建时,eclipse 似乎会擦除 gen 目录的全部内容(我发现作为一般原则很难与之争论)。

      基于相同原理的替代解决方案如下,假设您已经编写了 com.acme.superprogram 并且您希望创建 com.acme.superprogrampaid

      1. 确保您的清单按全名指向活动、服务等。根据@user426990 的回答,“.CoolActivity”必须列为 com.acme.superprogram.CoolActivity

      2. 在您的代码(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 文件,每个类需要一行。您可能需要子类化。

      3. 现在 (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 ...
        
      4. 根据@user426990,您现在可以将清单中的包从“com.acme.superprogram”更改为“com.acme.superprogrampaid”;此时您可能还想更改名称、启动图标和关键变量。

      5. 将 MyR 中的 import 行更改为 import com.acme.superprogrampaid.R

      你走了。

      ps。记得在最终导出对话框中更改文件名...

      【讨论】:

      • 如果你在编辑时不注意,这很容易成为一场灾难。不用了,谢谢
      【解决方案8】:

      更改软件包名称会有所帮助。
      只需使用 eclipse 更改您的付费应用程序的 包名称 并将其投放市场。

      这会解决你的问题。

      【讨论】:

      • 您将如何根据付费/免费检测/更改应用程序行为?
      【解决方案9】:

      为什么不创建两个不同的 Eclipse 项目呢?手动修复引用包名称的几件事并不难。注意更改导入语句和清单文件。无论如何,您需要编码两次。当然,在这两个地方都修复代码很麻烦。

      合适的解决方案是在市场上。它应该允许两个应用程序具有相同的包名称,并在安装另一个时要求替换一个。上传时,它应该检查包名是否真的来自同一软件供应商。

      雷内

      【讨论】:

      • 这是一个糟糕的解决方案。维护两个代码库远非理想,还有更优雅的解决方案。
      • +1 这并不理想,但对于任何使用适当源代码控制管理的人来说都是完全合理的
      • 没有人会选择这个解决方案。
      • 我不明白为什么这需要两个代码库。一个代码库可以有两个主文件,而 pro 主文件可以只是扩展主文件。所以必须有两种不同的构建方式,但都使用相同的代码库。
      猜你喜欢
      • 2012-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-09
      • 2016-04-12
      • 2014-06-19
      相关资源
      最近更新 更多