赞
踩
细微之处,杜绝隐患,提升效率
在Android的实际开发中,一般会有这样的需求:根据debug和release版本不同,配置不同的接口地址,控制日志是否打印,甚至做一些辅助的业务逻辑等
系统为我们提供了一个很方便的BuildConfig.DEBUG可以自动判断是否是debug模式,debug模式下该字段是true,在release版本中该字段是false
然后Androider就可以愉快的编程了:
if (BuildConfig.DEBUG) {
Log.d(TAG, "debug模式下,可以猥琐欲为啦,嘿嘿嘿~~");
}
我们看下运用到的代码有哪些:
【图】
然而有一天,工程目录变成了多module开发后,发现BuildConfig.DEBUG都变成了false,从而导致debug状态下的许多辅助代码不能被执行,譬如:控制台的日志不在打印,影响调试和定位问题等
目前我们的做法是,自己建了一个类:
public class BaseFunctionConfig {
//在打发布包的时候修改;DEBUG = false
public static final boolean DEBUG = true;
public static String PACKAGE_NAME = "com.sangfor.pocket"; // 包名
}
使用该类定义的DEBUG字段,但是,尤其需要注意一点:需要在打包的时候,千万千万记得手动修改!!!。
这样做的确暂时解决了问题,但是这样手动修改不仅增加了难度,而且带来了一定的风险~~(万一某天打包给漏了,那就悲剧了,会出现什么奇怪的东东)
从类名上进行分析,BuildConfig很明显是由Build与Config组成,Build = 构建,Config = 配置,这个类就是一个存储与配置相关信息的类
此类是不可修改的,严格来说不能通过我们之前正常编码那样对类一样修改,由编译环境帮我们自动生成,并存放在编译好的包名目录下,如图所示:
我们看下其中的内容:
public final class BuildConfig {
//这个常量是标识当前是否为`debug`环境,由构建脚本中的`buildType`中的`debuggable`来确定的,这是修改此类值的一个方式
public static final boolean DEBUG = Boolean.parseBoolean("true");
//application id
public static final String APPLICATION_ID = "com.sangfor.pocket";
//当前编译方式
public static final String BUILD_TYPE = "debug";
//编译渠道,如果没有渠道,则为空
public static final String FLAVOR = "dev";
//版本号
public static final int VERSION_CODE = 1;
//版本名,所以获取当前应用的版本名可以直接 BuildConfig.VERSION_NAME
public static final String VERSION_NAME = "9.9.99";
}
明确了BuildConfig是BuildConfig.java 是编译时自动生成的,并且每个 Module 都会生成一份以该 Module 的 packageName 为 BuildConfig.java 的 packageName,其中DEBUG字段的自动根据BUILD_TYPE来生成的之后,我们就可以做出以下推断:
在Android Studio中,被依赖module里BuildConfig.DEBUG的值总为false,因为Library projects目前只会生成release的包.
例如module A依赖module B和module C,在android Studio里B和C里的BuildConfig.DEBUG值总是false,A里的正常。这样就导致B和C中的if(BuildConfig.DEBUG){Log.d(…)}日志无法正常显示
BuildConfig中会有默认字段,但是我们也可以往里边添加自定义字段
修改配置文件:
android {
......
buildType {
debug {
buildConfigField "String","BASE_URL","\"http://www.test.com/\""
buildConfigField "int","DATE","20171109"
buildConfigField “boolean”, “IS_DEBUG”, “true”
}
}
}
这样子:
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.sangfor.pocket";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "9.9.99";
public static final String BASE_URL = "http://www.test.com/";
public static final int DATE = 20160701;
public static final boolean “IS_DEBUG” = Boolean.parseBoolean("true");
public BuildConfig() {
}
}
这的确是一种解决方案,但是还是需要打包时我们手动修改,并没有提供真正的解决思路
BuildConfig.DEBUG always false when building library projects with gradle
android {
publishNonDefault true
}
表示该 Module 打包时会同时打包其他版本,包括 Debug 版,而不单单是默认的release版本
dependencies {
releaseCompile project(path: ‘:library’, configuration: ‘release’)
debugCompile project(path: ‘:library’, configuration: ‘debug’)
}
表示依赖不同版本的依赖 Module。
这种方式的确可以解决问题,但是也带来了弊端:这种方式所有 Module 配置都需要修改,侵入性太强
如果 Lib Module 中能够 import 到外层真正运行 App 的 BuildConfig 那么就不存在问题了:
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
*/
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
}
}
}
}
//务必在自己的 App Application 内调用初始化
AppUtils.syncIsDebug(getApplicationContext());
传入了ApplicationContext,获取包名并最终映射到主app的对应class,利用反射得到值;同时,该过程只触发一次,由于BuildConfig不可改变,读出来的值直接静态存储即可
但是会存在以下问题:
即便是APP中的BuildConfig.java, 它的 packageName 是 该Module 的 Package Name,即 AndroidManifest.xml 中的 package 属性;
然而context.getPackageName() 得到的是应用的 applicationId,这个 applicationId 通过 build.gradle 是可以修改的。
所以当 build.gradle 中的 applicationId 与 AndroidManifest.xml 中的 package 属性不一致时,上面的反射查找类路径便会出错。
譬如我们在处理一些定制包版本的时候,直接修改build.gradle中的包名,这样子也会存在隐患。
PS:这种方案还有个变种就是通过 android.app.ActivityThread.currentPackageName 得到包名,从而省去传递 Context 初始化的步骤,但依然有 applicationId 被修改后类查找不到类似的问题
相对于4.3的隐患,我们进一步的去反编译分析Debug 包和 Release 包对比看看有没有其他的区别:
我们会发现 AndroidManifest.xml 中 application 节点的 android:debuggable 值是不同的:
Debug 包值为 true,Release 包值为 false,而且这是编译自动修改的
所以我们考虑通过 ApplicationInfo 的这个属性去判断是否是 Debug 版本,如下:
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
*/
public static void syncIsDebug(Context context) {
if (isDebug == null) {
isDebug = context.getApplicationInfo() != null &&
(context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
}
}
//务必在自己的App Application 内调用初始化:
AppUtils.syncIsDebug(getApplicationContext());
这样以后调用 AppUtils.isDebug() 即可判断是否是 Debug 版本,同时适用于 Module 是 Lib 和 applicationId 被修改的情况,且比BuildConfig.DEBUG 靠谱的多
这个方案有个注意事项就是自己 App Module 中不能主动设置 android:debuggable,否则无论 Debug 还是 Release 版会始终是设置的值, 当然本身就没有自动设置的必要
对于口袋助理而言,鉴于当前module的有限性,4.2方案和4.4方案其实均可
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。