Подтвердить что ты не робот

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

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

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

Edit/Ответил:

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

Это очень просто, используя build.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'
    }
}

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

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

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

}

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

Ответ 3

Лучший способ - использовать "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.template" для 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};
    }

Этот манифест обрабатывается gradle.build script с помощью 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, созданный для повторного использования кода.

Ответ 4

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

Ответ 5

Для всех, кто хочет использовать решение от Denis:
В новой версии 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'
    }
}

Ответ 6

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

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

Я думаю, что довольно просто построить AndroidManifest.xml с помощью правила Ant (в -re-build), который вставляет имя дистрибутивного пакета, а затем (в -pre-compile) сгенерированные ресурсы в default (Java).

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

Фил Лелло

Ответ 7

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

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