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 для моей библиотеки и моих приложений, флаги которых будут переопределены при создании приложений?
Ответы
Ответ 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;
}
}