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

Gradle: как использовать BuildConfig в android-библиотеке с флагом, который устанавливается в приложении

Мой (gradle 1.10 и gradle плагин 0.8), основанный на андроидном проекте, состоит из большой андроидной библиотеки, которая является зависимостью для 3 различных приложений для Android.

В моей библиотеке я хотел бы иметь возможность использовать такую ​​структуру

if (BuildConfig.SOME_FLAG) {
    callToBigLibraries()
}

поскольку proguard сможет уменьшить размер произведенного apk, исходя из конечного значения SOME_FLAG

Но я не могу понять, как это сделать с помощью gradle как:

* the BuildConfig produced by the library doesn't have the same package name than the app
* I have to import the BuildConfig with the library package in the library
* The apk of an apps includes the BuildConfig with the package of the app but not the one with the package of the library.

Я безуспешно пытался играть с BuildTypes и т.д.

release {
    // packageNameSuffix "library"
    buildConfigField "boolean", "SOME_FLAG", "true"
}
debug {
    //packageNameSuffix "library"
    buildConfigField "boolean", "SOME_FLAG", "true"
}

Каков правильный способ создания общего BuildConfig для моей библиотеки и моих приложений, флаги которых будут переопределены при создании приложений?

4b9b3361

Ответ 1

Вы не можете делать то, что хотите, потому что BuildConfig.SOME_FLAG не будет правильно распространяться в вашей библиотеке; сами типы сборки не распространяются на библиотеки - они всегда создаются как RELEASE. Это ошибка https://code.google.com/p/android/issues/detail?id=52962

Чтобы обойти это: если у вас есть контроль над всеми библиотечными модулями, вы можете убедиться, что весь код, затронутый callToBigLibraries(), находится в классах и пакетах, которые можно очистить с помощью ProGuard, а затем использовать отражение так что вы можете получить к ним доступ, если они существуют, и грамотно деградировать, если они этого не сделают. Вы по существу делаете то же самое, но вы делаете проверку во время выполнения вместо времени компиляции, и это немного сложнее.

Сообщите мне, если вам трудно понять, как это сделать; Я мог бы предоставить образец, если вам это нужно.

Ответ 2

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

/**
 * Gets a field from the project BuildConfig. This is useful when, for example, flavors
 * are used at the project level to set custom fields.
 * @param context       Used to find the correct file
 * @param fieldName     The name of the field-to-access
 * @return              The value of the field, or {@code null} if the field is not found.
 */
public static Object getBuildConfigValue(Context context, String fieldName) {
    try {
        Class<?> clazz = Class.forName(context.getPackageName() + ".BuildConfig");
        Field field = clazz.getField(fieldName);
        return field.get(null);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return null;
}

Чтобы получить поле DEBUG, например, просто вызовите это из своего Activity:

boolean debug = (Boolean) getBuildConfigValue(this, "DEBUG");

Я также поделился этим решением с AOSP Issue Tracker.

Ответ 3

Для меня работает следующее решение/обходное решение. Он был опубликован каким-то парнем в трекере Google:

Попробуйте установить publishNonDefault на true в проекте библиотека:

android {
    ...
    publishNonDefault true
    ...
}

И добавьте следующие зависимости к проекту app, который использует библиотеку:

dependencies {
    releaseCompile project(path: ':library', configuration: 'release')
    debugCompile project(path: ':library', configuration: 'debug')
}

Таким образом, проект, который использует библиотеку, включает правильный тип сборки библиотеки.

Ответ 4

В случае, когда applicationId не совпадает с пакетом (т.е. несколько приложений для каждого проекта) И вы хотите получить доступ к проекту библиотеки:

Используйте Gradle для хранения базового пакета в ресурсах.

В главном /AndroidManifest.xml:

android {
    applicationId "com.company.myappbase"
    // note: using ${applicationId} here will be exactly as above
    // and so NOT necessarily the applicationId of the generated APK
    resValue "string", "build_config_package", "${applicationId}"
}

В Java:

public static boolean getDebug(Context context) {
    Object obj = getBuildConfigValue("DEBUG", context);
    if (obj instanceof Boolean) {
        return (Boolean) o;
    } else {
        return false;
    }
}

private static Object getBuildConfigValue(String fieldName, Context context) {
    int resId = context.getResources().getIdentifier("build_config_package", "string", context.getPackageName());
    // try/catch blah blah
    Class<?> clazz = Class.forName(context.getString(resId) + ".BuildConfig");
    Field field = clazz.getField(fieldName);
    return field.get(null);
}

Ответ 5

Я использую статический класс BuildConfigHelper как в приложении, так и в библиотеке, поэтому у меня могут быть установлены пакеты BuildConfig в качестве конечных статических переменных в моей библиотеке.

В приложении поместите класс следующим образом:

package com.yourbase;

import com.your.application.BuildConfig;

public final class BuildConfigHelper {

    public static final boolean DEBUG = BuildConfig.DEBUG;
    public static final String APPLICATION_ID = BuildConfig.APPLICATION_ID;
    public static final String BUILD_TYPE = BuildConfig.BUILD_TYPE;
    public static final String FLAVOR = BuildConfig.FLAVOR;
    public static final int VERSION_CODE = BuildConfig.VERSION_CODE;
    public static final String VERSION_NAME = BuildConfig.VERSION_NAME;

}

И в библиотеке:

package com.your.library;

import android.support.annotation.Nullable;

import java.lang.reflect.Field;

public class BuildConfigHelper {

    private static final String BUILD_CONFIG = "com.yourbase.BuildConfigHelper";

    public static final boolean DEBUG = getDebug();
    public static final String APPLICATION_ID = (String) getBuildConfigValue("APPLICATION_ID");
    public static final String BUILD_TYPE = (String) getBuildConfigValue("BUILD_TYPE");
    public static final String FLAVOR = (String) getBuildConfigValue("FLAVOR");
    public static final int VERSION_CODE = getVersionCode();
    public static final String VERSION_NAME = (String) getBuildConfigValue("VERSION_NAME");

    private static boolean getDebug() {
        Object o = getBuildConfigValue("DEBUG");
        if (o != null && o instanceof Boolean) {
            return (Boolean) o;
        } else {
            return false;
        }
    }

    private static int getVersionCode() {
        Object o = getBuildConfigValue("VERSION_CODE");
        if (o != null && o instanceof Integer) {
            return (Integer) o;
        } else {
            return Integer.MIN_VALUE;
        }
    }

    @Nullable
    private static Object getBuildConfigValue(String fieldName) {
        try {
            Class c = Class.forName(BUILD_CONFIG);
            Field f = c.getDeclaredField(fieldName);
            f.setAccessible(true);
            return f.get(null);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

}

Затем в любом месте вашей библиотеки, где вы хотите проверить BuildConfig.DEBUG, вы можете проверить BuildConfigHelper.DEBUG и получить доступ к нему из любого места без контекста, а также для других свойств. Я сделал это так, чтобы библиотека работала со всеми моими приложениями, без необходимости передавать контекст или устанавливать имя пакета каким-либо другим способом, а классу приложения требуется только строка импорта, которая подходит для добавления в новую Приложение

Изменить: я просто хотел бы повторить, что это самый простой (и только один из перечисленных здесь) способ получить значения для конечных статических переменных в библиотеке из всех ваших приложений без необходимости использования контекста или жесткое кодирование имени пакета где-то, что почти так же хорошо, как наличие значений в стандартной библиотеке BuildConfig по умолчанию, для минимальной поддержки изменения этой строки импорта в каждом приложении.

Ответ 6

используйте оба

my build.gradle
// ...
productFlavors {
    internal {
        // applicationId "com.elevensein.sein.internal"
        applicationIdSuffix ".internal"
        resValue "string", "build_config_package", "com.elevensein.sein"
    }

    production {
        applicationId "com.elevensein.sein"
    }
}

Я хочу позвонить, как показано ниже

Boolean isDebug = (Boolean) BuildConfigUtils.getBuildConfigValue(context, "DEBUG");

BuildConfigUtils.java

public class BuildConfigUtils
{

    public static Object getBuildConfigValue (Context context, String fieldName)
    {
        Class<?> buildConfigClass = resolveBuildConfigClass(context);
        return getStaticFieldValue(buildConfigClass, fieldName);
    }

    public static Class<?> resolveBuildConfigClass (Context context)
    {
        int resId = context.getResources().getIdentifier("build_config_package",
                                                         "string",
                                                         context.getPackageName());
        if (resId != 0)
        {
            // defined in build.gradle
            return loadClass(context.getString(resId) + ".BuildConfig");
        }

        // not defined in build.gradle
        // try packageName + ".BuildConfig"
        return loadClass(context.getPackageName() + ".BuildConfig");

    }

    private static Class<?> loadClass (String className)
    {
        Log.i("BuildConfigUtils", "try class load : " + className);
        try { 
            return Class.forName(className); 
        } catch (ClassNotFoundException e) { 
            e.printStackTrace(); 
        }

        return null;
    }

    private static Object getStaticFieldValue (Class<?> clazz, String fieldName)
    {
        try { return clazz.getField(fieldName).get(null); }
        catch (NoSuchFieldException e) { e.printStackTrace(); }
        catch (IllegalAccessException e) { e.printStackTrace(); }
        return null;
    }
}