Android 中能够作为 Log 开关的一些操作以及安全性浅谈
自定义常量
开发阶段利用 Log 日志方便代码调试是再常见不过的事情。出于安全考虑,这种做法仅限于 Debug 模式,Release 模式下打包发布时一定要关掉。所以在我们的项目中,一定会有一个工具类或者方法来控制 Log 日志的使用,比如:
public class LogUtils {
public static final Boolean DEBUG_MODE = true;
public static void d(String message) {
if (DEBUG_MODE) {
Log.d("TAG", message);
}
}
}
常见的做法便是像上面这样,自定义一个布尔类型的常量作为开关来控制是否打印日志。但是这种做法有一个弊端,那就是每次发布 Release 包时都需要手动修改这个常量的值为 false,然后下一次开发阶段再手动修改为 true。
虽然是很简单的手动修改操作,但是也很容易忘记。那么有没有一种办法实现自动化管理呢?答案当然是有的,使用 BuildConfig 类。
BuildConfig
类似 R 资源文件,BuildConfig 也是在编译阶段,Gradle 插件自动生成的一个 class 文件。该文件包含一些帮助开发人员辨别当前 build 类型的常量信息。当然你也可以通过 Gradle 提供的定制功能向该文件里面添加其他辅助内容。这里我们看一下默认情况下,BuildConfig 文件都包含有哪些内容:
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.yifeng.sample";
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 = "1.0";
}
能够看出,都是一些大家很熟悉的信息。其中包括一个 DEBUG 常量,其值便可用于判断当前 build 类型。debug 模式下为 true,release 模式下为 false。所以,使用 BuildConfig.DEBUG 可以替代前面我们自定义的常量,实现自动管理 Log 日志的打印:
public static void d(String message) {
if (BuildConfig.DEBUG) {
Log.d("TAG", message);
}
}
看上去貌似已经很完美了,但其实还是有瑕疵的。BuildConfig 类文件的生成依据于 Module,也就是说每一个 Module 编译时都会产生自己的这个文件。如果你的主 app module 使用其他依赖 module 中 BuildConfig 文件里面的 DEBUG 值,就需要多加注意。
默认情况下,Library 的构建永远是以 Release 模式执行的,所以其 BuildConfig.DEBUG 值一定是 false!即使主 Module 使用 Debug 模式构建,也是如此。
那么,有没有办法修改 Library Module 的默认构建方式呢?答案也是肯定的。打开对应 Library 的 build.gradle 文件,添加这样一行配置代码:
android {
// 这里省略其他内容
publishNonDefault true
}
即表示不使用默认构建方式,编译时也会自动生成其他 build 类型的 BuildConfig 类文件。你可以在相应 Library 路径下查看配置该命令前后 BuildConfig 文件的生成情况,目录地址为:
libraryName/build/generated/source/buildConfig/ + debug/release
然后在我们的主 Module 依赖的时候同时引入 debug 和 release 两种配置,这里以 extras/PullToRefresh 作为 Library 为例,看下依赖代码:
dependencies {
releaseCompile project(path: ':extras:PullToRefresh', configuration: 'release')
debugCompile project(path: ':extras:PullToRefresh', configuration: 'debug')
}
如此这般,便可以解决前面提到的依赖 Module 问题。当然,如果你的项目比较简单,只是单一 Module,也就不存在这个问题。
但是如果项目中的依赖 Module 比较多的话,这种处理方式还是略显麻烦。你需要在用到的地方针对每个 Module 逐一处理。其实还有一种更好的解决方案,那就是使用 Manifest 清单文件中 application 标签里的 debuggable 属性。
ApplicationInfo
application 标签里有个 android:debuggable 属性,表示当前应用是否可以被调试(一般不建议手动设置这个属性)。这个属性也会随着 build 类型自动改变。所以,利用这个特性也能判定应用是否处于 Debug 模式,比如:
public static boolean isDebug(Context context) {
return (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
控制 Log 日志打印的开关,除了上面讲到的这些方式,其实还有别的方式。比如利用 Gradle 的灵活性在 build.gradle 文件中自定义一个 Boolean 变量,根据 build 类型动态赋值,也能达到我们的目的。
更安全的 Log 用法
前面所有这些做法都只是使 release 包不去显示 Log 日志,从而提高安全性。但是,有没有想过,如果 apk 被反编译的话,这些 Log 相关的代码还是能够别识别出来,别人只需要稍作修改,重新打包,依旧能够使 Log 重现。
当然,使用常量作为 LogUtils 中的判断条件的话,根据 proguard 的优化规则,在 Release 包中是不包含条件体中的 Log.d 等操作代码的。关于这一点,可以自己反编译 apk 尝试看下。
然而,在其他调用 LogUtils 工具类的地方依旧暴露了我们的意图。所以,定义一个 LogUtils 类虽然提高了使用 Log 的效率,依旧解决不了 Log 安全的问题。相比而言,我们做了这么多努力只是稍微提高了一些安全的门槛而已。
所以,最好的办法就是,Release 包中不包含任何用于调试的 Log 代码(如果使用 LogUtils 的话,也包括 该类的调用)。也就是说,不使用 LogUtils 工具类封装,在任何需要的地方,不嫌麻烦的逐一添加判断条件:(可以使用 Live Template 提高效率)
if (BuildConfig.DEBUG) {
Log.d("TAG", message);
}
这样,打包时,开启 proguard 后,Release 包会自动删除上面的代码,彻底根绝 Log 引发的安全问题。关于这一部分的细节操作,可以参考这两篇文章:
(END)