LogUtils
LogUtils copied to clipboard
java.lang.StackOverflowError 错误问题
已经找到原因以及解决办法(由于个人fork 了此项目一份代码,并且改动很大,源码已经和 LogUtils 库不同了。所以不好修改,待我有空会提交并关闭此问题) 提出此问题望作者修复 谢谢。
复现代码
// 先创建一个 map 和 message
// 注意,map 和 message 都有 Parser 实现
// 分别对应 MapParse 和 MessageParse
Map<String, Object> testMap = new HashMap<>();
Message message = Message.obtain();
// 在这里,map 持有 message, 而 message 持有 map
message.obj = testMap;
testMap.put("testKey", message);
LogUtil.objToString(testMap);
其中 LogUtil.objToString 方法是我调试用的方法
它实际是这样的
public static String objToString(Object object) {
return ObjectUtil.getInstance().objectToString(object);
}
也就是说,ObjectUtil 的 objectToString 方法内部实现有一点小错误
分析及解决
原因就是循环引用后,解析的时候,循环解析了。 虽然平常确实不会有这种情况,但是实际确实遇到了
viewModel?.helloWorld?.observe(this, Observer {
// LogUtil.objToString()
默认情况下,解析 ObservableField 会死循环
logger.i("listener on changed" + com.zhouzhou.newsdemo.LogUtil.objToString(it))
})
分析:
以上面代码为例
当检测到 Object 是一个 Map 的时候,调用了 MapParse.parseString 方法
然后 MapParse 中 又调用了 ObjectUtil.getInstance().objectToString(value) 来解析 value
而此 value 就是 Message,而 Message 中又引用了 Map,这时候又去解析 Map。不断的循环
然后......就炸了
解决
在 ObjectUtil 类中,解析 Object 对象时,传递了 childLevel 层级。 所以,对 Object 对象的循环引用不会导致死循环。 但是对继承Parser的解析类,没有传递 childLevel 层级,导致解析时死循环
解决办法应该是:将 Parser 接口方法 String parseString(T t); 增加一个参数,childLevel 层级
在 Parser 实现类中,调用 ObjectUtil.getInstance().objectToString 方法时,将 childLevel 层级始终传递并 +1,使其可以被跳出,防止死循环了
不与此 bug 有关的一点优化建议
package com.apkfuns.logutils 中的 Logger
方法 getTopStackInfo 调用了两次 getCurrentStackTrace
private String getTopStackInfo() {
String customTag = mLogConfig.getFormatTag(getCurrentStackTrace());
if (customTag != null) {
return customTag;
}
StackTraceElement caller = getCurrentStackTrace();
String stackTrace = caller.toString();
stackTrace = stackTrace.substring(stackTrace.lastIndexOf('('), stackTrace.length());
String tag = "%s.%s%s";
String callerClazzName = caller.getClassName();
callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1);
tag = String.format(tag, callerClazzName, caller.getMethodName(), stackTrace);
return tag;
}
建议改成只调用一次 getCurrentStackTrace
因为 Thread.currentThread().getStackTrace() 方法是一个相对耗时操作。
之前测试过 性能。改成只调用一次之后,性能提升大约 百分之70
大概就是 一万条日志从 400ms 减少到 两百多ms
private String getTopStackInfo() {
StackTraceElement caller = getCurrentStackTrace();
if (caller == null) return "Null Stack Trace";
String stackTrace = caller.toString();
stackTrace = stackTrace.substring(stackTrace.lastIndexOf('('));
String tag = "%s.%s%s";
String callerClazzName = caller.getClassName();
callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1);
tag = String.format(tag, callerClazzName, caller.getMethodName(), stackTrace);
return tag;
}
private StackTraceElement getCurrentStackTrace() {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
int stackOffset = getStackOffset(trace);
return stackOffset == -1 ? null : trace[stackOffset];
}
private int getStackOffset(StackTraceElement[] trace) {
for (int i = tackOffset; i < trace.length; i++) {
if (trace[i].getClassName().equals(findTackClassName)) return ++i;
}
return -1;
}
修改后的打印日志:
I/LogUtils: java.util.HashMap [
testKey -> android.os.Message [
what = 0
when = 0
arg1 = 0
arg2 = 0
data = android.os.Bundle [
]
obj = java.util.HashMap [
testKey -> { when=-19d7h52m44s765ms barrier=0 }
]
]
]
已经没有循环解析导致的问题了,最后的
testKey -> { when=-19d7h52m44s765ms barrier=0 }
已经跳出了解析。
棒棒棒!! 最近工作比较忙,周末会抽空看看的,感谢反馈
另外。Log4a 日志文件写入库,cpp 文件我提交过 bug 修复,Log4a 已经合并到 主分支了
https://github.com/pqpo/Log4a/commit/6942b7a3f7753df94dd0907500035c54a96ef32e
建议您也从 Log4a 重新同步一下 cpp 文件
