Gradle:如何在Android-Library中使用buildConfig和在应用程序中设置的标志



我的(Gradle 1.10和Gradle插件0.8)基于Android项目由大型Android-library组成,它是3个不同的Android-Apps

的依赖性

在我的库中,我很想能够使用这样的结构

if (BuildConfig.SOME_FLAG) {
    callToBigLibraries()
}

作为proguard可以根据某些_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的正确方法是什么?解决方法,您可以使用此方法,该方法使用反射从应用程序获取字段值(而不是库):

/**
 * 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问题跟踪器上共享了该解决方案。

update update :与Android Gradle的较新版本插件publishNonDefault已弃用,没有效果。所有变体现在都发布。

以下解决方案/解决方法对我有用。它是由Google问题跟踪器中的某个人发布的:

尝试将publishNonDefault设置为库中的true 项目:

android {
    ...
    publishNonDefault true
    ...
}

并将以下依赖关系添加到使用库的 app 项目:

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

这样,使用库的项目包括库的正确构建类型。

您无法做您想做的事,因为BuildConfig.SOME_FLAG不会正确地传播到您的库;构建类型本身并没有传播到库 - 它们总是以发行版为生。这是bug https://code.google.com/p/android/issues/detail?id=52962

要围绕它进行工作:如果您可以控制所有图书馆模块,则可以确保callToBigLibraries()触摸的所有代码都在类和软件包中,您可以用Proguard干净地切割,然后使用反射,以便您以便您如果它们存在,可以访问它们,如果不存在,可以优雅地降解。您本质上是在做同样的事情,但是在运行时进行检查,而不是编译时间,这要困难。

让我知道您是否在弄清楚如何执行此操作时遇到了麻烦;如果需要的话,我可以提供一个样本。

我在应用程序和库中都使用static 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,并在没有上下文的任何地方访问它,而其他属性则相同。我这样做了,以便图书馆可以与我的所有应用程序一起使用,而无需通过其他方式传递上下文或设置包裹名称,而应用程序类只需要更改导入行才能在将其添加到新的新时才适合应用

编辑:我只想重申,这是从您的所有应用程序中将值分配给库中最终静态变量的最简单(仅在此处列出的)方法,而无需上下文或硬编码的包装名称在某个地方,几乎与默认库中的值一样好,无论如何,在每个应用程序中更改该导入行的最小维护。

对于ApplicationID与软件包不同的情况(即每个项目的多个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);
}

使用

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

对我而言,这是唯一一个可以接受的*解决方案来确定Android应用程序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() - 可以返回不是应用程序contexWrapper实现的上下文 - 查看应用程序类扩展的哪些&amp;了解上下文包装的可能性
  2. 最终应用程序返回的类可以由与使用它的类负载相比,可以加载不同的类加载程序 - 取决于Loader实现和一些典型的负载(ChierSarchy,可见性)
  3. 一切都取决于在这种情况下的实施简单委托!!! - 解决方案可能更复杂 - 我只想在这里显示委托模式的使用:)

**为什么我要击败所有基于反射的模式,因为它们都有弱点,并且在某些条件下它们都会失败:

  1. class.forname(className); - 由于没有指定的加载程序
  2. context.getpackagename() &quot; buildConfig'

a)context.getpackagename() - 默认默认 - 否则请参见b);返回不是在清单中定义的软件包,而是应用程序ID(shostine俩都是相同的),请查看清单软件包属性的使用及其流程 - 最终APT工具将用applicaton ID替换它(请参阅ComponentName类,例如PKG代表那里)

b)context.getPackageName() - 将返回Impartaio想要的内容:p

***在我的解决方案中要更改什么,以使其更加完美

  1. 替换类的名称,当许多访问不同的装载机访问/或用于获得涉及类的最终结果时,wchich可能会出现问题(请了解什么描述了两个类之间的平等性(对于编译器在运行时) - 简而言之通过带有不同加载程序的外部类) - 事实证明,我们将获得非法访问错误:)即使在同一软件包中,所有修改器都允许对其访问它,允许外部类访问:)编译器/链接器" vm&quort"将它们视为两个无关的类...

相关内容

  • 没有找到相关文章

最新更新