Timber

作者: Poseidon_Wang | 来源:发表于2017-04-24 18:13 被阅读97次

    最近项目需要开始搭建框架,偶然了解到(Timeber)[],因为有数据安全性的问题,所以跟数据有关的,我个人觉得还是自己写下 比较靠谱,对比了(Logger)[],各取所长吧。Logger的话前面已经讲过远离,所以今天就来看下Timber的源码。Timber的源码比较短,就一个文件。

    源码阅读

    按照我的习惯,我阅读源码都是从使用出发。这里我们随便拿一个log.v的方法追踪进去
    <pre>
    public static void v(@NonNls String message, Object... args) {
    TREE_OF_SOULS.v(message, args);
    }
    </pre>
    然后追踪到TREE_OF_SOULS发现它是Tree,然后我门继续看Tree,是个抽象类,这里需要特别注意下这个类,因为下面要将的DebugTree也是继承这个规范。这里我们主要看prepareLog方法,因为所有的i,v,d最终调用的都是这个方法,当然
    <pre>
    private void prepareLog(int priority, Throwable t, String message, Object... args) {
    // Consume tag even when message is not loggable so that next message is correctly tagged.
    String tag = getTag();

            if (!isLoggable(tag, priority)) {
                return;
            }
            if (message != null && message.length() == 0) {
                message = null;
            }
            if (message == null) {
                if (t == null) {
                    return; // Swallow message if it's null and there's no throwable.
                }
                message = getStackTraceString(t);
            } else {
                if (args.length > 0) {
                    message = formatMessage(message, args);
                }
                if (t != null) {
                    message += "\n" + getStackTraceString(t);
                }
            }
    
            log(priority, tag, message, t);
        }
    

    </pre>
    首先来看看getTag()方法
    <pre>
    final ThreadLocal<String> explicitTag = new ThreadLocal<>();
    String getTag() {
    String tag = explicitTag.get();
    if (tag != null) {
    explicitTag.remove();
    }
    return tag;
    }
    </pre>
    ThreadLocal 这是一个线程独立的变量,有兴趣的同学可以看看我前面讲Handler原理的文章,简单就是隔离一个数据,实现1个类只拥有唯一的资源的需求。函数体只涉及到变量的remove,所以我们就来找一下这个变量的赋值来搞清具体用处,我们查找explicitTag的所有使用,发现唯一赋值的地方在我们的外层Timber类里
    <pre>
    public static Tree tag(String tag) {
    Tree[] forest = forestAsArray;
    //noinspection ForLoopReplaceableByForEach
    for (int i = 0, count = forest.length; i < count; i++) {
    forest[i].explicitTag.set(tag);
    }
    return TREE_OF_SOULS;
    }
    </pre>
    这里循环遍历了我们的Tree数组,将所有内部Tree的Tag都进行了赋值。
    然后我门再回过头来看看prepareLog函数,这里主要对message进行了处理,特别关注的是Throwable参数,这里有个getStackTraceString方法,往内层追踪到内层用了Throwable类的printStackTrace方法,这里先不管她的实现细节,总之是获取了Throwable里的信息放到了流里面,最后格式化好message调用log方法,log是个抽象方法由用户实现,这条道就走到底了,接下来我们再来看看Tree的几个实现类,最开始的TREE_OF_SOULS变量,他是一个匿名内部Tree的实现类,覆盖了v,d这些方法,后面我门调用plant方法的时候会往forestAsArray数组里面添加我门的Tree实现
    <pre>
    private static final Tree[] TREE_ARRAY_EMPTY = new Tree[0];
    // Both fields guarded by 'FOREST'.
    private static final List<Tree> FOREST = new ArrayList<>();
    static volatile Tree[] forestAsArray = TREE_ARRAY_EMPTY;
    @Override
    public void v(String message, Object... args) {
    Tree[] forest = forestAsArray;
    //noinspection ForLoopReplaceableByForEach
    for (int i = 0, count = forest.length; i < count; i++) {
    forest[i].v(message, args);
    }
    }
    </pre>
    明明都是final的变量,而且是长度为0的数组,然后又加了遍历,好吧,反正先放着等下再来揣测作者的意图,这个内部类就看完了,我们把看完的代码折起来,现在再来看剩下的,这里有个DebugTree,代码比较短,但我们必须每个方法都来看看。因为这里重载了getTag所以其实会被优先调用(根据前面Tree代码的阅读)
    <pre>
    @Override
    final String getTag() {
    String tag = super.getTag();
    if (tag != null) {
    return tag;
    }

            // DO NOT switch this to Thread.getCurrentThread().getStackTrace(). The test will pass
            // because Robolectric runs them on the JVM but on Android the elements are different.
            StackTraceElement[] stackTrace = new Throwable().getStackTrace();
            if (stackTrace.length <= CALL_STACK_INDEX) {
                throw new IllegalStateException(
                        "Synthetic stacktrace didn't have enough elements: are you using proguard?");
            }
            return createStackElementTag(stackTrace[CALL_STACK_INDEX]);
        }
    

    </pre>
    这里重点关注下StackTraceElement[] stackTrace = new Throwable().getStackTrace()这句代码上面做了注释,相比于Thread.getCurrentThread().getStackTrace(),获得的线程信息更详细一点具体见下图,这里我们发现new throwable少了vm那层的信息,对于我们来讲这一层其实也没什么参考价值

    WX20170424-174628@2x.png
    这里我门可以看到主要可操控的地方在于对堆栈信息的筛选。
    然后我门回过头来看看plant函数,也是我门的初始化函数
    <pre>
    public static void plant(Tree tree) {
    if (tree == null) {
    throw new NullPointerException("tree == null");
    }
    if (tree == TREE_OF_SOULS) {
    throw new IllegalArgumentException("Cannot plant Timber into itself.");
    }
    synchronized (FOREST) {
    FOREST.add(tree);
    forestAsArray = FOREST.toArray(new Tree[FOREST.size()]);
    }
    }
    </pre>
    这里的关键点在于FORSEST.add(),这个函数将我门实现的Tree加了进去,而在Tree的实现里我门又看到具体的log实现是遍历我门的Tree数组进行打印,到这里其实Timber实现的是一个代理的作用,对日志信息进行了管理。

    相关文章

      网友评论

          本文标题:Timber

          本文链接:https://www.haomeiwen.com/subject/igkpzttx.html