54 votes

Créer des versions gratuites / payantes de l'application à partir du même code

Donc, je suis venue vers le bas pour libérer à temps pour mon application. Nous prévoyons sur la libération de deux versions, une gratuite des annonces à jouer à débloquer la version, et un payée entièrement débloqué version. J'ai le code, que je peux simplement mettre un drapeau sur le démarrage pour activer/désactiver les annonces et verrouiller/déverrouiller toutes les fonctionnalités. Donc, littéralement une seule ligne de code s'exécute différemment entre ces versions.

Afin de libérer deux applications distinctes, ils ont besoin de différents noms de paquets, donc ma question est la suivante: est-il un moyen facile de refactoriser le code de mon application nom du package? Eclipse du refactoring de l'outil ne permet pas de résoudre le générés R fichier, ou tout XML références dans la mise en page et les fichiers manifeste. J'ai tenté de faire un nouveau projet à l'aide de l'original comme source, mais je ne peut pas référence à l'actif et les ressources, et je suis à la recherche afin d'éviter de reproduire toute partie de mon code et des ressources. Ce n'est pas une énorme douleur à refactoriser le code manuellement, mais je pense qu'il doit y avoir une meilleure façon de le faire. Quelqu'un a une solution élégante à ce?

Modifier/Répondu:

Pour ma situation, je trouve qu'il est parfaitement acceptable d'utiliser le Projet -> Android Outils -> Renommer le dossier de Demande. Je savais pas que ça existait, et je me sens comme un idiot pour l'affichage de cette maintenant. Merci pour tous les réponses et les commentaires, n'hésitez pas à voter ce fermé.

17voto

mmeyer Points 2773

Éventuellement, une copie de Vrac de Publication d'Applications Android.

Android de projets de Bibliothèque va le faire pour vous aussi bien. Vous vous retrouverez avec 1 bibliothèque de projet et d'un projet pour chaque édition (gratuit/complet) avec ceux qui sont vraiment juste contenant différentes ressources comme les icônes d'application et les différents manifestes, qui est l'endroit où le nom du paquet sera varié.

Espérons que cela aide. Il a bien fonctionné pour moi.

8voto

galex Points 308

Gradle permet d'utiliser BuildConfig.java généré pour transmettre certaines données au code.

 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"
    }
 

8voto

galex Points 308

La meilleure façon est d'utiliser des "Android Studio" -> gradle.build -> [productFlavors + générer le fichier de manifeste de modèle]. Cette combinaison permet de construire des gratuit/payant versions et des tas d'éditions pour les différents marchés d'applications à partir d'une seule source.


C'est une partie de type "modèle" fichier manifeste:


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

C'est un modèle "ProductInfo.modèle" pour le fichier java: 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};
    }

Ce manifeste est traitée par gradle.le script de construction avec productFlavors et processManifest tâche crochet:


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")
    }
}

Il est séparé de la tâche à traiter


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

Mise à jour: processTemplateFile créé pour le code de la réutilisation à des fins.

0voto

Phil Lello Points 4582

Une approche que j'suis en train d'expérimenter avec l'aide complet des noms pour les activités, et en changeant simplement le paquet attribut. Il évite tout réel refactoring (1 copie de fichier, 1 texte sous).

Cela fonctionne presque, mais l'généré classe R n'est pas ramassé, comme le programme de ce qui est sorti de AndroidManifest.xml, afin de se retrouver dans le nouveau package.

Je pense qu'il devrait être assez simple à construire AndroidManifest.xml via une Fourmi de la règle (en pré-construction) qui insère le package de distribution de nom, et ensuite (en pré-compiler) les ressources générées dans le répertoire par défaut (Java) package.

Espérons que cela aide,

Phil Lello

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X