最近在做项目分层化的一些工作,具体思路是将原有项目一些基础服务模块和设备服务模块抽离出来,上传到本地的 Maven 服务器,然后再通过在主项目中的 build.gradle 文件中通过
compile
语句进行导包处理。但通过这种方法编译成的 aar 包只能是 release 版本,无法使用到BuildConfig
动态设置的一些常量,比如常见的「Debug 日志开关」,我们一般都是在日志类通过BuildConfig.DEBUG
来获取包状态,从而设置是否要打印出日志。
这里特别提一下为什么要用 Maven 这种方式,实际上单纯的抽离模块可以通过子模组的方式,Git 也有 submodule 这样的子模组管理方法,但用 Git 管理子模组会有很多问题,比如项目引用子模组的指针问题,在一个并行开发团队中,子模组指针往往会导致很多出人意料的问题,以后我会专门的在这方面写一篇博客做一个解析。
通过把子模块上传至 Maven 库有很多好处,比如导入起来很方便,只需要在配置文件中 compile 即可,而且开发该模块的时候只需要单独打开该子模组的代码,不像通过 Git 管理的依赖子模组,即使你只是为了修改子模组代码,也需要打开原来的完整项目,然后在项目下的子 module 进行开发,最后子模组代码和主项目代码得同时提交。
compile 'com.github.bumptech.glide:okhttp-integration:1.4.0'
另外它还有一些安全性的好处。通过这种方式,实际上也是一种对代码的保护,客户端开发人员不会轻易的修改这部分代码。
具体如何通过 Android studio 构建 maven 库,请移步 Gradle实战:发布aar包到maven仓库
我把项目中的一些基础服务专门抽离了出来,其中就包括了一些跟 Log 日志打印相关的代码,原来项目中在build.gradle 中的 buildTypes 中设置了 DEVELOP_MODE
常量,来控制在不同渠道打包下的 Log 开关。
buildTypes {
debug {
// log日志开关
buildConfigField("boolean", "DEVELOP_MODE", "true")
}
release{
buildConfigField("boolean", "DEVELOP_MODE", "false")
}
然后,在具体的日志类中调用,动态获取 DEVELOP_MODE
值
public class DEBUG {
public final static boolean DEVELOP_MODE = BuildConfig.DEVELOP_MODE;
}
现在,当我把这部分代码单独抽离出来到名叫「middleWare」的子 library 后,即使我仍然可以在该 library 中的 build.gradle 文件设置上述的 buildTypes 代码,但通过 maven 编译出来的 aar 包只能是 release 版本,自然也无法获取到那些动态配置的常量值。
所以,只能通过获取引入项目的 BuildConfig 类来获取了,所以我们自然的想到了用反射。下面是具体代码,注释也比较详细:
public class BuildConfigProvider {
private static Context sContext;
private static String packageName;
/**
* 通过反射获取ApplicationContext
*
* @return
*/
private static Context getContext() {
if (sContext == null) {
try {
final Class> activityThreadClass = Class.forName("android.app.ActivityThread");
final Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
final Object activityThread = currentActivityThread.invoke(null);
final Method getApplication = activityThreadClass.getDeclaredMethod("getApplication");
final Application application = (Application) getApplication.invoke(activityThread);
sContext = application.getApplicationContext();
} catch (Exception e) {
e.printStackTrace();
}
}
return sContext;
}
/**
* 通过反射获取包名
*
* @return
*/
private static String getPackageName() {
if (packageName == null) {
try {
final Class> activityThreadClass = Class.forName("android.app.ActivityThread");
final Method currentPackageName = activityThreadClass.getDeclaredMethod("currentPackageName");
packageName = (String) currentPackageName.invoke(null);
} catch (Exception e) {
packageName = getContext().getPackageName();
}
}
return packageName;
}
/**
* 获取具体的域值
*
* @return
*/
public static Object getBuildConfigValue(String fieldName) {
try {
Class> clazz = Class.forName(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();
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
return "";
}
}
这里实际上可以用一个方法获取 context 然后就直接可以拿到 packageName,为了看的清晰,所以分成两个方法了。
然后把之前那个 DEBUG 类适当改动下,将 DEBUG 类放在该子模块代码中:
public final static boolean DEVELOP_MODE = isDevelopMode();
static boolean isDevelopMode(){
return (boolean)BuildConfigProvider.getBuildConfigValue("DEVELOP_MODE");
}
}
这样只需要在引入项目中的 buildTypes 中设置 DEVELOP_MODE
的各种配置就能在项目中动态的调用日志开关了。
比如我们项目中的日志打印类 Glog.class
,每个日志等级的打印前都会有 DEBUG.DEVELOP_MODE 的判断:
public class GLog {
public static void d(String message) {
if(TextUtils.isEmpty(message)){
return;
}
//判断日志开关
if (DEBUG.DEVELOP_MODE) {
final StackTraceElement[] stack = new Throwable().getStackTrace();
final int i = 1;
final StackTraceElement ste = stack[i];
Log.println(Log.DEBUG, LOG_TAG, String.format("[%s][%s][%s]%s", ste.getFileName(), ste.getMethodName(), ste.getLineNumber(), message));
}
}
}
这里只是通过打印日志的例子来展示如何在 aar 中调用主项目的 BuildConfig 类获取编译类型,其他各种 BuildConfig 中的域都可以用这种方式获取到