(2.2.8.10) Android多module下的构建版本区分

细微之处,杜绝隐患,提升效率

一、背景

在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研究

从类名上进行分析,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(…)}日志无法正常显示

四、解决方案

4.1 添加module自定义字段

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() {
    }
}

这的确是一种解决方案,但是还是需要打包时我们手动修改,并没有提供真正的解决思路

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

BuildConfig.DEBUG always false when building library projects with gradle

  • 在被依赖的 Module 提供除 Release 版以外的其他版本,这种方案需要将所有被依赖 library 中添加:
android {
    publishNonDefault true
}

表示该 Module 打包时会同时打包其他版本,包括 Debug 版,而不单单是默认的release版本

  • 并且需要在 App Module 中将其依赖的 library 如下逐个添加:

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

表示依赖不同版本的依赖 Module。

这种方式的确可以解决问题,但是也带来了弊端:这种方式所有 Module 配置都需要修改,侵入性太强

4.3 获取主App的BuildConfig

如果 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.4 使用 ApplicationInfo.FLAG_DEBUGGABLE

相对于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方案其实均可

参考文献

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值