需求
我在做 Android 的日志工具库时,有个需求:希望能够按照如下格式打印日志
类名.方法名(文件名/代码行号): 日志内容
日志内容很好处理,但是类名、方法名、文件名、代码行号这样的信息要如何处理呢?
方法调用栈
这些信息是和方法调用栈相关的,在 Java 中可以通过两种方法获取到方法调用栈的信息
- (new Throwable()).getStackTrace()
- Thread.currentThread().getStackTrace()
这两种方式都能返回一个 StackTraceElement 数组,StackTraceElement 对象中包含了类名、方法名、文件名、代码行号这样的信息
1 | public final class StackTraceElement implements java.io.Serializable { |
写一个例子来演示一下吧
1 | package com.okada.go; |
打印出来的结果如下
1 | index=0---------------------------------- |
从打印结果可以发现:StackTraceElement 数组里面有两个元素。这是为什么?
这个是方法调用栈的知识。在 Java 中有一个方法栈,每执行到一个方法,就将方法压入栈,执行完毕再将方法弹出栈。在上面的例子中,main() 方法是第一个方法,因此首先将 main() 方法压入栈,在 main() 方法中遇到了 method() 方法,因此再将 method() 压入栈中。这时候方法栈的情况如下
1 | | | |
因此数组中元素的位置如下
1 | |method|main| |
这样就能明白为什么打印结果是这样的了。
把上面的例子改造一下
1 | package com.okada.go; |
这时候的打印结果如下
1 | index=0---------------------------------- |
因为 method() 方法已经执行完毕,被弹出了方法栈,此时方法栈中只有一个 main() 方法,所以这时候的打印结果就是这样。
获取某一个方法调用栈信息
上面的例子中有两个方法 main() 和 method(),如果我只想知道 method() 的信息的话要怎么做?
要实现这个需求,需要在 StackTraceElement 数组的索引上做文章。可以这么实现
1 | package com.okada.go; |
打印结果
1 | className=com.okada.go.StackTraceDemo |
因为这段代码
1 | StackTraceElement[] stackTraceElements = (new Throwable()).getStackTrace(); |
是在 method() 方法中执行的,此时的方法调用栈栈顶是 method() 方法,所以使用 0 作为数组的索引就可以获取到 method() 的类名、文件名、方法名和行号信息了。
实战
我希望在实际代码中,调用日志库 Logger 的方法打印出相关信息。例子如下
1 | package com.okada.go; |
根据方法调用栈的定义和我的代码层级,使用 1 作为数组索引
1 | public class Logger { |
打印结果
1 | com.okada.go.StackTraceDemo.test(StackTraceDemo.java/14)我是日志内容 |
以上就是我在开发 Android 日志库时的核心方法。只需要明白
- 方法调用栈
- 代码层级
即可实现。