android 日志输出封装,重拾Android之路之日志封装StackTrace

引言

对于日志打印,之前都是使用Log.i()的方式。今天来探究下StackTraceElement获取方法调用栈的信息。

痛点:编写项目的时候,肯定会或多或少的使用Log,尤其是发现bug的时候,会连续在多个类中打印Log信息,当问题解决了,然后需要一行一行地去删除刚才随便添加的Log,有时候还要几个轮回才能删除干净。

当然了,我们有很多方案可以不去删除:

我们可以通过gradle去配置debug、release常量去区分

可以对Log进行一层封装,通过debug开关常量来控制

所以,我们的需求是这样的:

可以对Log封装,通过debug开关来控制正常日志信息的输出

在修bug时,用于定位杂乱的log日志,我们希望可以在bug解除后,很快的定位到,然后删除灭迹。

一、什么是StackTrace

StackTrace(堆栈轨迹)存放的就是方法调用栈的信息,异常处理中常用的printStackTrace()实质就是打印异常调用的堆栈信息。

二、StackTraceElement介绍

StackTraceElement表示StackTrace(堆栈轨迹)中的一个方法对象,属性包括方法的类名、方法名、文件名以及调用的行数。

public final class StackTraceElement implements java.io.Serializable {

// Normally initialized by VM (public constructor added in 1.5)

private String declaringClass;

private String methodName;

private String fileName;

private int lineNumber;

}

StackTraceElement被定义为final,可见其作为一个Java的基础类不允许被继承。

获取StackTraceElement的方法有两种,均返回StackTraceElement数组,也就是这个栈的信息。

1、Thread.currentThread().getStackTrace()

2、new Throwable().getStackTrace()

StackTraceElement数组包含了StackTrace(堆栈轨迹)的内容,通过遍历它可以得到方法间的调用过程,即可以得到当前方法以及其调用者的方法名、调用行数等信息

StackTraceElement数组,你可以理解为当我们调用方法的时候,每进入一个方法,会将该方法的相关信息(例如:类名,方法名,方法调用行数等)存储下来,压入到一个栈中,当方法返回的时候再将其出栈。

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

a();

}

void a() {

b();

}

void b() {

StringBuffer err = new StringBuffer();

StackTraceElement[] stack = Thread.currentThread().getStackTrace();

for (int i = 0; i < stack.length; i++) {

err.append("\tat ");

err.append(stack[i].toString());

err.append("\n");

}

Log.e("TAG", err.toString());

}

我在onCreate中,调用了a方法,然后a中调用的b方法。在b方法中打印出当前线程中的栈帧集合信息。

at dalvik.system.VMStack.getThreadStackTrace(Native Method)

at java.lang.Thread.getStackTrace(Thread.java:579)

at com.zxy.recovery.test.MainActivity.b(MainActivity.java:26)

at com.zxy.recovery.test.MainActivity.a(MainActivity.java:21)

at com.zxy.recovery.test.MainActivity.onCreate(MainActivity.java:17)

at android.app.Activity.performCreate(Activity.java:5231)

...

可以看到我们整个方法的调用过程,底部的最先开始调用,顺序为onCreate->a->b->Thread.getStackTrace->VMStack.getThreadStackTrace.

最后两个是因为我们的stacks是在VMStack.getThreadStackTrace方法中获取,然后返回的,所以包含了这两个的内部调用信息。

上面的例子便于理解在文末代码中的编写逻辑。

三、用途

1、我们可以封装一个日志库,在打印目标日志的时候,也可以通过这个调用栈打印出这个日志所在的行数,这样就可以迅速的定位到日志输出行,再也不要全局搜索去查找了。

public static void d(String tag, String msg, Object... params) {

StackTraceElement targetStackTraceElement = getTargetStackTraceElement();

Log.d(tag, "(" + targetStackTraceElement.getFileName() + ":"

+ targetStackTraceElement.getLineNumber() + ")");

Log.d(tag, String.format(msg, params));

}

2、如果我们写了一个SDK,希望某个方法在固定的位置被调用,我们也可以在这个方法被调用的时候,进行检查,看这个方法的调用位置是否正确。

例如,必须在Activity.onResume中执行,PVSdk.onResume,所以我们在调用PVSdk.onResume方法的时候,在PVSdk.onResume方法里面来通过获取调用栈的信息检测这个方法是否在Activity的onResume方法中调用的。

public class PVSdk {

public static void onResume() {

StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();

boolean result = false;

for (StackTraceElement stackTraceElement : stackTrace) {

String methodName = stackTraceElement.getMethodName();

String className = stackTraceElement.getClassName();

try {

boolean assignableFromClass = Class.forName(className).isAssignableFrom(Activity.class);

if (assignableFromClass && "onResume".equals(methodName)) {

result = true;

break;

}

} catch (ClassNotFoundException e) {

}

}

if (!result)

throw new RuntimeException("PVSdk.onResume must in Activity.onResume");

}

}

3、我们在进行源码分析的时候,如果想分析整个代码的执行流程,我们可以进行通过打印栈的信息来获取,这个在源码分析的时候还是挺有用的。

推荐一下

我们今天要谈的就是Log的封装,当然封装不仅仅是是上述的好处,我们还可以让使用更加便捷,打出来的Log信息展示的更加优雅。

比如:

这个库,就对Log的信息的展示做了非常多的处理,展示给大家是一个非常nice的效果:

85415948e313?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

最后附上核心代码:

public static void i(String msg) {

if (!isDebug) return;

StackTraceElement targetStackTraceElement = getTargetStackTraceElement();

Log.i(TAG, "(" + targetStackTraceElement.getFileName() + ":"

+ targetStackTraceElement.getLineNumber() + ")");

Log.i(TAG, msg);

}

private static StackTraceElement getTargetStackTraceElement() {

// find the target invoked method

StackTraceElement targetStackTrace = null;

boolean shouldTrace = false;

StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();

for (StackTraceElement stackTraceElement : stackTrace) {

boolean isLogMethod = stackTraceElement.getClassName().equals(L.class.getName());

if (shouldTrace && !isLogMethod) {

targetStackTrace = stackTraceElement;

break;

}

shouldTrace = isLogMethod;

}

return targetStackTrace;

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值