在使用Android Studio开发的时候,当程序出现错误的时候,我们通常可以通过点击错误日志跳转到指定方法出错的那一行。例如,下图便是常见的空指针异常所抛出的错误日志:
然而,当我们自己使用Log.x去打印Log的时候却没有这种跳转的功能,只能显示打印的信息。那么如何实现让我们打印的日志也能跳转呢?其实方法很简单,只要让我们打印的日志信息符合系统的要求,你输出的日志信息就会自动实现可跳转的功能。所以只要让你的输出的日志符合这种要求便可。这里,我们可以使用JDK自带的StackTraceElement类来帮助我们实现这种要求,StackTraceElement可以简单理解为是记录调用栈信息的类,也就是说,我们的输出信息含有了这种调用栈信息,便可以实现自动跳转的功能。
下面给出两个种实现方法:
- Android打印日志,实现快速定位源代码 ——大头呆
public class LogUtils {
public static void PrintD(String content, Object... args) {
for (int i = 0; i < Thread.currentThread().getStackTrace().length; i++) {
String realContent = getContent(content, i, args);
Log.d("default", realContent);
}
}
public static void PrintD(String tag, String content, Object... args) {
Log.d(tag, getContent(content, 4, args));
}
private static String getNameFromTrace(StackTraceElement[] traceElements, int place) {
StringBuilder taskName = new StringBuilder();
//判断调用栈的层级,大于place的才打印Log输出
if (traceElements != null && traceElements.length > place) {
StackTraceElement traceElement = traceElements[place];
taskName.append(traceElement.getMethodName());
taskName.append("(").append(traceElement.getFileName()).append(":").append(traceElement.getLineNumber()).append(")");
}
return taskName.toString();
}
private static String getContent(String msg, int place, Object... args) {
try {
String sourceLinks = getNameFromTrace(Thread.currentThread().getStackTrace(), place);
return sourceLinks + String.format(msg, args);
} catch (Throwable throwable) {
return msg;
}
}
}
//在下面调用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
printD("MyTag:", "xxxxx");
}
- Android 从StackTraceElement反观Log库 ——Hongyang
private 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;
}
public static void e(String tag, String msg, Object... params) {
if (!sDebug) return;
String finalTag = getFinalTag(tag);
StackTraceElement targetStackTraceElement = getTargetStackTraceElement();
Log.e(finalTag, "(" + targetStackTraceElement.getFileName() + ":"
+ targetStackTraceElement.getLineNumber() + ")");
Log.e(finalTag, String.format(msg, params));
}
//在下面调用
public class L{
private static boolean sDebug = true;
private static String sTag = "zhy";
public static void init(boolean debug, String tag){
L.sDebug = debug;
L.sTag = tag;
}
public static void e(String msg, Object... params){
e(null, msg, params);
}
public static void e(String tag, String msg, Object[] params){
if (!sDebug) return;
tag = getFinalTag(tag);
//TODO 通过stackElement打印具体log执行的行数
Log.e(tag, content);
}
private static String getFinalTag(String tag){
if (!TextUtils.isEmpty(tag)){
return tag;
}
return sTag;
}
}
上面两种实现方法都是使用了 StackTraceElement类,主要区别在于:如何过滤掉无用信息。在下图中,是一些没有进行过滤的调用栈信息,而我们只需要蓝色部分字体所在行的信息。所以,怎样去掉其他行显得很重要,上面则是两种实现方法。
第一种方法中,使用 if (traceElements != null && traceElements.length > place) ,通过调用层级place进行判断,因为处于低层的信息肯定是系统打印的信息(如上图中的getThreadStackTrace(VMStack java-2)),这个肯定不是我们想看到的。所以,只要控制好层级数并可以过滤掉掉这些无用的信息。上文中代码选用的层级为4。具体根据自己的实现进行更改(即上面的LogUtils是怎样实现的 )。
第二种方法是,通过stackTraceElement.getClassName().equals(L.class.getName()),即打印的信息是否是Log方法的调用方打印出来的,如上面的L类。在L类中调用打印Log,所以我们也只关心L类中输出的信息,而不去关心其他基类的信息。于是,通过判断类名是否为L类也可过滤掉无用信息。
上述两种方法均可实现过滤的效果,可以根据自身喜好进行选择。
参考文章: