Logger简介
用了蛮久的Logger框架,突然想自己做一个,所以从Github上down了一份源码,研究了一下,本身原理比较简单,主要是定制个人有个人需求,技术点就那么几个。
网上使用较高的就是orhanobut的Logger框架,题外话,用了蛮多他的插件的,属于国外的大神。
主要原理
StackTraceElement
关于这个StackTraceElement的原生api解释,网上有很多,我就简单说一下。
本身我们知道方法栈的概念就是当每个方法嵌套调用的时候,方法会以指针形式存到我们的方法栈,符合先进后出的原理,也就是最内层的方法是最后入栈的,出栈也就意为着程序执行这个方法。
我们核心代码是
StackTraceElement[] elements=Thread.currentThread().getStackTrace();
调用这个方法就能获取到当前方法所在方法栈信息,我们再来看看StackTraceElement这个类
StackTraceElement主要关注这几个方法,后面我们需要用到这几个api打印一些关键信息,以及定位。
另外需要注意的是上述的数组,因为会先走底层方法
at dalvik.system.VMStack.getThreadStackTrace(Native Method)
at java.lang.Thread.getStackTrace(Thread.java:579)
所以第三条信息才是我们定义的方法(自定义方法),接下来就是被嵌套的函数,依次类推。
源码分析
因为Logger框架有许多定制的地方,我们需要来看看各个实现,先不上升到设计模式,单纯功能性分析。
按照我的习惯,我喜欢先从功能入手来看源码,于是我首先查看了Logger类,一般辅助类与主类属于从属关系,我决定以下面的顺序进行解读(缩略版的话其实可以省略这些)
- Helper类,这个类看到他的使用后就知道他是个辅助类,类似util做一些处理
- isEmpty(CharSequence):boolean 判断字符串是否为空或者长度为0
- equals(CharSequence,CharSequence);boolean 逐字比较字符串是否相等
- getStackTraceString(Throwable):String 打印错误信息
- Setting因为嵌套在Printer里面所以首先选择解析这个
如图这些都是Settings的属性,其实Settings就跟他的名字一样用来控制Log的打印,这里的methodCount,methodOffset目前意义不详,我们到时候回过头来分析
- showThreadInfo 就是是否需要打印线程信息
- logAdapter 就是对系统提供的Log框架的分享
- logLevel 枚举有两个常量FULL,NONE就是是否需要打印log的开关了
3.AndroidLogAdapter是LogAdapter的实现,无非是系统log的实现
4.接下来看Logger类,除了init方法需要注意,也就是我们阅读程序的入口了,其他只是对外提供方法调用而已,在init里面我们看到了整个框架的核心LoggerPrinter
5.LoggerPrinter是Printer的实现,此类实现了所有打印的定制,格式问题我们就不展开了,具体看下几个核心业务的实现就可以动手写我们自己的了
仔细一看所有设计到log(e,w,i,d...)的最后都调用到log方法,那么就简单了我们之需要看这个函数就行了
logTopBorder(priority, tag);
logHeaderContent(priority, tag, methodCount);
//get bytes of message with system's default charset (which is UTF-8 for Android)
byte[] bytes = message.getBytes();
int length = bytes.length;
if (length <= CHUNK_SIZE) {
if (methodCount > 0) {
logDivider(priority, tag);
}
logContent(priority, tag, message);
logBottomBorder(priority, tag);
return;
}
if (methodCount > 0) {
logDivider(priority, tag);
}
for (int i = 0; i < length; i += CHUNK_SIZE) {
int count = Math.min(length - i, CHUNK_SIZE);
//create a new String with system's default charset (which is UTF-8 for Android)
logContent(priority, tag, new String(bytes, i, count));
}
logBottomBorder(priority, tag);
省略了前面的各种判断,这里首先打印了头,就是那个花哨的东西,然后判断log内容是否超出限制
然后我们再进到logHeaderContent,logContent函数
这两个函数分别打印线程信息,与消息体,这里我们只需要关注线程信息就可以了
private void logHeaderContent(int logType, String tag, int methodCount) {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
if (settings.isShowThreadInfo()) {
logChunk(logType, tag, HORIZONTAL_DOUBLE_LINE + " Thread: " + Thread.currentThread().getName());
logDivider(logType, tag);
}
String level = "";
int stackOffset = getStackOffset(trace) + settings.getMethodOffset();
//corresponding method count with the current stack may exceeds the stack trace. Trims the count
if (methodCount + stackOffset > trace.length) {
methodCount = trace.length - stackOffset - 1;
}
for (int i = methodCount; i > 0; i--) {
int stackIndex = i + stackOffset;
if (stackIndex >= trace.length) {
continue;
}
StringBuilder builder = new StringBuilder();
builder.append("║ ")
.append(level)
.append(getSimpleClassName(trace[stackIndex].getClassName()))
.append(".")
.append(trace[stackIndex].getMethodName())
.append(" ")
.append(" (")
.append(trace[stackIndex].getFileName())
.append(":")
.append(trace[stackIndex].getLineNumber())
.append(")");
level += " ";
logChunk(logType, tag, builder.toString());
}
}
根据前面的原理桌布来看就很容易理解了,我们首先获取队栈信息,然后打印线程信息,接着打印具体方法,这时候我们就知道Settings里面的offset的用处了,他就是用来帮助我们定位到自己的方法的,所以尽量不要去懂Settings里面的默认值,这个methodCount的含义就是总的嵌套方法数,写了这么多,其实要是不是初学者看下原理,基本自己能实现了
最后附上自己的github地址已上传Maven
网友评论