public static class Builder {
int methodCount = 2;
int methodOffset = 0;
boolean showThreadInfo = true;
@Nullable LogStrategy logStrategy;
@Nullable String tag = "PRETTY_LOGGER";
下面是最重要的 log 方法的实现:
@Override public void log(int priority, @Nullable String onceOnlyTag, @NonNull String message) {
// 必要的参数合法性校验,每个人都需要注意
checkNotNull(message);
// 一次性日志的使用
String tag = formatTag(onceOnlyTag);
// 上层边框
logTopBorder(priority, tag);
// 打印线程信息和方法信息
logHeaderContent(priority, tag, methodCount);
//get bytes of message with system's default charset (which is UTF-8 for Android)
// 默认编码 UTF—8, 获取 message 长度
byte[] bytes = message.getBytes();
int length = bytes.length;
// 支持最大长度 4000 个字节Byte, 可使用 adb logcat -d 查看大小
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);
}
其中打印方法的函数如下:
private void logHeaderContent(int logType, @Nullable String tag, int methodCount) {
// 获取当前执行的所有堆栈帧
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
if (showThreadInfo) {
logChunk(logType, tag, HORIZONTAL_LINE + " Thread: " + Thread.currentThread().getName());
logDivider(logType, tag);
}
// 控制打印方法的缩进
String level = "";
// 方法的偏移量
int stackOffset = getStackOffset(trace) + methodOffset;
//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(HORIZONTAL_LINE)
.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());
}
}
Logger:Powerful logging library in Android
[TOC]
这篇文章介绍 Android 中常用的日志功能。
日志上在开发中常用的一个工具,可以打印任何需要的信息来辅助开发,系统提供的日志模块在这里不做过多叙述;下面会分别从基本使用和源码去介绍一个强大的日志工具。
基本用法
这个日志的地址和基本使用方法在其开源库主页都有基本介绍:logger.
总结一下对于使用者来说,优点在哪:
使用
这里还有一个常见的开发技巧:对于第三方库来说,尽量自己再包装一层,方便以后替换。
初始化:
ThirdModule.kt
新建文件
LogUtils
进行常用方法封装上述的基本的用法介绍,对于 debug 级别的日志来说,可以打印很多数据类型,其余级别只支持打印 String 类型的数据。
基本使用和日志输出见官网
进阶使用
在进阶使用中,可以配置相关的日志输出选项,比如线程信息,方法信息等, 见下面代码:
相应代码的输出如下:
此外, 还可以控制日志是否打印, 以及自定义 logtag 输出到文件。
优化
在 Android 常见的性能优化中,对于三方库来说,能延迟初始化的一定与延迟初始化,所以这里没有必要在 Provider 中做初始化,可以在具体使用的地方初始化,改动如下:
源码分析
首先看一下官网的流程图。
初始化
Logger.addLogAdapter(AndroidLogAdapter())
: 首先来看初始化工作做了什么:初始化 AndroidAdapter,如上所示,是接口 LogAdapter的具体实现类,另一个是 DiskLogAdater;分别用来输出到 LogCat 和文件,其内部的接口变量 FormatStrategy 来实现具体的内容。
首先来看 AndroidLogAdapter:
两个构造方法,提供默认实现和接收自定义的输出策略(对参数的必要性检查)。
默认的输出策略如下:PrettyFormatStrategy, 与 CsvFormatStrategy 一样,是 FormatStrategy 的具体实现类,控制不同的输出。
采用 Build Pattern 创建进阶使用的相关参数
下面是最重要的 log 方法的实现:
其中打印方法的函数如下:
其中一个堆栈帧如下:
打印的方法数由偏移量决定,上述得到的偏移量为8(默认5 + 非 LoggerPrinter 和 非 Logger), 打印方法数为2, 所以在控制台看到打印的。
打印流程
至此,分析了初始化的相关源码, 下面来看一下一个具体的
Logger.d
是如何工作的。具体的实现在 Logger 类里面:
当 debug 级别输出一个对象时,
需要注意的一点:Logger 提供 一次打印 tag,
private final ThreadLocal<String> localTag = new ThreadLocal<>();
是用 ThreadLocal 来保存,隔离线程。那么,到现在,Logger.d() 方法输出到控制台的整个流程和源码都完成了分析。
其余的其他方法, 包括保存在 Disk 的方法这里就不做过多叙述,有疑问可以提 issue 共同探讨。
输出到文件
该日志库除了打印到控制台外,还可以将日志内容输出到文件。使用方法上面介绍过,这里着重说一下如何写到文件。
当进行日志输出时,首先使用 HandlerThread 创建了一个 handler, 在自线程中完成耗时的 IO 操作。
然后 handler 发送消息,并处理消息完成工作。代码如下:
具体写入的代码如下:
总结