Mon (gradle 1.10 et gradle plugin 0.8) -based Android project se compose d'une grande bibliothèque Android qui est une dépendance pour 3 applications Android différentes
Dans ma bibliothèque, j'aimerais pouvoir utiliser une structure comme celle-ci
if (BuildConfig.SOME_FLAG) {
callToBigLibraries()
}
comme proguard pourrait réduire la taille de l'apk produit, basé sur la valeur finale de SOME_FLAG
Mais je ne peux pas comprendre comment le faire avec gradle comme:
* 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.
J'ai essayé sans succès de jouer avec des BuildTypes et des trucs comme
release {
// packageNameSuffix "library"
buildConfigField "boolean", "SOME_FLAG", "true"
}
debug {
//packageNameSuffix "library"
buildConfigField "boolean", "SOME_FLAG", "true"
}
Quelle est la bonne façon de créer un BuildConfig partagé pour ma bibliothèque et mes applications dont les indicateurs seront remplacés lors de la création dans les applications?
Vous ne pouvez pas faire ce que vous voulez, car BuildConfig.SOME_FLAG
Ne sera pas propagé correctement dans votre bibliothèque; les types de build eux-mêmes ne sont pas propagés aux bibliothèques - ils sont toujours construits en tant que RELEASE. C'est un bug https://code.google.com/p/Android/issues/detail?id=52962
Pour contourner ce problème: si vous avez le contrôle sur tous les modules de la bibliothèque, vous pouvez vous assurer que tout le code touché par callToBigLibraries()
est dans des classes et des packages que vous pouvez nettoyer proprement avec ProGuard, puis utilisez la réflexion afin que vous puissiez y accéder s'ils existent et se dégrader gracieusement s'ils ne le font pas. Vous faites essentiellement la même chose, mais vous effectuez la vérification au moment de l'exécution au lieu de la compilation, et c'est un peu plus difficile.
Faites-moi savoir si vous avez du mal à trouver comment procéder; Je pourrais vous fournir un échantillon si vous en avez besoin.
Pour contourner ce problème, vous pouvez utiliser cette méthode, qui utilise la réflexion pour obtenir la valeur du champ depuis l'application (et non la bibliothèque):
/**
* Gets a field from the project's 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;
}
Pour obtenir le champ DEBUG
, par exemple, il suffit d'appeler cela depuis votre Activity
:
boolean debug = (Boolean) getBuildConfigValue(this, "DEBUG");
J'ai également partagé cette solution sur le AOSP Issue Tracker .
La solution/solution de contournement suivante fonctionne pour moi. Il a été publié par un gars dans le suivi des problèmes de Google:
Essayez de définir publishNonDefault
sur true
dans le projet bibliothèque:
Android {
...
publishNonDefault true
...
}
Et ajoutez les dépendances suivantes au projet app qui utilise la bibliothèque:
dependencies {
releaseCompile project(path: ':library', configuration: 'release')
debugCompile project(path: ':library', configuration: 'debug')
}
De cette façon, le projet qui utilise la bibliothèque inclut le type de génération correct de la bibliothèque.
Dans le cas où l'ID d'application n'est pas le même que le package (c'est-à-dire plusieurs ID d'application par projet) ET que vous souhaitez accéder à partir d'un projet de bibliothèque:
Utilisez Gradle pour stocker le package de base dans les ressources.
Dans main/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}"
}
En 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);
}
J'utilise une classe BuildConfigHelper statique dans l'application et la bibliothèque, afin de pouvoir définir les packages BuildConfig comme variables statiques finales dans ma bibliothèque.
Dans l'application, placez une classe comme celle-ci:
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;
}
Et dans la bibliothèque:
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;
}
}
}
Ensuite, n'importe où dans votre bibliothèque où vous souhaitez vérifier BuildConfig.DEBUG, vous pouvez vérifier BuildConfigHelper.DEBUG et y accéder de n'importe où sans contexte, et de même pour les autres propriétés. Je l'ai fait de cette façon pour que la bibliothèque fonctionne avec toutes mes applications, sans avoir besoin de passer un contexte ou de définir le nom du package d'une autre manière, et la classe d'application n'a besoin que de modifier la ligne d'importation pour l'adapter à l'ajout dans un nouveau application
Edit: je voudrais simplement réitérer que c'est la façon la plus simple (et la seule répertoriée ici) d'obtenir les valeurs à affecter aux variables statiques finales de la bibliothèque à partir de toutes vos applications sans avoir besoin d'un contexte ou d'un codage en dur de la le nom du package quelque part, qui est presque aussi bon que d'avoir les valeurs dans la bibliothèque par défaut BuildConfig de toute façon, pour l'entretien minimal de changer cette ligne d'importation dans chaque application.
utilise les deux
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"
}
}
Je veux appeler comme ci-dessous
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;
}
}
// base entry point
// abstract application
// which defines the method to obtain the desired class
// the definition of the application is contained in the library
// that wants to access the method or in a superior library package
public abstract class BasApp extends Android.app.Application {
/*
* GET BUILD CONFIG CLASS
*/
protected Class<?> getAppBuildConfigClass();
// HELPER METHOD TO CAST CONTEXT TO BASE APP
public static BaseApp getAs(Android.content.Context context) {
BaseApp as = getAs(context, BaseApp.class);
return as;
}
// HELPER METHOD TO CAST CONTEXT TO SPECIFIC BASEpp INHERITED CLASS TYPE
public static <I extends BaseApp> I getAs(Android.content.Context context, Class<I> forCLass) {
Android.content.Context applicationContext = context != null ?context.getApplicationContext() : null;
return applicationContext != null && forCLass != null && forCLass.isAssignableFrom(applicationContext.getClass())
? (I) applicationContext
: null;
}
// STATIC HELPER TO GET BUILD CONFIG CLASS
public static Class<?> getAppBuildConfigClass(Android.content.Context context) {
BaseApp as = getAs(context);
Class buildConfigClass = as != null
? as.getAppBuildConfigClass()
: null;
return buildConfigClass;
}
}
// FINAL APP WITH IMPLEMENTATION
// POINTING TO DESIRED CLASS
public class MyApp extends BaseApp {
@Override
protected Class<?> getAppBuildConfigClass() {
return somefinal.app.package.BuildConfig.class;
}
}
TILISATION DANS LA BIBLIOTHÈQUE:
Class<?> buildConfigClass = BaseApp.getAppBuildConfigClass(Context);
if(buildConfigClass !- null) {
// do your job
}
* il y a quelques choses à surveiller:
** pourquoi j'ai rétrogradé tous les modèles basés sur la réflexion car ils ont tous des points faibles et ils échouent tous dans certaines conditions:
context.getPackageName () + ".BuildConfig"
a) context.getPackageName () - "par défaut - sinon voir b)" renvoie non le package défini dans le manifeste mais l'identifiant de l'application (parfois ils sont tous les deux les mêmes), voyez comment la propriété du package manifeste est utilisée et son flux - à la fin L'outil apt le remplacera par l'ID applicaton (voir la classe ComponentName par exemple ce que le paquet représente là)
b) context.getPackageName () - retournera ce que l'implémentaio veut: P
*** quoi changer dans ma solution pour la rendre plus parfaite
j'écrirai plus sur les problèmes que nous pourrions rencontrer ici sous peu quand j'aurai du temps libre ...