前言
本篇文章将解析Android开源日志框架Logger的源码,Logger的版本是2.2.0,对这个框架还不了解的朋友可以先快速学习基本使用再来阅读本文。
初始化
先看一下Logger初始化的基本操作
FormatStrategy logcatFormatStrategy = PrettyFormatStrategy.newBuilder()
.tag("MyTag")
.showThreadInfo(true)
.logStrategy(new LogcatLogStrategy())
.methodCount(2)
.methodOffset(0)
.build();
AndroidLogAdapter logcatLogAdapter = new AndroidLogAdapter(logcatFormatStrategy);
Logger.addLogAdapter(logcatLogAdapter);
这里将策略模式和建造者模式结合在一起,根据不同的需求定制出不同显示策略的log(可以把日志打印到logcat上或是文件中)。接着再把该显示策略的适配器添加到Logger中去。跳进addLogAdapter方法中看看。
@NonNull private static Printer printer = new LoggerPrinter();
...
public static void printer(@NonNull Printer printer) {
Logger.printer = checkNotNull(printer);
}
...
public static void addLogAdapter(@NonNull LogAdapter adapter) {
printer.addAdapter(checkNotNull(adapter));
}
传进来的适配器被添加到Printer中去了,Printer只是一个接口,它的实现类是LoggerPrinter。所以再来看看LoggerPrinter的addAdapter方法。
private final List<LogAdapter> logAdapters = new ArrayList<>();
...
@Override
public void addAdapter(@NonNull LogAdapter adapter) {
logAdapters.add(checkNotNull(adapter));
}
可以看到我们一开始传进来的log适配器最终被放进了一个列表里,这个列表存放了各种策略的log适配器,后面会解释它的的作用。至此,Logger初始化就结束了。
打印日志
Logger有很多打印日志的方法,先来看看最常见的使用方法。
Logger.v("verbose");
Logger.i("info");
Logger.d("debug");
Logger.e("error");
Logger.w("warning");
这五个方法的实现其实区别不大,这里就以Logger.d方法为例来分析,跳进去看看。
//Logger.java
public static void d(@Nullable Object object) {
printer.d(object);
}
//LoggerPrinter.java
@Override public void d(@NonNull String message, @Nullable Object... args) {
log(DEBUG, null, message, args);
}
@Override public void d(@Nullable Object object) {
log(DEBUG, null, Utils.toString(object));
}
前面说过Printer的实现类是LoggerPrinter,所以实际上是调用了LoggerPrinter方法。再深入log方法看看。
/**
* This method is synchronized in order to avoid messy of logs' order.
*/
private synchronized void log(int priority,
@Nullable Throwable throwable,
@NonNull String msg,
@Nullable Object... args) {
checkNotNull(msg);
//1
String tag = getTag();
//2
String message = createMessage(msg, args);
//3
log(priority, tag, message, throwable);
}
注释上说使用synchronized是为了防止日志打印顺序错乱。这是合理的,毕竟可能会有多个线程同时调用这个方法。这里有三个步骤,先获取tag,看看getTag方法。
private final ThreadLocal<String> localTag = new ThreadLocal<>();
...
@Nullable private String getTag() {
String tag = localTag.get();
if (tag != null) {
localTag.remove();
return tag;
}
return null;
}
这里使用ThreadLocal了来区分不同的线程,也就是说不同的线程得到的Tag可能不一样,后面会解释原因。接着再来看createMessage方法。
@NonNull
private String createMessage(@NonNull String message, @Nullable Object... args) {
return args == null || args.length == 0 ? message : String.format(message, args);
}
可以看到当初调用Logger.d()的时候如果传入的只有字符串没有后面的参数,则直接返回这个字符串,如果传了参数就把这个字符串格式化。比如Logger.d(“num:%d”,123),这时返回的就是 “num:123” 这个字符串。最后再来看看log方法做了什么。
@Override public synchronized void log(int priority,
@Nullable String tag,
@Nullable String message,
@Nullable Throwable throwable) {
if (throwable != null && message != null) {
message += " : " + Utils.getStackTraceString(throwable);
}
if (throwable != null && message == null) {
message = Utils.getStackTraceString(throwable);
}
if (Utils.isEmpty(message)) {
message = "Empty/NULL log message";
}
//1
for (LogAdapter adapter : logAdapters) {
if (adapter.isLoggable(priority, tag)) {
adapter.log(priority, tag, message);
}
}
}
看到代码1的for循环,上面说过,在初始化的时候我们会通过Logger.addLogAdapterf方法把log适配器添加到logAdapters这个列表中,现在将所有的适配器都遍历一遍,调用它们log方法。比如,我们添加了一个logcat适配器和文件适配器,那么调用 Logger.d(“hello world”) 后就会在logcat和文件中打印这个日志了。
Loggger还能打印json和xml,基本使用如下。
//{"name":"PYJTLK","isHandsome":false}
Logger.json("{\"name\":\"PYJTLK\",\"isHandsome\":false}");
//<person><name>PYJTLK</name><handsome>false</handsome></person>
Logger.xml("<person><name>PYJTLK</name><handsome>false</handsome></person>");
实现非常简单,来看看代码。
@Override public void json(@Nullable String json) {
if (Utils.isEmpty(json)) {
d("Empty/Null json content");
return;
}
try {
json = json.trim();
//单个json
if (json.startsWith("{")) {
JSONObject jsonObject = new JSONObject(json);
String message = jsonObject.toString(JSON_INDENT);
d(message);
return;
}
//json数组
if (json.startsWith("[")) {
JSONArray jsonArray = new JSONArray(json);
String message = jsonArray.toString(JSON_INDENT);
d(message);
return;
}
e("Invalid Json");
} catch (JSONException e) {
e("Invalid Json");
}
}
再看看xml的解析。
@Override public void xml(@Nullable String xml) {
if (Utils.isEmpty(xml)) {
d("Empty/Null xml content");
return;
}
try {
Source xmlInput = new StreamSource(new StringReader(xml));
StreamResult xmlOutput = new StreamResult(new StringWriter());
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
transformer.transform(xmlInput, xmlOutput);
d(xmlOutput.getWriter().toString().replaceFirst(">", ">\n"));
} catch (TransformerException e) {
e("Invalid xml");
}
}
临时TAG
文章开头初始化Logger后,调用Logger.d()方法使用TAG是全局的。如果要临时使用其他的TAG,调用Logger.t().d()就可以了。
Logger.t("MyTag2").d("hello world");
/**
* Given tag will be used as tag only once for this method call regardless of the tag that's been
* set during initialization. After this invocation, the general tag that's been set will
* be used for the subsequent log calls
*/
public static Printer t(@Nullable String tag) {
return printer.t(tag);
}
注释上的意思是说调用这个方法传进来的TAG只会使用一次,这个方法结束后,Logger又会用回之前的全局TAG。如此一来就能做到临时变更TAG了,继续深入这个方法看看。
@Override public Printer t(String tag) {
if (tag != null) {
localTag.set(tag);
}
return this;
}
前面说过localTag是一个ThreadLocal,这里将临时TAG放了进去,接着再回顾一下上面使用到localTag的地方。
@Nullable private String getTag() {
String tag = localTag.get();
if (tag != null) {
localTag.remove();
return tag;
}
return null;
}
在打印的时候Logger就会把它取出来,然后localTag的TAG被移除,如此一来就实现了临时TAG的功能。这里需要注意,t() 和 d()是连着用的。如果先在线程A上调用t(),再到线程B调用d(),是无效的。那是因为两个线程的ThreadLocal的TAG值是不一样的
//有效,因为在同一个线程同时调用t()和d()
Logger.t("MyTag2").d("hello world");
//无效,此时线程A的ThreadLocal存着临时TAG,可线程B的ThreadLocal中存放空值
Logger.t("MyTag2");//线程A
Logger.d("hello world");//线程B
最后
本篇文章对Logger的源码解析了一定解析,相信这样各位对Android Logger会有更深的认识。