在项目开发中经常使用 BuildConfig.DEBUG 判断是否是 Debug 模式,从而在 Debug 模式下才开启的特殊的操作,如打印日志。好处就是可以不用手动去修改,这个值就可以在 Debug 模式下为 true ,Release 模式下为 false。

1.问题

在有多个Module的情况下,在被依赖的的库 如 lib,在 lib 内有工具类 LogUtils,使用 BuildConfig.DEBUG 作为判断,如下:

1
2
3
4
5
public static void d(String str) {
if (BuildConfig.DEBUG) {
Log.d("test", str);
}
}

会发现始终无法打印日志,因为其中的 BuildConfig.DEBUG 返回的值始终为 false。

2.原因

BuildConfig.java 是编译时自动生成的,并且每个 Module 都会生成一份,以该 Module 的 packageName 为 BuildConfig.java 的 packageName。所以如果你的应用有多个 Module 就会有多个 BuildConfig.java 生成,而上面的 Lib Module import 的是自己的 BuildConfig.java,编译时被依赖的 Module 默认会提供 Release 版给其他 Module 或工程使用,这就导致该 BuildConfig.DEBUG 会始终为 false。

3.解决方法

这里参考 Trinea 的 解决方案

  • 使用外层 运行 app 的 BuildConfig.DEBUG
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void syncIsDebug(Context context) {
if (isDebug == null) {
try {
String packageName = context.getPackageName();
Class buildConfig = Class.forName(packageName + ".BuildConfig");
Field DEBUG = buildConfig.getField("DEBUG");
DEBUG.setAccessible(true);
isDebug = DEBUG.getBoolean(null);
} catch (Throwable t) {
// Do nothing
}
}
}

通过反射得到真正执行的 Module 的 BuildConfig,在自己的Application内调用。但这种解决方案还是有问题,因为 BuildConfig.java 的 packageName 是 Module 的 Package Name,即 AndroidManifest.xml 中的 package 属性,而 context.getPackageName() 得到的是应用的 applicationId,这个 applicationId 通过 build.gradle 是可以修改的。所以当 build.gradle 中的 applicationId 与 AndroidManifest.xml 中的 package 属性不一致时,上面的反射查找类路径便会出错。

  • 被依赖的 Module 提供其他版本

让被依赖的 Module 提供除 Release 版以外的其他版本,这种方案需要将所有被依赖 library 中添加:

1
2
3
android {
publishNonDefault true
}

表示该 Module 打包时会同时打包其他版本,包括 Debug 版。并且需要在 App Module 中将其依赖的 library 如下逐个添加:

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

表示依赖不同版本的依赖 Module。
然而这种方式所有 Module 配置都需要修改,侵入性太强

  • Trinea 提出的最终解决方案:使ApplicationInfo.FLAG_DEBUGGABLE

既然 BuildConfig 的方式行不通,我们反编译 Debug 包和 Release 包对比看看有没有其他的区别,会发现他们 AndroidManifest.xml 中 application 节点的 android:debuggable 值是不同的。Debug 包值为 true,Release 包值为 false,这是编译自动修改的。所以我们考虑通过 ApplicationInfo 的这个属性去判断是否是 Debug 版本,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class AppUtils {
private static Boolean isDebug = null;
public static boolean isDebug() {
return isDebug == null ? false : isDebug.booleanValue();
}
/**
* Sync lib debug with app's debug value. Should be called in module Application
*
* @param context
*/
public static void syncIsDebug(Context context) {
if (isDebug == null) {
isDebug = context.getApplicationInfo() != null &&
(context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
}

然后在自己的 Application 内调用进行初始化

1
AppUtils.syncIsDebug(getApplicationContext());

这样以后调用 AppUtils.isDebug() 即可判断是否是 Debug 版本,比如在上面的 LogUtils 中。同时适用于 Module 是 Lib 和 applicationId 被修改的情况,比 BuildConfig.DEBUG 靠谱的多。

这个方案有个注意事项就是自己 App Module 中不能主动设置 android:debuggable,否则无论 Debug 还是 Release 版会始终是设置的值。当然本身就没有自动设置的必要。