开发者

Best way to have paid and free version of an Android app [closed]

开发者 https://www.devze.com 2023-01-15 23:28 出处:网络
Closed. This question needs to be more focused. It is not currently accepting answers. Want to improve this question? Update the question so it focuses on one problem only by editing this
Closed. This question needs to be more focused. It is not currently accepting answers.

Want to improve this question? Update the question so it focuses on one problem only by editing this post.

Closed 4 years ago.

Improve this question

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.

What's the cleanest way of doing 开发者_JAVA技巧this?


The Android SDK formally addresses the issue of a shared or common codebase with something called a library project.

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.

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.

Good luck.

Daniel


Several approaches exist, but you usually don't see the drawbacks until you try them for yourself. Here are my experiences:

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

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

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

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

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

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.


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.

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.

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.

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

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.

If you need to check whether current version is pro or free in code, you can use a method like following.

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

For more information, Refer this


One source code two apps (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.

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.

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.

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

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.

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.


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.

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

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 {

    ...

}


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.

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?


Changing the package name will help.
Just change the package name of your paid app using eclipse and put it in market.

That will solve your problem.


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

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

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

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

  3. 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 ...
    
  4. 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.

  5. Change the import line in MyR to import com.acme.superprogrampaid.R

And away you go.

ps. Remember to change the file name in the final export dialog box ...


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.

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

0

精彩评论

暂无评论...
验证码 换一张
取 消