web-dev-qa-db-ja.com

Gradle:アプリで設定されるフラグを使用してAndroidライブラリでBuildConfigを使用する方法

私の(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をビルドする正しい方法は何ですか?

53
Lakedaemon

_BuildConfig.SOME_FLAG_はライブラリに適切に伝播されないため、目的の操作を実行できません。ビルドタイプ自体はライブラリに伝播されません。常にリリースとしてビルドされます。これはバグです https://code.google.com/p/Android/issues/detail?id=52962

回避するには:すべてのライブラリモジュールを制御できる場合は、callToBigLibraries()が触れるすべてのコードがProGuardできれいに切り離せるクラスとパッケージにあることを確認してから、リフレクションを使用できます。それらが存在する場合はアクセスでき、存在しない場合は適切にデグレードできます。基本的に同じことをしていますが、コンパイル時ではなく実行時のチェックを行っているため、少し難しくなります。

これを行う方法がわからない場合はお知らせください。必要に応じてサンプルを提供できます。

19
Scott Barta

回避策として、このメソッドを使用できます。このメソッドは、リフレクションを使用して、アプリ(ライブラリではなく)からフィールド値を取得します。

/**
 * 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 で共有しました。

42
Phil

次の解決策/回避策は私のために働く。 google issue trackerの誰かによって投稿されました:

libraryプロジェクトでpublishNonDefaulttrueに設定してみてください。

Android {
    ...
    publishNonDefault true
    ...
}

そして、ライブラリを使用しているappプロジェクトに次の依存関係を追加します。

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

このように、ライブラリを使用するプロジェクトには、ライブラリの正しいビルドタイプが含まれます。

32
AllThatICode

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);
}
4
Mark

アプリとライブラリの両方で静的な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です。

3
Kane O'Riley

両方を使う

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;
    }
}
1
ohlab

私にとって、これはAndroid APPLICATION BuildConfig.class:

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

*注意が必要なことがいくつかあります:

  1. getApplicationContext()-App ContexWrapper実装ではないコンテキストを返す可能性があります-Applicatioクラスが拡張するものを確認し、コンテキストラッピングの可能性を把握します
  2. 最終アプリによって返されるクラスは、それを使用するクラスローダーとは異なるクラスローダーによってロードできます-ローダーの実装と、ローダーの典型的な(階層、可視性)プリンシパルに依存します
    1. すべては、この場合の単純な委任のような実装に依存します!!! -ソリューションはより洗練されたものになる可能性があります-私はここでDELEGATIONパターンの使用法を示したいだけです:)

**すべてのリフレクションベースのパターンには弱点があり、ある特定の条件ではすべて失敗するため、なぜ私は反射ベースのパターンをすべてダウンウォートしました:

  1. Class.forName(className); -ローダーが指定されていないため
  2. context.getPackageName()+ ".BuildConfig"

    a)context.getPackageName()-「デフォルト-そうでなければb)を参照」はマニフェストで定義されたパッケージではなく、アプリケーションIDを返します(両方とも同じである場合があります)。マニフェストパッケージプロパティの使用方法とフロー-最後にaptツールはそれをアプリケーションIDに置き換えます(例えば、pkgが何を表すかについてはComponentNameクラスを参照してください)

    b)context.getPackageName()-implementaioが望むものを返します:P

***ソリューションをより完璧にするために何を変更するか

  1. クラスをその名前に置き換えて、クラスに関係する最終結果を取得するために異なるローダーでロードされた多くのクラスが使用される場合に発生する可能性のある問題を解決します(2つのクラスの等価性を説明するものを取得します(実行時のコンパイラーの場合)) -要するにクラスの平等は自己クラスではなく、ローダーとクラスによって構成されるペアを定義します。(いくつかの宿題-異なるローダーで内部クラスをロードし、外部クラスでアクセスしてみてください異なるローダーでロードされます)-不正なアクセスエラーが発生することが判明します:)内部クラスが同じパッケージに含まれていても、外部クラスへのアクセスを許可するすべての修飾子があります:)コンパイラ/リンカー「VM」はそれらを2つとして扱いません関連クラス...

暇があればすぐにここで遭遇する可能性のある問題についてもっと書きます...

0
ceph3us