最近项目需要开始搭建框架,偶然了解到(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那层的信息,对于我们来讲这一层其实也没什么参考价值
这里我门可以看到主要可操控的地方在于对堆栈信息的筛选。
然后我门回过头来看看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实现的是一个代理的作用,对日志信息进行了管理。