xLog是什么呢?看名字你也大概能猜到它是日志打印类,android的自带日志类为Log,这个框架确命名为xLog,相比是加了很多特殊的功能吧!所以说研究框架源码不只是更好的运用框架,也要通过研究学习人家源码的“”秘密“,这里的秘密我指它为好的方面,也就是优点,它的这些优点是怎么写出来的,从而把这些优点变成自己的。那么先说一下xLog的优点:
1、它可以打印xml、json格格式化的数据,也就是看起来舒服
2、它可以自己声明要打印信息的格式化结构(就是怎么显示,比如加个括号了,反正随你想怎么打印,怎么打印)
3、它可以为你的打印加上边框(看起来很酷,下面会贴图)
4、它可以打印堆栈信息
5、它可以打印线程信息(当然默认的打印只有线程的名字,可以自己扩展)
6、支持打印信息保存为文件(比如在异常发生时,我们可以将打印写入文件,并在空闲时将文件上传到服务器,便于找问题)
7、它不会出现信息打印不全的问题,因为Log默认每一条信息不能超过4k,超过只打印4k的信息量,超出部分省略
8、在打印之前进行拦截一下,可以进行额外的处理。
在研究之前先看一下怎么用的:XLog源码地址
首先可以直接在Application类中全局配置我们的xLog的输出格式,是否支持文件保存(保存到哪个文件)等等信息
LogConfiguration config = new LogConfiguration.Builder()
.tag("huoying") // 声明tag的名字
// .t() // 是否打印线程信息,默认不打印
// .st(2) //是否打印堆栈信息(参数为深度,其实这里就是打印几行堆栈)
// .b() // 是否有边框,默认没有边框
// .jsonFormatter(new MyJsonFormatter()) // 自定义json输出格式
// .xmlFormatter(new MyXmlFormatter()) // 自定义Xm输出格式,不设置用默认值
// .throwableFormatter(new MyThrowableFormatter()) // 自定义异常输出格式,不设置用默认值
// .threadFormatter(new MyThreadFormatter()) // 自定义线程输出格式,不设置用默认值
// .stackTraceFormatter(new MyStackTraceFormatter()) // 自定义堆栈信息输出格式,不设置用默认值
// .borderFormatter(new MyBoardFormatter()) // 自定义边框输出格式,不设置用默认值
// .addObjectFormatter(AnyClass.class, // 自定义输出对象的格式化格式,默认输出为string
// new AnyClassObjectFormatter())
.build();
如果你不需要保存文件的话接下来只需要调用这句代码就ok了
XLog.init(BuildConfig.DEBUG ? LogLevel.ALL : LogLevel.NONE,
config);
如果想把打印信息保存到文件中你得这么设置:
Printer androidPrinter = new AndroidPrinter(); // 默认android的打印类
Printer filePrinter = new FilePrinter // 声明将打印信息保存到文件中
.Builder(new File(Environment.getExternalStorageDirectory(), "xlogsample").getPath())
.fileNameGenerator(new DateFileNameGenerator()) // 不设置默认采用它 ChangelessFileNameGenerator("log")
// .backupStrategy(new MyBackupStrategy()) //不设置默认采用它 FileSizeBackupStrategy(1024 * 1024)
// .logFlattener(new MyLogFlattener()) // 不设置默认采用它 DefaultLogFlattener
.build();
XLog.init(BuildConfig.DEBUG ? LogLevel.ALL : LogLevel.NONE,
config,
androidPrinter,
filePrinter);
这些初始化工作做完后,那么你只需要调用 XLog.d、Xlog.e、Xlog.i等等,和android的自带的Log打印类一个样,所以这个框架另一个有点是使用成本低。好了,下面分别看一下打印信息的区别
设置打印线程:
12-06 11:32:41.929: V/XLog(20525): Thread: main
12-06 11:32:41.929: V/XLog(20525): Welcome XLog
打印信息如上,这里每次我们调用打印的时候先打印线程的名字然后再打印我们的信息(可以区分在哪个线程中执行的代码)
设置堆栈打印:
12-06 11:38:07.259: V/XLog(20525): ├ com.elvishew.xlogsample.MainActivity.printLog(MainActivity.java:309)
12-06 11:38:07.259: V/XLog(20525): ├ com.elvishew.xlogsample.MainActivity.access$7(MainActivity.java:261)
12-06 11:38:07.259: V/XLog(20525): ├ com.elvishew.xlogsample.MainActivity$5.onClick(MainActivity.java:121)
12-06 11:38:07.259: V/XLog(20525): ├ android.view.View.performClick(View.java:5273)
12-06 11:38:07.259: V/XLog(20525): ├ android.view.View$PerformClick.run(View.java:21315)
12-06 11:38:07.259: V/XLog(20525): ├ android.os.Handler.handleCallback(Handler.java:743)
12-06 11:38:07.259: V/XLog(20525): ├ android.os.Handler.dispatchMessage(Handler.java:95)
12-06 11:38:07.259: V/XLog(20525): ├ android.os.Looper.loop(Looper.java:150)
12-06 11:38:07.259: V/XLog(20525): ├ android.app.ActivityThread.main(ActivityThread.java:5665)
12-06 11:38:07.259: V/XLog(20525): ├ java.lang.reflect.Method.invoke(Native Method)
12-06 11:38:07.259: V/XLog(20525): ├ com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:822)
12-06 11:38:07.259: V/XLog(20525): └ com.android.internal.os.ZygoteInit.main(ZygoteInit.java:712)
12-06 11:38:07.259: V/XLog(20525): Welcome XLog
打印信息中先将除了框架打印信息的堆栈以前的信息打印出来,然后打印出自己设置的字符串 (Welcome XLog),这里并没有设置堆栈深度,所以全部当前点打印执行的堆栈
设置边框打印:
12-06 11:41:20.139: V/XLog(20525): ╔═══════════════════════════════════════════════════════════════════════════════════════════════════
12-06 11:41:20.139: V/XLog(20525): ║Welcome XLog
12-06 11:41:20.139: V/XLog(20525): ╚═══════════════════════════════════════════════════════════════════════════════════════════════════
这个打印看到了什么,要打印的信息被一个莫名的边框给括起来了,看起来是不是很酷。(这样打印的结果就是人群中一眼就可以找到想要找到的信息)。
打印json格式数据:
代码:logger.json("{name:\"hah\",age:\"13\"}");
12-06 13:40:49.546: D/XLog(17030): {
12-06 13:40:49.546: D/XLog(17030): "name": "hah",
12-06 13:40:49.546: D/XLog(17030): "age": "13"
12-06 13:40:49.546: D/XLog(17030): }
打印xml格式数据:
代码: logger.xml("<?xml version=\"1.0\" encoding=\"utf-8\"?><userdata createuser=\"false\"> </userdata>");
12-06 13:40:49.627: D/XLog(17030): <?xml version="1.0" encoding="UTF-8"?>
12-06 13:40:49.627: D/XLog(17030): <userdata createuser="false"> </userdata>
可以看见一坨坨不规范的字符串,输出的时候是那么的规范
打印信息文件保存:
在手机上找到自己设置的保存路径找到文件,打开就可以看见所有打印的信息。
接下来开始介绍这么实在的功能,内部源码是怎么实现的,一切的开头都是从XLog类开始的,那么这个类的模式在这可以叫它外观模式,以它为入口类串联整个子系统的用法。ok从 XLog.init(......)方法进入
public static void init(int logLevel, LogConfiguration logConfiguration, Printer... printers) {
if (sIsInitialized) {
throw new IllegalStateException("XLog is already initialized, do not initialize again");
}
sIsInitialized = true;
sLogLevel = logLevel;
if (logConfiguration == null) {
throw new IllegalArgumentException("Please specify a LogConfiguration");
}
sLogConfiguration = logConfiguration;
sPrinter = new PrinterSet(printers);
sLogger = new Logger(sLogConfiguration, sPrinter);
}
这个方法就是做一些初始工作,首先标记该方法已将被调用 sIsInitialized声明为true,然后记录日志的级别,将printers交给PrinterSet管理,最后创建Logger类用于打印日志和保存日志。
private static Logger sLogger;
/**
* Global log configuration.
*/
static LogConfiguration sLogConfiguration;
/**
* Global log printer.
*/
static Printer sPrinter;
/**
* Global log level, below of which would not be log.
*/
static int sLogLevel = LogLevel.ALL;
static boolean sIsInitialized;
可以看见以上初始化的类都声明为静态对象,那就是变相的把xLog当做单例,这也就是怪不得初始化一次,以后可以在不同的类中自由的定义不同打印类。当然,你也可以不用XLog这个类而直接操作Logger类,哪里用到打印就单独创建对象,当然两种方式各有利弊,任君选择。以下代码是第二种方式
Logger.Builder builder = new Logger.Builder();
builder.tag(tag);
//打印线程信息
builder.t();
//打印堆栈信息
builder.st(STACK_TRACE_DEPTHS[stackTraceDepth.getSelectedItemPosition()]);
//是否存在边框
builder.b();
//设置最终的打印类
builder.printers(
viewPrinter,
new AndroidPrinter(),
XLogSampleApplication.globalFilePrinter);
//打印信息
logger.i(MESSAGE);
public static void init(int logLevel, LogConfiguration logConfiguration) {
init(logLevel, logConfiguration, DefaultsFactory.createPrinter());
}
像上面这个方法,默认的采用了AndroidPrinter进行打印
public static Printer createPrinter() {
return new AndroidPrinter();
}
这里的DefaultsFactory采用了简单工厂的模式,默认为开发者提供了一些工具类的创建
public class DefaultsFactory {
//默认文件名字
private static final String DEFAULT_LOG_FILE_NAME = "log";
//单个文件最大保存的信息量
private static final long DEFAULT_LOG_FILE_MAX_SIZE = 1024 * 1024; // 1M bytes;
/**
* Create the default JSON formatter.
*/
public static JsonFormatter createJsonFormatter() {
return new DefaultJsonFormatter();
}
/**
* Create the default XML formatter.
*/
public static XmlFormatter createXmlFormatter() {
return new DefaultXmlFormatter();
}
/**
* Create the default throwable formatter.
*/
public static ThrowableFormatter createThrowableFormatter() {
return new DefaultThrowableFormatter();
}
/**
* Create the default thread formatter.
*/
public static ThreadFormatter createThreadFormatter() {
return new DefaultThreadFormatter();
}
/**
* Create the default stack trace formatter.
*/
public static StackTraceFormatter createStackTraceFormatter() {
return new DefaultStackTraceFormatter();
}
/**
* Create the default border formatter.
*/
public static BorderFormatter createBorderFormatter() {
return new DefaultBorderFormatter();
}
/**
* Create the default log flattener.
*/
public static LogFlattener createLogFlattener() {
return new DefaultLogFlattener();
}
/**
* Create the default printer.
*/
public static Printer createPrinter() {
return new AndroidPrinter();
}
/**
* Create the default file name generator for {@link FilePrinter}.
*/
public static FileNameGenerator createFileNameGenerator() {
return new ChangelessFileNameGenerator(DEFAULT_LOG_FILE_NAME);
}
/**
* Create the default backup strategy for {@link FilePrinter}.
*/
public static BackupStrategy createBackupStrategy() {
return new FileSizeBackupStrategy(DEFAULT_LOG_FILE_MAX_SIZE);
}
}
这里的方法只要带Formatter结尾的都是提供了信息格式化工具类,比如json输出的格式,在输出前需要DefaultJsonFormatter先进行数据格式化,ChangelessFileNameGenerator默认的文件名字计算类,FileSizeBackupStrategy(是否继续写入的文件判断类)。这里注意一下打印的配置类LogConfiguration,如果这里对一切的格式化输出格式都没有定义的话,那么这里在构造这个LogConfiguration的时默认调用initEmptyFieldsWithDefaultValues方法对格式化的对象进行创造,如下所示
public LogConfiguration build() {
initEmptyFieldsWithDefaultValues();
return new LogConfiguration(this);
}
private void initEmptyFieldsWithDefaultValues() {
if (jsonFormatter == null) {
jsonFormatter = DefaultsFactory.createJsonFormatter();
}
if (xmlFormatter == null) {
xmlFormatter = DefaultsFactory.createXmlFormatter();
}
if (throwableFormatter == null) {
throwableFormatter = DefaultsFactory.createThrowableFormatter();
}
if (threadFormatter == null) {
threadFormatter = DefaultsFactory.createThreadFormatter();
}
if (stackTraceFormatter == null) {
stackTraceFormatter = DefaultsFactory.createStackTraceFormatter();
}
if (borderFormatter == null) {
borderFormatter = DefaultsFactory.createBorderFormatter();
}
}
}
这些表明默认工厂就是在LogConfiguration配置对象创建的时候调用的为其所有的格式化类进行赋值操作。这里需要注意一下日志级别和android自带的日志级别是一样的,如下:
public class LogLevel {
/**
* Log level for Log.v.
*/
public static final int VERBOSE = android.util.Log.VERBOSE;
/**
* Log level for Log.d.
*/
public static final int DEBUG = android.util.Log.DEBUG;
/**
* Log level for Log.i.
*/
public static final int INFO = android.util.Log.INFO;
/**
* Log level for Log.w.
*/
public static final int WARN = android.util.Log.WARN;
/**
* Log level for Log.e.
*/
public static final int ERROR = android.util.Log.ERROR;
全部引用android的原生的值有木有,好接下来直接以某个级别打印跟进源码
public void i(String msg, Throwable tr) {
println(LogLevel.INFO, msg, tr);
}
public void w(Object object) {
println(LogLevel.WARN, object);
}
public void e(Object object) {
println(LogLevel.ERROR, object);
}
不管是何种级别的最后都是调用的Logger对象的println(.....)方法,如下:
private void println(int logLevel, Object[] array) {
if (logLevel < XLog.sLogLevel) {
return;
}
printlnInternal(logLevel, Arrays.deepToString(array));
}
首先对当前的级别进行判断,是否是大于VERBOSE 的值,android的源码定义如下,所有级别中 VERBOSE 是最小的
public static final int VERBOSE = 2;
/**
* Priority constant for the println method; use Log.d.
*/
public static final int DEBUG = 3;
/**
* Priority constant for the println method; use Log.i.
*/
public static final int INFO = 4;
/**
* Priority constant for the println method; use Log.w.
*/
public static final int WARN = 5;
/**
* Priority constant for the println method; use Log.e.
*/
public static final int ERROR = 6;
private void printlnInternal(int logLevel, String msg) {
String thread = logConfiguration.withThread
? logConfiguration.threadFormatter.format(Thread.currentThread())
: null;
//得到当前的全部堆栈信息
String stackTrace = logConfiguration.withStackTrace
? logConfiguration.stackTraceFormatter.format(
StackTraceUtil.getCroppedRealStackTrack(new Throwable().getStackTrace(),
logConfiguration.stackTraceDepth))
: null;
printer.println(logLevel, logConfiguration.tag, logConfiguration.withBorder
? logConfiguration.borderFormatter.format(new String[]{thread, stackTrace, msg})
: ((thread != null ? (thread + SystemCompat.lineSeparator) : "")
+ (stackTrace != null ? (stackTrace + SystemCompat.lineSeparator) : "")
+ msg));
}
这个方法首先判断是否设置了线程信息打印和堆栈信息打印,好这里假设已经设置了这两种打印。首先将当前线程信息通过格式化工具格式化一下,这里采用框架默认的格式化工具,也就是DefaultThreadFormatter这个类
public class DefaultThreadFormatter implements ThreadFormatter {
@Override
public String format(Thread data) {
return "Thread: " + data.getName();
}
}
这类只是获得了当前线程的名字并返回,这就证明了上面的打印结果,所有的格式化实现类都是实现了这个接口类,所以我们可以继承它进行扩展
public interface Formatter<T> {
/**
* Format the data to a readable and loggable string.
*
* @param data the data to format
* @return the formatted string data
*/
String format(T data);
}
同理,堆栈信息的格式化工具类为DefaultStackTraceFormatter类
public class DefaultStackTraceFormatter implements StackTraceFormatter {
@Override
public String format(StackTraceElement[] stackTrace) {
StringBuilder sb = new StringBuilder(256);
if (stackTrace == null || stackTrace.length == 0) {
return null;
} else if (stackTrace.length == 1) {
return "\t─ " + stackTrace[0].toString();
} else {
for (int i = 0, N = stackTrace.length; i < N; i++) {
if (i != N - 1) {
sb.append("\t├ ");
sb.append(stackTrace[i].toString());
sb.append(SystemCompat.lineSeparator);
} else {
sb.append("\t└ ");
sb.append(stackTrace[i].toString());
}
}
return sb.toString();
}
}
}
这个实现方法就是遍历堆栈信息然后在最后一条数据前加上└标志,在除了最后一条数据前加上├标志,符合上面的打印,不过需要注意一下\t的作用是在前面加8个空格。
这里还需注意的一个点就是当前堆栈信息的获取是通过new Throwable().getStackTrace()获取的。
public StackTraceElement[] getStackTrace() {
return getInternalStackTrace().clone();
}
private StackTraceElement[] getInternalStackTrace() {
if (stackTrace == EmptyArray.STACK_TRACE_ELEMENT) {
stackTrace = nativeGetStackTrace(stackState);
stackState = null; // Let go of intermediate representation.
return stackTrace;
} else if (stackTrace == null) {
return EmptyArray.STACK_TRACE_ELEMENT;
} else {
return stackTrace;
}
}
如上是通过jni调用c++方法nativeGetStackTrace获取的堆栈信息。上面有提到堆栈深度表示打印几行,ok下面揭晓这个答案
private static StackTraceElement[] getRealStackTrack(StackTraceElement[] stackTrace) {
int ignoreDepth = 0;
int allDepth = stackTrace.length;
//排除掉XLog的堆栈信息
for (int i = 0; i < allDepth; i++) {
if (!stackTrace[i].getClassName().startsWith(XLOG_CLASS_PREFIX)) {
ignoreDepth = i;
break;
}
}
int realDepth = allDepth - ignoreDepth;
StackTraceElement[] realStack = new StackTraceElement[realDepth];
System.arraycopy(stackTrace, ignoreDepth, realStack, 0, realDepth);
return realStack;
}
private static StackTraceElement[] cropStackTrace(StackTraceElement[] callStack,
int maxDepth) {
int realDepth = callStack.length;
if (maxDepth > 0) {
realDepth = Math.min(maxDepth, realDepth);
}
StackTraceElement[] realStack = new StackTraceElement[realDepth];
System.arraycopy(callStack, 0, realStack, 0, realDepth);
return realStack;
}
}
方法中的XLOG_CLASS_PREFIX=com.elvishew.xlog.,也就是说凡是堆栈信息包括com.elvishew.xlog.的堆栈信息全部抛弃,既然用了XLog框架,那么这个框架的代码执行当然就会产生堆栈信息,那么利用它打印堆栈信息当然就不需要这个框架的堆栈信息,所以这里需要将框架的堆栈信息过渌掉,值得一提的时越是后面的堆栈信息,也就是说越晚执行的堆栈信息越是放在集合前面的,所以有了上面代码把框架前面的堆栈全部去掉,最后通过System.arraycopy截取为新的堆栈。
获得完线程信息和堆栈信息以后,开始判断是否设置了边框打印如果设置边框打印将把三种数据通过边框格式化类格式化。如下:
public class DefaultBorderFormatter implements BorderFormatter {
private static final char VERTICAL_BORDER_CHAR = '║';
// Length: 100.
private static final String TOP_HORIZONTAL_BORDER =
"╔═════════════════════════════════════════════════" +
"══════════════════════════════════════════════════";
// Length: 99.
private static final String DIVIDER_HORIZONTAL_BORDER =
"╟─────────────────────────────────────────────────" +
"──────────────────────────────────────────────────";
// Length: 100.
private static final String BOTTOM_HORIZONTAL_BORDER =
"╚═════════════════════════════════════════════════" +
"══════════════════════════════════════════════════";
看到了什么,那么库的边框就是通过这三个特殊字符串结合组成的,好接着看实现方法
public String format(String[] segments) {
//判断传过来的字符集合是否合法
if (segments == null || segments.length == 0) {
return "";
}
String[] nonNullSegments = new String[segments.length];
int nonNullCount = 0;
//赋值给临时数组nonNullSegments
for (String segment : segments) {
if (segment != null) {
nonNullSegments[nonNullCount++] = segment;
}
}
if (nonNullCount == 0) {
return "";
}
StringBuilder msgBuilder = new StringBuilder();
//ystemCompat.lineSeparator就是\n(换行符)
//首先添加 "╔═════════════════════════════════════════════════" + "══════════════════════════════════════════════════"
//然后添加换行符
msgBuilder.append(TOP_HORIZONTAL_BORDER).append(SystemCompat.lineSeparator);
for (int i = 0; i < nonNullCount; i++) {
msgBuilder.append(appendVerticalBorder(nonNullSegments[i]));
if (i != nonNullCount - 1) {
msgBuilder.append(SystemCompat.lineSeparator).append(DIVIDER_HORIZONTAL_BORDER)
.append(SystemCompat.lineSeparator);
} else {
//最后一条数据加上 "╚═════════════════════════════════════════════════" +"══════════════════════════════════════════════════"
msgBuilder.append(SystemCompat.lineSeparator).append(BOTTOM_HORIZONTAL_BORDER);
}
}
return msgBuilder.toString();
}
private static String appendVerticalBorder(String msg) {
StringBuilder borderedMsgBuilder = new StringBuilder(msg.length() + 10);
//以换行符截取字符
String[] lines = msg.split(SystemCompat.lineSeparator);
for (int i = 0, N = lines.length; i < N; i++) {
if (i != 0) {
borderedMsgBuilder.append(SystemCompat.lineSeparator);
}
String line = lines[i];
//先加'║',在加字符串
borderedMsgBuilder.append(VERTICAL_BORDER_CHAR).append(line);
}
return borderedMsgBuilder.toString();
}
}
那种酷酷的边框打印就是通过这两个方法循环拼接字符串之后形成的代码,看到这有木有感觉很简单,其实写代码把简单的东西写到极限,就成就了不凡的代码。好了既然边框也加上了,那么最后就剩下调用PrinterSet这个对象进行打印了,PrinterSet是啥?那不就是就是init(......)方法里面创建的吗。
public class PrinterSet implements Printer {
private Printer[] printers;
/**
* Constructor, pass printers in and will use all these printers to print the same logs.
*
* @param printers the printers used to print the same logs
*/
public PrinterSet(Printer... printers) {
this.printers = printers;
}
@Override
public void println(int logLevel, String tag, String msg) {
for (Printer printer : printers) {
printer.println(logLevel, tag, msg);
}
}
}
最终调用的是PrinterSet对象的
println方法,遍历printers数组进行打印操作,根据上面的配置此时的Printer【】集合保存了AndroidPrinter和FilePrinter,好先看看AndroidPrinterpublic class AndroidPrinter implements Printer {
static final int MAX_LENGTH_OF_SINGLE_MESSAGE = 4063;
//截取超过4k的量,超过4k原生打印不全省略号
@Override
public void println(int logLevel, String tag, String msg) {
if (msg.length() <= MAX_LENGTH_OF_SINGLE_MESSAGE) {
printChunk(logLevel, tag, msg);
return;
}
int msgLength = msg.length();
int start = 0;
int end = start + MAX_LENGTH_OF_SINGLE_MESSAGE;
while (start < msgLength) {
printChunk(logLevel, tag, msg.substring(start, end));
start = end;
end = Math.min(start + MAX_LENGTH_OF_SINGLE_MESSAGE, msgLength);
}
}
/**
* Print single chunk of log in new line.
*
* @param logLevel the level of log
* @param tag the tag of log
* @param msg the msg of log
*/
void printChunk(int logLevel, String tag, String msg) {
android.util.Log.println(logLevel, tag, msg);
}
}
看到了什么,超过4k的数据是怎么打印的,分批打印,而最后的打印最终还是调用了android的原生的Log的 println方法,其实Log.i等其他级别的打印最后都是调用的Log.println方法进行打印的。ok,进入FilePrinter看看
public void println(int logLevel, String tag, String msg) {
if (lastFileName == null || fileNameGenerator.isFileNameChangeable()) {
String newFileName = fileNameGenerator.generateFileName(logLevel, System.currentTimeMillis());
if (newFileName == null || newFileName.trim().length() == 0) {
throw new IllegalArgumentException("File name should not be empty.");
}
if (!newFileName.equals(lastFileName)) {
if (mBufferedWriter != null) {
closeLogWriter();
}
lastFileName = newFileName;
openLogWriter();
}
}
// If some how the writer is not opened, just give up logging.
if (mBufferedWriter == null) {
return;
}
if (backupStrategy.shouldBackup(logFile)) {
// Backup the log file, and create a new log file.
closeLogWriter();
File backupFile = new File(folderPath, lastFileName + ".bak");
if (backupFile.exists()) {
backupFile.delete();
}
logFile.renameTo(backupFile);
openLogWriter();
if (mBufferedWriter == null) {
return;
}
}
try {
String flattenedLog = logFlattener.flatten(logLevel, tag, msg).toString();
mBufferedWriter.write(flattenedLog);
//写入一个/n,也就是换行
mBufferedWriter.newLine();
mBufferedWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
首先文件的命名默认提供了两种方式,一种根据时间,一种根据日志级别,当然我们也可以自己扩展,如下代码:
日志级别:
public class LevelFileNameGenerator implements FileNameGenerator {
@Override
public boolean isFileNameChangeable() {
return true;
}
/**
* Generate a file name which represent a specific log level.
*/
@Override
public String generateFileName(int logLevel, long timestamp) {
return LogLevel.getLevelName(logLevel);
}
}
日期:
public class DateFileNameGenerator implements FileNameGenerator {
ThreadLocal<SimpleDateFormat> mLocalDateFormat = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd", Locale.US);
}
};
@Override
public boolean isFileNameChangeable() {
return true;
}
/**
* Generate a file name which represent a specific date.
*/
@Override
public String generateFileName(int logLevel, long timestamp) {
SimpleDateFormat sdf = mLocalDateFormat.get();
sdf.setTimeZone(TimeZone.getDefault());
return sdf.format(new Date(timestamp));
}
}
计算完文名字就需要判断文件是否存在,不存在创建,继续判断文件的大小是否超过默认的设置大小,不超过直接写入日志,超过的话先删除原来文件,再创建新文件,也就是说抛弃以前的日志打印,默认文件最大值为1M。一般写入sd卡的框架都会限制文件最大值,例如
DiskLruCache,也就是可以保存的最大数量,当然这是必须的,你要问为什么?当然是为用户考虑了,如果不做限制,用户安装了你的app,内存空间一天天的没有了,用户还用个鸟啊。
这个方法最终将信息通过DefaultLogFlattener 将级别、标签、拼接的信息通过”|”拼接到一块,最终写到文件中,顾名思义查看文件的时候我们当然想区分日志级别、日志tag了。
public class DefaultLogFlattener implements LogFlattener {
@Override
public CharSequence flatten(int logLevel, String tag, String message) {
return Long.toString(System.currentTimeMillis())
+ '|' + LogLevel.getShortLevelName(logLevel)
+ '|' + tag
+ '|' + message;
}
}
文件的写入是通过io流如下:
rivate void openLogWriter() {
logFile = new File(folderPath, lastFileName);
if (!logFile.exists()) {
try {
File parent = logFile.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
logFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
lastFileName = null;
logFile = null;
return;
}
}
try {
FileOutputStream fos = new FileOutputStream(logFile, true);
OutputStreamWriter osw = new OutputStreamWriter(fos);
mBufferedWriter = new BufferedWriter(osw);
} catch (FileNotFoundException e) {
e.printStackTrace();
lastFileName = null;
logFile = null;
}
}
好了,就分析到这里。