LogUtils icon indicating copy to clipboard operation
LogUtils copied to clipboard

java.lang.StackOverflowError 错误问题

Open zwc456baby opened this issue 5 years ago • 4 comments

已经找到原因以及解决办法(由于个人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;
    }

zwc456baby avatar Mar 24 '20 03:03 zwc456baby

修改后的打印日志:

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 }

已经跳出了解析。

zwc456baby avatar Mar 24 '20 03:03 zwc456baby

棒棒棒!! 最近工作比较忙,周末会抽空看看的,感谢反馈

pengwei1024 avatar Mar 24 '20 04:03 pengwei1024

另外。Log4a 日志文件写入库,cpp 文件我提交过 bug 修复,Log4a 已经合并到 主分支了

https://github.com/pqpo/Log4a/commit/6942b7a3f7753df94dd0907500035c54a96ef32e

建议您也从 Log4a 重新同步一下 cpp 文件

zwc456baby avatar Mar 24 '20 05:03 zwc456baby

9150e4e5gy1gbmmiwmht8j205k05kdfr

pengwei1024 avatar Mar 24 '20 05:03 pengwei1024