Android 自定义Log 多模式
先上个效果图:
模板一:
模板二:
LOG常用的方法有以下5个:
Log.v() Log.d() Log.i() Log.w() Log.e() 。分别对应下图,除Assert
1、Verbose 的调试颜色为黑色的,任何消息都会输出,这里的v代表verbose啰嗦的意思,平时使用就是Log.v(“”,”“);
2、Debug 的输出颜色是蓝色的,仅输出debug调试的意思,但他会输出上层的信息,过滤起来可以通过DDMS的Logcat标签来选择.
3、Info 的输出为绿色,一般提示性的消息information,它不会输出Log.v和Log.d的信息,但会显示i、w和e的信息
4、Warn 的意思为橙色,可以看作为warning警告,一般需要我们注意优化Android代码,同时选择它后还会输出Log.e的信息。
5、Error 为红色,可以想到error错误,这里仅显示红色的错误信息,这些错误就需要我们认真的分析,查看栈的信息了。
6、Assert 表示断言失败后的错误消息,这类错误原本是不可能出现的错误,现在却出现了,是极其严重的错误类型。
Debug属于调试日志
其他五类Log的重要程度排序如下。
Assert > Error > Warn > Info > Verbose
推荐颜色:
- Verbose:#000000
- Debug :#0000FF
- Info:#008700
- Warn:#878700
- Error:#FF0000
-
Assert:#8F0005
Android Studio设置颜色步骤:
File ->Setting ->Editor->Color&Fonts->Android Logcat
有个Log的工具类:https://github.com/orhanobut/logger
用法很简单:
//在Application中初始化一下
Logger.addLogAdapter(new AndroidLogAdapter());
//就可以用了
Logger.v("verbose");
Logger.d("debug");
Logger.i("information");
Logger.w("warning");
Logger.e("error");
Logger.json("{\"a\": \"Hello\", \"b\": \"World\"}");
Logger.xml("<note>\n" +
"<to>George</to>\n" +
"<from>John</from>\n" +
"<heading>Reminder</heading>\n" +
"<body>Don't forget the meeting!</body>\n" +
"</note>");
好炫酷,好想自己写一个,看了看源码,试试写个简单的
先分析下源码的执行流程:
Logger.v
-> LoggerPrinter.log
-> AndroidLogAdapter.log
-> PrettyFormatStrategy.log
-> PrettyFormatStrategy.logChunk
-> LogcatLogStrategy.log 至此打印结束
多日志打印即循环执行此过程,此项目很好的使用了策略模式和Builder模式。
其中:
Printer 的实现类有:LoggerPrinter
LogAdapter 的实现类有:AndroidLogAdapter、DiskLogAdapter
FormatStrategy 的实现类有:CsvFormatStrategy、PrettyFormatStrategy
LogStrategy 的实现类有:DiskLogStrategy、LogcatLogStrategy
BuildConfig 里面是一些常量
Logger 一些具体的执行方法,调用LoggerPrinter 中的具体实现供客户端调用
Utils 工具类
下面仿照大佬的代码写个简单的吧
既然是简单的,就简单到底,就3个核心类(实际就一个):
1、IPrinter.java(定义一些接口)
package com.zx.logs;
public interface IPrinter {
void v(String message, Object... args);
void d(String message, Object... args);
void i(String message, Object... args);
void w(String message, Object... args);
void e(String message, Object... args);
void json(String json);
void xml(String xml);
void log(int priority, String message, Throwable throwable, Object... args);
// priority 优先顺序
// message 消息
// tag 日志中tag,相当于Logcat Filter
// throwable 子线程中打印日志,提取发生行数
// args String格式化字符串
void log(int priority, String message, String tag, Throwable throwable, Object... args);
}
2、Logs.java(暴露出来方法)
package com.zx.logs;
public class Logs {
private static IPrinter iPrinter = new LogsPrinter();
public static void v(String message, Object... args) {
iPrinter.v(message, args);
}
public static void d(String message, Object... args) {
iPrinter.d(message, args);
}
public static void i(String message, Object... args) {
iPrinter.i(message, args);
}
public static void w(String message, Object... args) {
iPrinter.w(message, args);
}
public static void e(String message, Object... args) {
iPrinter.e(message, args);
}
public static void log(int priority, String tag, String message, Throwable throwable) {
iPrinter.log(priority, tag, message, throwable);
}
public static void json(String json) {
iPrinter.json(json);
}
public static void xml(String xml) {
iPrinter.xml(xml);
}
}
3、LogsPrinter.java(大部分处理都在这里了)
package com.zx.logs;
import android.text.TextUtils;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.UnknownHostException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class LogsPrinter implements IPrinter {
//Android对日志项的最大限制是4076字节,所以4000个字节作为块的大小从默认的字符集
private static final int CHUNK_SIZE = 4000;
//最小堆栈
private static final int MIN_STACK_OFFSET = 5;
public static final int VERBOSE = 2;
public static final int DEBUG = 3;
public static final int INFO = 4;
public static final int WARN = 5;
public static final int ERROR = 6;
public static final int ASSERT = 7;
private static final int JSON_INDENT = 2;
private String tag = "LogsPrinter";
Pattern mPattern = new Pattern();
public void setTag(String tag) {
this.tag = tag;
}
@Override
public void v(String message, Object... args) {
log(VERBOSE, message, args);
}
@Override
public void d(String message, Object... args) {
log(DEBUG, message, args);
}
@Override
public void i(String message, Object... args) {
log(INFO, message, args);
}
@Override
public void w(String message, Object... args) {
log(WARN, message, args);
}
@Override
public void e(String message, Object... args) {
log(ERROR, message, args);
}
@Override
public void json(String json) {
if (TextUtils.isEmpty(json)) {
d("Empty/Null json content");
return;
}
try {
json = json.trim();
if (json.startsWith("{")) {
JSONObject jsonObject = new JSONObject(json);
String message = jsonObject.toString(JSON_INDENT);
d(message);
return;
}
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");
}
}
@Override
public void xml(String xml) {
if (TextUtils.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");
}
}
private String getStackTraceString(Throwable tr) {
if (tr == null) {
return "";
}
// This is to reduce the amount of log spew that apps do in the non-error
// condition of the network being unavailable.
Throwable t = tr;
while (t != null) {
if (t instanceof UnknownHostException) {
return "";
}
t = t.getCause();
}
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
tr.printStackTrace(pw);
pw.flush();
return sw.toString();
}
private synchronized void log(int priority, String message, Object... args) {
log(priority, message, null, args);
}
@Override
public synchronized void log(int priority, String message, Throwable throwable, Object... args) {
message = configMessage(message, throwable, args);
logNext(priority, tag, message);
}
@Override
public synchronized void log(int priority, String message, String tag, Throwable throwable, Object... args) {
message = configMessage(message, throwable, args);
logNext(priority, tag, message);
}
private String configMessage(String message, Throwable throwable, Object... args) {
message = createMessage(message, args);
if (throwable != null && message != null) {
message += " : " + getStackTraceString(throwable);
}
if (throwable != null && message == null) {
message = getStackTraceString(throwable);
}
return message;
}
//大于0就显示打印日志位置
int methodCount = 2;
//是否显示线程信息 Thread: main(主线程中打印)或Thread: Thread-4(子线程中打印)
boolean showThreadInfo = true;
int methodOffset = 0;
private synchronized void logNext(int priority, String tag, String message) {
logTopBorder(priority, tag);
logHeaderContent(priority, tag, methodCount);
//得到系统的默认字符集的信息字节(Android是UTF-8)
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);
}
//日志--顶部边框
private void logTopBorder(int logType, String tag) {
logChunk(logType, tag, mPattern.getTopBorder());
}
//日志--底部边框
private void logBottomBorder(int logType, String tag) {
logChunk(logType, tag, mPattern.getBottomBorder());
}
//日志--分割线
private void logDivider(int logType, String tag) {
logChunk(logType, tag, mPattern.getMiddleBorder());
}
@SuppressWarnings("StringBufferReplaceableByString")
private void logHeaderContent(int logType, String tag, int methodCount) {
//获取当前线程状态
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
if (showThreadInfo) {
logChunk(logType, tag, mPattern.getHorizontalLine() + " 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(mPattern.getHorizontalLine())
.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());
}
}
//日志--内容
private void logContent(int logType, String tag, String chunk) {
String[] lines = chunk.split(System.getProperty("line.separator"));
for (String line : lines) {
logChunk(logType, tag, mPattern.getHorizontalLine() + " " + line);
}
}
private void logChunk(int priority, String tag, String chunk) {
Log.println(priority, tag, chunk);
}
//确定堆栈跟踪的起始索引
private int getStackOffset(StackTraceElement[] trace) {
for (int i = MIN_STACK_OFFSET; i < trace.length; i++) {
StackTraceElement e = trace[i];
String name = e.getClassName();
if (!name.equals(LogsPrinter.class.getName()) && !name.equals(Logs.class.getName())) {
return --i;
}
}
return -1;
}
//截取最后一次出现的位置的下一个
private String getSimpleClassName(String name) {
int lastIndex = name.lastIndexOf(".");
return name.substring(lastIndex + 1);
}
//字符串格式化
private String createMessage(String message, Object... args) {
return args == null || args.length == 0 ? message : String.format(message, args);
}
}
4、Pattern.java(一个模式选择类)
package com.zx.logs;
public class Pattern {
private static final int SINGLE_LINE = 1;
private static final int DOUBLE_LINE = 2;
//模式切换
private int type = SINGLE_LINE;
private char topLeftCorner;
private char bottomLeftCorner;
private char middleCorner;
private char horizontalLine;
private String doubleDivider;
private String singleDivider;
private String topBorder;
private String bottomBorder;
private String middleBorder;
private char getChar(char value1, char value2) {
char corner = value1;
switch (type) {
case SINGLE_LINE:
corner = value1;
break;
case DOUBLE_LINE:
corner = value2;
break;
}
return corner;
}
private String getString(String value1, String value2) {
String corner = value1;
switch (type) {
case SINGLE_LINE:
corner = value1;
break;
case DOUBLE_LINE:
corner = value2;
break;
}
return corner;
}
public char getTopLeftCorner() {
topLeftCorner = getChar(SingleLine.TOP_LEFT_CORNER, DoubleLine.TOP_LEFT_CORNER);
return topLeftCorner;
}
public char getBottomLeftCorner() {
bottomLeftCorner = getChar(SingleLine.BOTTOM_LEFT_CORNER, DoubleLine.BOTTOM_LEFT_CORNER);
return bottomLeftCorner;
}
public char getMiddleCorner() {
middleCorner = getChar(SingleLine.MIDDLE_CORNER, DoubleLine.MIDDLE_CORNER);
return middleCorner;
}
public char getHorizontalLine() {
horizontalLine = getChar(SingleLine.HORIZONTAL_LINE, DoubleLine.HORIZONTAL_LINE);
return horizontalLine;
}
public String getDoubleDivider() {
doubleDivider = getString(SingleLine.DOUBLE_DIVIDER, DoubleLine.DOUBLE_DIVIDER);
return doubleDivider;
}
public String getSingleDivider() {
singleDivider = getString(SingleLine.SINGLE_DIVIDER, DoubleLine.SINGLE_DIVIDER);
return singleDivider;
}
public String getTopBorder() {
topBorder = getString(SingleLine.TOP_BORDER, DoubleLine.TOP_BORDER);
return topBorder;
}
public String getBottomBorder() {
bottomBorder = getString(SingleLine.BOTTOM_BORDER, DoubleLine.BOTTOM_BORDER);
return bottomBorder;
}
public String getMiddleBorder() {
middleBorder = getString(SingleLine.MIDDLE_BORDER, DoubleLine.MIDDLE_BORDER);
return middleBorder;
}
static class SingleLine {
private static final char TOP_LEFT_CORNER = '┌';
private static final char BOTTOM_LEFT_CORNER = '└';
private static final char MIDDLE_CORNER = '├';
private static final char HORIZONTAL_LINE = '│';
private static final String DOUBLE_DIVIDER = "────────────────────────────────────────────────────────";
private static final String SINGLE_DIVIDER = "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄";
private static final String TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
private static final String BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
private static final String MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER;
}
static class DoubleLine {
private static final char TOP_LEFT_CORNER = '╔';
private static final char BOTTOM_LEFT_CORNER = '╚';
private static final char MIDDLE_CORNER = '╟';
private static final char HORIZONTAL_LINE = '║';
private static final String DOUBLE_DIVIDER = "════════════════════════════════════════════";
private static final String SINGLE_DIVIDER = "────────────────────────────────────────────";
private static final String TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
private static final String BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
private static final String MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER;
}
}
客户端使用(比较简单,无需初始化,直接使用):
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void log(View view) {
doLog();
new Thread(new Runnable() {
@Override
public void run() {
doLog();
}
}).start();
}
private void doLog() {
Logs.v("vvvvvvvvvvvvvvvvvvvvvvv");
Logs.d("dddddddddddddddddddddddddd");
Logs.i("iiiiiiiiiiiiiiiiiiiiiiiiiiiii");
Logs.w("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww");
Logs.e("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
Logs.json("{\"a\": \"Hello\", \"b\": \"World\"}");
Logs.xml("<note>\n" +
"<to>George</to>\n" +
"<from>John</from>\n" +
"<heading>Reminder</heading>\n" +
"<body>Don't forget the meeting!</body>\n" +
"</note>");
}
}
大功告成啦!
附:
大佬的源码中有3个类我没用到(即只提取了AndroidLogAdapter适配器的功能):
(作者完美使用了适配器模式+策略模式)
即另外一种情况下使用到的Log打印,因为在使用Logger打印的时候,需要在Application中进行适配器的设置:
//正常情况打印(默认选此配置)
Logger.addLogAdapter(new AndroidLogAdapter());
//保存日志到本地
Logger.addLogAdapter(new DiskLogAdapter());
从 Logger.v-> LoggerPrinter.log 之后,会根据适配器的设置选择使用AndroidLogAdapter或者DiskLogAdapter->DiskLogStrategy->CsvFormatStrategy
CsvFormatStrategy:保存日志到本地sd卡中是在根目录下一个logger文件,是.cvs文件
源码下载:
http://download.csdn.net/download/u013277740/10244736
最后啰嗦一句,重点提醒
Log打印框架都仅仅适用于调试(Debug)模式,如果要发布,千万要关掉Log打印,至于怎么关闭方法很多:Log框架自带的方法关闭、代码注释、混淆等等。。。
正式发布如果不禁掉,返回的json数据会进行格式转换,很消耗时间的,如果打印的日志在主线程,则会使APP变的卡顿
推荐阅读
-
Android 自定义 HorizontalScrollView 打造多图片OOM 的横向滑动效果(实例代码)
-
Android编程ProgressBar自定义样式之动画模式实现方法
-
Android 自定义 HorizontalScrollView 打造多图片OOM 的横向滑动效果(实例代码)
-
Android自定义View实现多片叶子旋转滑动(五)
-
Android编程ProgressBar自定义样式之动画模式实现方法
-
Android自定义View实现多片叶子旋转滑动(五)
-
Android自定义view知识-view的测量模式
-
Android自定义RadioGroupX实现多行多列布局
-
Android自定义ViewGroup多行多列效果
-
超实用的android自定义log日志输出工具类