Создание бесплатных / платных версий приложения из одного кода



Так я иду вниз, чтобы освободить время для моего приложения. Мы планируем выпустить две версии, бесплатную версию play-to-unlock на основе рекламы и платную полностью разблокированную версию. У меня есть настроенный код, который я могу просто установить флаг при запуске, чтобы включить/отключить рекламу и заблокировать/разблокировать все функции. Так буквально только одна строка кода будет выполняться по-разному между этими версиями.



чтобы выпустить два отдельных приложения, они требуют разных имен пакетов, поэтому мой вопрос есть ли простой способ рефакторинга имени пакета моего приложения? Инструмент рефакторинга Eclipse не разрешает сгенерированный файл R или любые ссылки XML в файлах макета и манифеста. Я пытался сделать новый проект, используя оригинал в качестве источника, но я не могу ссылаться на активы и ресурсы, и я ищу, чтобы избежать дублирования кода и активов. Это не огромная боль, чтобы рефакторинг это вручную, но я чувствую, что там должен быть лучший способ, чтобы сделать это. У кого-нибудь есть элегантное решение это?



Редактировать/Ответил:



для моей ситуации я считаю вполне приемлемым просто использовать Project - > Android Tools - > Rename Application Package. Я не знал, что это существует, и я чувствую себя идиотом, чтобы опубликовать это сейчас. Спасибо за ответы и комментарии каждого, не стесняйтесь голосовать за это закрыто.

256   7  

7 ответов:

возможно, дубликат массовая публикация приложений для Android.

проекты библиотеки Android сделают это для вас красиво. Вы получите 1 проект библиотеки, а затем проект для каждого издания (бесплатный/полный) с теми, кто действительно содержит разные ресурсы, такие как значки приложений и разные манифесты, где имя пакета будет изменяться.

надеюсь, что это поможет. Это хорошо сработало для меня.

Это очень просто с помощью строить.gradle в Android Studio. Читайте о productFlavors. Это очень полезная функция. Просто добавьте следующие строки в build.Gradle в:

productFlavors {
    lite {
        packageName = 'com.project.test.app'
        versionCode 1
        versionName '1.0.0'
    }
    pro {
        packageName = 'com.project.testpro.app'
        versionCode 1
        versionName '1.0.0'
    }
}

в этом примере я добавляю два вкуса продукта: первый для облегченной версии и второй для полной версии. Каждая версия имеет свой собственный код версии и имя версии (для публикации Google Play).

в коде просто проверьте BuildConfig.Вкус:

if (BuildConfig.FLAVOR == "lite") {
   // add some ads or restrict functionallity

}

для работы и тестирование на устройстве используйте вкладку "варианты сборки" в Android Studio для переключения между версиями: enter image description here

лучший способ-использовать "Android Studio" - > gradle.build - > [productFlavors + создать файл манифеста из шаблона]. Эта комбинация позволяет создавать бесплатные / платные версии и кучу выпусков для разных рынков приложений из одного источника.


это часть шаблонного файла манифеста:


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

это шаблон "ProductInfo.шаблон " для 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};
    }

этот манифест обрабатывается градля.построить скрипт с помощью productFlavors и processManifest задач крюк:


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

это отдельная задача для обработки файла


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

обновление: processTemplateFile создан для повторного использования кода.

Gradle позволяет использовать сгенерированный BuildConfig.java для передачи некоторых данных в код.

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

для всех, кто хочет использовать решение Дениса:
В новой версии gradle packageName теперь applicationId и не забудьте поставить productFlavors { ... } на android { ... }

productFlavors {
    lite {
        applicationId = 'com.project.test.app'
        versionCode 1
        versionName '1.0.0'
    }
    pro {
        applicationId = 'com.project.testpro.app'
        versionCode 1
        versionName '1.0.0'
    }
}

один из подходов, с которым я экспериментирую,-это использование полных имен для действий и просто изменение атрибута пакета. Это позволяет избежать реального рефакторинга (1 копия файла, 1 текстовый суб).

Это почти работает, но сгенерированный класс R не подобран, так как пакет для этого вытащен из AndroidManifest.xml, так что заканчивается в новом пакете.

Я думаю, что это должно быть довольно прямо вперед, чтобы построить AndroidManifest.XML с помощью правил Муравей (в -предварительно построить), что вставляет имя пакета распространения, а затем (в процессе предварительной компиляции) созданные ресурсы в пакет по умолчанию (Java).

надеюсь, это поможет,

Фил Лелло

Если вы хотите другое имя приложения, в зависимости от вкуса, вы можете также добавить это:

productFlavors {
    lite {
        applicationId = 'com.project.test.app'
        resValue "string", "app_name", "test lite"
        versionCode 1
        versionName '1.0.0'
    }
    pro {
        applicationId = 'com.project.testpro.app'
        resValue "string", "app_name", "test pro"
        versionCode 1
        versionName '1.0.0'
    }
}

Comments

    Ничего не найдено.