安卓开发打印 Log 最佳实践

677 查看

Log最佳实践

概要:使用更好的log来调试应用。
本文会不定期更新,推荐watch下项目。如果喜欢请star,如果觉得有纰漏请提交issue,如果你有更好的点子可以提交pull request。本文的示例代码主要是基于loggerLogUtilstimber进行编写的,如果想了解更多请查看他们的详细解释。我很推荐大家多多进行对比,选择适合你自己的库来使用。

本文固定连接:https://github.com/tianzhijiexian/Android-Best-Practices

本文推荐的库:https://github.com/tianzhijiexian/logger

一、需求背景

Android中log是这么写的:

我觉得不爽,而且tag连空校验都没做!

二、需求

  1. 我才不要每次打log都去想tag叫什么名字呢
  2. 我希望可以自动把当前类名作为默认的tag
  3. 如果我真的要写tag了,你就必须显示我定义的tag
  4. 我希望我写的模板代码越少越好,一个logd就能打印一切
  5. 我要我的log变的好看,直观,就是美
  6. 我打出的log后面要根上这个log的文件源头的连接,我可以直接点击跳转到log的位置
  7. log中还能提示我当前的线程名,方便我调试
  8. 我还要打印出list,map,json,pojo这样的对象
  9. release包中不能泄漏我高傲的log
  10. 只要我想让我的log显示,release版本也阻挡不了我
  11. log信息过长后应该要自动换行,我不允许我的log打印不全
  12. 在release版本中残留的log代码应该对效率无影响

注意:我希望只要写真正有意义的内容!

回看这些需求,不合理么?很合理,我们的宗旨就是让无意义的重复代码去死,如果死不掉就交给机器来做。我们应该做那些真正需要我们做的事情,而不是像一个没思想的猿猴一般整天写模板式代码。这才是程序员思维,而不是程序猿思维!

三、实现

无论一个第三方库有多好,我还是推荐不要直接使用它,因为你很有可能会去替换这个第三方库,而且还可能会有各种意想不到的需求。对于网络请求、图片请求和log,是应该事先考虑到后续的扩展和替换的。

建立包装类

这个包装类用来包裹logger(logger是本文介绍的一个log库),下面是一个代码片段:

对于包装类的起名最好不要和“Log”这个名字类似,能有明显的区别最好,一是防止自己手抖写错了,二是方便review的时候方便自己检查有没有误用原始的Log。

自动打tag

现在索性把当前类名作为这样一个TAG的标识。我们可以通过下面代码来设置tag:

这样我们就轻易的摆脱了tag的纠缠。

这个方法来自于豪哥的建议,这里感谢豪哥的意见。

自定义tag

我们想要偶尔打打log的tag方便做其他的处理:

我上面的做法是把tag用getSimpleName的方式来得到,但会因为混淆的问题在混淆的包里出现a.b.c这样的类名。如果你的log是要出现在混淆的包里的,强烈建议去手动设置tag值,否则你完全就没办法过滤了。至于如何手动设置tag的值,下面会讲到logt这个快捷命令。

将Log代码快捷模板

有人说我们IDE不都有代码提示了么,你还想怎么简化log的输入呢?这里可以利用as的一个模板提示的功能:
QQ截圖20151214150341.png-112.7kB

我们可以模仿这里原有的模板来做自己的代码模板,简化模板式代码的输入。至于具体模仿的方式我就不手把手教了,简单到爆。下面仅展示下自带的log模板的使用方式:
写tag:
log1.gif-64.8kB
自动填写参数和方法名:
log2.gif-83.7kB

让log更加美观

我要美,要直观,要够酷!做到这点也简单,就是在输出前做点字符串拼接的工作,比如加上下面这行横线。

因为做了很多拼接的工作,所以好看的log也是消耗性能的。我的习惯是调试完毕后立刻删除无用的log,这样既能减少性能影响,也减少同事的阅读代码的负担,效果如下:
QQ截圖20160327093141.png-197.6kB

显示当前方法名和所在类并加超链

这个功能其实ide是原生支持的,不相信的话你随便用原生的log打印出onCreate: (MainActivity.java:39)试试。只不过我们可以通过一些神奇的方法来做到更好的效果:

这里同样需要注意的是在混淆后是得不到正确的类名的,所以可以酌情让activity、fragment、view不被混淆,具体方案还是看自己的取舍。

支持POJO、Map、Collection、jsonStr、Array

这个需求实现起来也比较容易,如果是简单的POJO的对象,我们可用反射得到对象的类变量,通过字符串拼接的方式最终输出值。如果是map等数组结构,那么就用其内部的遍历依次输出值和内容。如果是json的字符串,就需要判断json的{},[]这样的特殊字符进行换行处理。至于具体的代码是怎样了,大家移步去看源码就好,这个不是重点。重点是结果:
QQ截圖20160327093200.png-156.6kB

增加自动化或强制开关

区分release和debug版本有系统自带的BuildConfig.DEBUG变量,用这个就可以控制是否显示log了。强制开关也很简单,在log初始化的最后判断强制开关是否打开,如果打开那么就覆盖之前的显示设置,直接显示log。转为代码就是这样:

支持超长的log信息

有时候网络的返回值是很长的,android.util.Log类是有最大长度限制的,为了解决这个问题。我们只需要判断这个字符串的长度,然后手动让其换行即可。

解决log字符拼接的效率影响

多参数log信息应该这样打印,避免拼接好后再打印。这样在关闭log后就不会进行字符串的拼接工作了,减少log语句在release版本中的性能影响。

这条来自朋友helder的建议,感谢!

各种需求和应对方案

虽然提出了上面的思路和方案,但我并不能确保可以满足所有的需求,我给出下面的思维流程,方便大家随机应变:

Created with Raphaël 2.1.2优先用ida的debug系统实在不行就在debug和error级别打log信息不够时才在info、warning、error的级别打提交测试时,删除无用log,预留测试环境中的log开关正式版中优先用数据统计平台或应用打点工具来代替log日志如果必须出现log,正式版中仅泄漏info级别以上的log,即warn和erro

说明:
1. 尽量用as的debug模式下的log系统,无入侵。不用写代码就能打log,十分方便。(下文会介绍)
2. 如果真的要打log做调试,先放在debug和error级别,提交代码时务必记得清除。
3. 如果提交的代码中需要在某个关键点打log,或者要给同事看这些log,可以放在在info级别以上。
4. 在realse中推荐用自己的log包装类的开关做处理,这样方便在公司内部测试时可以查看到log。
5. 如果一些信息需要在发出去的用户版本中出现,优先考虑数据统计的方式进行关键点的数据打点。
6. 如果真的要在正式发布的apk中还带着log,只保留info级别以上的,不把info级别之下的信息漏出去。

四、IDEA的超强debug技巧

上文中我就提到了可以利用as的调试模式来加速debug,下面分享下两个和log有关的经验。
测试代码:

  1. 通过console热部署打印log信息
    我通过debug工具,可以在任意位置打印出任意对象的值,通过这种方式就可以精准调试一些信息了。这回我是让其在不中断运行的情况下打印index的值。
    debug1.gif-158.1kB
  2. 动态设置值
    有时候某种分支需要在某个情况下才能走到,我可以利用debug的setValue(F12)方法动态设置值,比如我把下面的123改成了520,最终在终端打印出的信息也会变成520。整个过程对原本代码完全屏蔽,无入侵。
    debug2.gif-273.6kB

五、最终成果

依赖库:
https://github.com/tianzhijiexian/logger
如果你觉得这个库不好,请提交issue,万不可冷嘲热讽。要知道精品永远是个位数,而中庸的东西永远是层出不穷的。我希望大家多提意见齐心协力优化出一个精品,而不是花时间去在平庸的选项中做着选择难题。

六、后记

我们可以看到即使一行代码的log都有很多点是可优化的,还明白了我们之前一直写的模板式代码是多么的枯燥乏味。
通过这篇文章,希望大家可以看到一个优化编码的思维过程,也希望大家去尝试下logger这个库。当然,我知道还是有很多人不喜欢,那么不妨提出更好的解决方案来一起讨论,不满意可以提嘛。
宁信书则不如无书,具体如何使用还得看自己的需求,欢迎通过邮件或者是gitter的方式进行交流。

在文章后面我也给出了通过idea的debug模式下打印log的方法,意思是即使你有了这个log库,但是我仍旧希望你可以能找到更好的方法来达到目的,拥有技巧,使用技巧,最终化为无形才是最高境界。相信我们的最终目的是一致的,那就是让开发越来越简便,越来越优雅~

最后说下我没直接用文章开头那几个库的原因,logger的库很漂亮,但是冗余行数过多,调试多行的数据就会受到信息干扰,timber的本身设计就是一个log的框架,打印是交给开发者自定义的。所以我将timber的框架和logger的美观实现进行了结合。这当然还要感谢logUtils的作者,让log支持了object类型。

参考文章

http://ihongqiqu.com/blog/2014/10/16/android-log/
https://github.com/pengwei1024/LogUtils
https://github.com/orhanobut/logger
http://droidyue.com/blog/2015/11/01/thinking-about-android-log/
https://github.com/JakeWharton/timber