Android Logger日志框架源码解析

前言

本篇文章将解析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会有更深的认识。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值