私の(gradle 1.10およびgradleプラグイン0.8)ベースのAndroidプロジェクトは、3つの異なるAndroidアプリの依存関係である大きなAndroidライブラリで構成されています
私のライブラリでは、このような構造を使用できるようになりたい
if (BuildConfig.SOME_FLAG) {
callToBigLibraries()
}
proguardは、SOME_FLAGの最終値に基づいて、生成されたapkのサイズを縮小できるため
しかし、私は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をビルドする正しい方法は何ですか?
_BuildConfig.SOME_FLAG
_はライブラリに適切に伝播されないため、目的の操作を実行できません。ビルドタイプ自体はライブラリに伝播されません。常にリリースとしてビルドされます。これはバグです https://code.google.com/p/Android/issues/detail?id=52962
回避するには:すべてのライブラリモジュールを制御できる場合は、callToBigLibraries()
が触れるすべてのコードがProGuardできれいに切り離せるクラスとパッケージにあることを確認してから、リフレクションを使用できます。それらが存在する場合はアクセスでき、存在しない場合は適切にデグレードできます。基本的に同じことをしていますが、コンパイル時ではなく実行時のチェックを行っているため、少し難しくなります。
これを行う方法がわからない場合はお知らせください。必要に応じてサンプルを提供できます。
回避策として、このメソッドを使用できます。このメソッドは、リフレクションを使用して、アプリ(ライブラリではなく)からフィールド値を取得します。
/**
* 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;
}
たとえば、DEBUG
フィールドを取得するには、Activity
からこれを呼び出します。
boolean debug = (Boolean) getBuildConfigValue(this, "DEBUG");
また、このソリューションを AOSP Issue Tracker で共有しました。
次の解決策/回避策は私のために働く。 google issue trackerの誰かによって投稿されました:
libraryプロジェクトでpublishNonDefault
をtrue
に設定してみてください。
Android {
...
publishNonDefault true
...
}
そして、ライブラリを使用しているappプロジェクトに次の依存関係を追加します。
dependencies {
releaseCompile project(path: ':library', configuration: 'release')
debugCompile project(path: ':library', configuration: 'debug')
}
このように、ライブラリを使用するプロジェクトには、ライブラリの正しいビルドタイプが含まれます。
ApplicationIdがパッケージと同じではない場合(つまり、プロジェクトごとに複数のapplicationIds)で、ライブラリプロジェクトからアクセスする場合:
Gradleを使用して、基本パッケージをリソースに保存します。
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}"
}
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);
}
アプリとライブラリの両方で静的な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の値を保持するのと同じくらい良いalmostです。
両方を使う
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;
}
}
// 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;
}
}
ライブラリの使用:
Class<?> buildConfigClass = BaseApp.getAppBuildConfigClass(Context);
if(buildConfigClass !- null) {
// do your job
}
*注意が必要なことがいくつかあります:
**すべてのリフレクションベースのパターンには弱点があり、ある特定の条件ではすべて失敗するため、なぜ私は反射ベースのパターンをすべてダウンウォートしました:
context.getPackageName()+ ".BuildConfig"
a)context.getPackageName()-「デフォルト-そうでなければb)を参照」はマニフェストで定義されたパッケージではなく、アプリケーションIDを返します(両方とも同じである場合があります)。マニフェストパッケージプロパティの使用方法とフロー-最後にaptツールはそれをアプリケーションIDに置き換えます(例えば、pkgが何を表すかについてはComponentNameクラスを参照してください)
b)context.getPackageName()-implementaioが望むものを返します:P
***ソリューションをより完璧にするために何を変更するか
暇があればすぐにここで遭遇する可能性のある問題についてもっと書きます...