在安卓项目中,打印日志既是一种调试手段也是一种检测业务逻辑完整性的方法。在项目中,日志系统是不可或缺的。但是,大部分时候,我们只希望在debug包中输出日志,而不希望在release包中输出日志,所以我们需要一种安全的日志打印方式。
何为安全
日志系统在很大程度上会暴露系统的架构路线和业务流程,是不允许将一些敏感的日志暴露到发布环境的。而且,优秀的日志系统在发布环境下,还能经受住hook、反射修改这些入侵手段。因此,我觉得安全的日志系统至少应该包括以下几个特点:
- 在需要时正常的打印;在不需要时不打印
- 在不需要打印时,在对应的地方,不应该出现打印日志的相关代码
- 没有被使用的日志类不应该出现在发布版的包中
- 实现方式尽量简单,接近原生
满足以上的4个特性,我觉得才能说是安全的,而且是便捷的。这也是符合我们的诉求的。接下来我们来看下具体的实现方式。
实现
针对上述的第一点,我们通常诉求是:在debug环境下,我们打印日志;在release环境下,我们不打印。基于此,我们可以使用BuildConfig
来实现,通过默认的DEBUG
字段来判定编译环境。大致思路应该如下:
public static void info(String msg) {
if (BuildConfig.DEBUG) {
Log.i(TAG, msg);
}
}
其中BuildConfig.DEBUG
是编译系统根据当前编译选项自动生成的对应当前构建环境的配置信息。
上述代码就满足了我们关于安全性的第一个特性,使用起来也不麻烦。但是距离完善的日志系统,还有很多不足的地方。
首先,在release环境下,虽然不再打印日志了,但是打印日志的代码还是存在的。而且还可以通过反射等手段修改BuildConfig.DEBUG
的值,这样子的话,在release环境下,也会将日志打印出来。所以,我们需要解决第二个和第三个特性涉及的难点问题。
我们知道,在实际开发过程中,我们一般只对release的包进行混淆处理。混淆的主要作用是以简短的字符来描述类和属性信息,增加阅读难度,起到一定的代码保护作用。在这里,我们使用到的主要工具就是Proguard
,在AndroidStudio
中,默认使用的也是Proguard
。
实际上,Proguard
的能力比我们了解的还要强大。总的来说,针对安卓项目,它支持以下几个功能输出。
- 压缩:主要是移除没有被引用的类和变量
- 优化:主要是类和变零的合并,方法内联
- 混淆:用简短的字符重命名类名、属性名;移除调试信息
- 预校验:针对不同java版本和平台,对类文件进行合法性的校验
我们平常在使用过程中,由于压缩和优化可能会影响我们的原始代码(删除),所以使用比较少。
而在这里,我们就是利用优化的功能来实现我们的诉求的。
优化指令中有一种针对方法返回值的优化策略assumenosideeffects
,它的优化原则是:
如果一个有返回值的表达式,其返回值没有被存储或者没有被使用,那么,这个表达式是可以移除的。
其原文表达为:
In the optimization step, ProGuard can then remove calls to such methods, if it can determine that the return values aren’t used.
因此,我们可以修改前面的日志工具类
public static boolean info(String msg) {
if (BuildConfig.DEBUG) {
Log.i("TAG", msg);
return true;
}
return false;
}
我们在调用时,仍然采用相同的方式,而且不理会返回值。
Logger.i("this is test message.)
为了在release包中(开启了proguard),移除对应的代码。。我们需要在proguard-rues.pro
(该文件是在build.gradle中配置并提供给proguard的默认配置文件)文件中配置我们的优化策略。
# 去除日志系统
-assumenosideeffects class com.github.common.Logger {*;}
到这里,我们已经解决了上述特性1、2、4三个。那么特性3是如何实现的呢?
这就要提到上述的压缩指令了。事实上,在每个优化措施后面,都会紧跟一个压缩流程。
It also applies a shrinking step after each optimization step, since some optimizations may open up the possibility to remove more classes and class members.
当最后一个日志调用被移除后,触发的压缩指令就会从项目中移除该日志类文件。达到了我们上述特性3的目的。
最后,我们来看下对比效果,我们在hasPms
方法的第一行打印了一行日志,同时,为了便于观看,我们去掉了混淆。
这是开启proguard之前的效果:
这是开启proguard之后的效果:
很明显,打印日志的逻辑已经被移除了,同时,在文件列表中也没有对应日志类文件了。
到此,完美的解决了我们的日志打印需求。。