您的位置:首页 > 移动开发 > Android开发

Android中LogCat输出日志的自定义

2016-01-06 17:28 816 查看

Android中LogCat输出日志的自定义

没有耐心的客官可以直接看这里github,里面有源码和使用方法。

Android中LogCat输出日志的自定义
获取原理

Log工具实践
普通打印

打印json

打印对象

打印Collection和Map

总结

获取原理

一个好的log工具,必然得尽可能的打印出详细的信息,所以必须秉承着不管有的没的,一切都要为客户准备好的原则。而通常来说我们是使用
Thread.currentThread().getStackTrace()
这个方法来获取我们所需要的信息。

Thread.currentThread().getStackTrace()
是什么鬼?来让我们运行一下。

StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
for (StackTraceElement stackTraceElement:stackTraceElements){
System.out.println("stack " + stackTraceElement);
}


输出为:

I/System.out: stack dalvik.system.VMStack.getThreadStackTrace(Native Method)
I/System.out: stack java.lang.Thread.getStackTrace(Thread.java:579)
I/System.out: stack com.sky.yunlv.yunlv_android.MainActivity.onCreate(MainActivity.java:120)
I/System.out: stack android.app.Activity.performCreate(Activity.java:5133)
I/System.out: stack android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
I/System.out: stack android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2175)
I/System.out: stack android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2261)
I/System.out: stack android.app.ActivityThread.access$600(ActivityThread.java:141)
I/System.out: stack android.app.ActivityThread$H.handleMessage(ActivityThread.java:1256)
I/System.out: stack android.os.Handler.dispatchMessage(Handler.java:99)
I/System.out: stack android.os.Looper.loop(Looper.java:137)
I/System.out: stack android.app.ActivityThread.main(ActivityThread.java:5103)
I/System.out: stack java.lang.reflect.Method.invokeNative(Native Method)
I/System.out: stack java.lang.reflect.Method.invoke(Method.java:525)
I/System.out: stack com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
I/System.out: stack com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
I/System.out: stack dalvik.system.NativeStart.main(Native Method)


Thread.currentThread().getStackTrace()
可以看到我们调用这个方法可以获得
StackTraceElement
的数组,把数组打印出来就是上面的这些信息。

这时候你可能心里想的是,这又是什么鬼的什么鬼?别急,现在我们想一个问题,在一堆
StackTraceElement
数组中,我们有没有发现什么可以利用的东西呢?这时根据上图我们可以发现,这第三个
StackTraceElement
东东貌似打印出了一些不得了的东西,它追踪到了我们调用该方法的类中的具体方法里面,而里面的详细信息正是我们所需要的。哈哈哈,皇天不负有心人,打印了那么多终于找到了。

现在又有一个问题,是不是在任何情况下我们都选择第三个?抱着这个想法,我写了一个类,方法如下:

public class CommUtil {

public static StackTraceElement[] getStack(){

return Thread.currentThread().getStackTrace();
}

}


然后再调用它:

StackTraceElement[] stackTraceElements = CommUtil.get();
for (StackTraceElement stackTraceElement:stackTraceElements){
System.out.println("stack " + stackTraceElement);
}


再看看打印数据。

I/System.out: stack dalvik.system.VMStack.getThreadStackTrace(Native Method)
I/System.out: stack java.lang.Thread.getStackTrace(Thread.java:579)
I/System.out: stack com.sky.yunlv.yunlv_android.util.CommUtil.getStack(CommUtil.java:10)
I/System.out: stack com.sky.yunlv.yunlv_android.MainActivity.onCreate(MainActivity.java:121)
I/System.out: stack android.app.Activity.performCreate(Activity.java:5133)


噢买噶,显然可以发现打印的
StackTraceElement
信息又变了,看来不是一直在第三个的。靠,但是生活得继续,我们得找规律。

如果你慢慢,再慢慢的看,可以看到它把我们所调用
Thread.currentThread().getStackTrace()
所经过的类以及方法全都打印出来了,而我们实际所需的又往下被挤了一位。从这里我们可以得出,如果想要获得我们需要的那个
StackTraceElement
,那么就得找出准确的位置,确切的说要在合适的位置调用获取
StackTraceElement
的方法。

LogUtils就是直接返回的
Thread.currentThread().getStackTrace()[4]
,由此可以知道期间一定经过两次调用。

总之,简单的说就像是快递查询可以知道你的包裹经过了哪些地方,待了多久一样,
Thread.currentThread().getStackTrace()
是对你的方法进行追踪的,这样所有调用它的地方都能查询到。

现在我们再来看看当获得到了需要的
StackTraceElement
后,我们能从中获得什么信息。

/**
* A representation of a single stack frame. Arrays of {@code StackTraceElement}
* are stored in {@link Throwable} objects to represent the whole state of the
* call stack at the time a {@code Throwable} gets thrown.
*
* @see Throwable#getStackTrace()
*/
public final class StackTraceElement implements Serializable {

private static final long serialVersionUID = 6992337162326171013L;

private static final int NATIVE_LINE_NUMBER = -2;

String declaringClass;

String methodName;

String fileName;

int lineNumber;
......//省略若干行代码
/**
* Returns the fully qualified name of the class belonging to this
* {@code StackTraceElement}.
*
* @return the fully qualified type name of the class
*/
public String getClassName() {
return (declaringClass == null) ? "<unknown class>" : declaringClass;
}

/**
* Returns the name of the Java source file containing class belonging to
* this {@code StackTraceElement}.
*
* @return the name of the file, or {@code null} if this information is not
*         available.
*/
public String getFileName() {
return fileName;
}

/**
* Returns the line number in the source for the class belonging to this
* {@code StackTraceElement}.
*
* @return the line number, or a negative number if this information is not
*         available.
*/
public int getLineNumber() {
return lineNumber;
}

/**
* Returns the name of the method belonging to this {@code
* StackTraceElement}.
*
* @return the name of the method, or "<unknown method>" if this information
*         is not available.
*/
public String getMethodName() {
return (methodName == null) ? "<unknown method>" : methodName;
}
......//省略若干代码
}


由源码可以得知每个
StackTraceElement
里面有
declaringClass
methodName
fileName
lineNumber
这四个变量以及相应的get方法,我们现在输出这几个值看看。

//由于是在主函数里面获取StackTraceElement,所以只用取第二个就可以了
StackTraceElement element = Thread.currentThread().getStackTrace()[2];
System.out.println(element);
System.out.println("className: " + element.getClassName());//类名
System.out.println("filesName: " + element.getFileName());//文件名
System.out.println("methodName: " + element.getMethodName());//方法名
System.out.println("lineNumber: " + element.getLineNumber());//调用的位置


输出结果:

I/System.out: className: com.sky.yunlv.yunlv_android.MainActivity
I/System.out: filesName: MainActivity.java
I/System.out: methodName: onCreate
I/System.out: lineNumber: 127


这时再想想我们打印log需要的信息,类名,方法名,打印的位置,年薪百万,升职,人生巅峰….这正是我们需要的啊!到此我们差不多就是万事具备只欠东风了,接着只需要拿起我们得到的
StackTraceElement
,就能投身与创建自己的log工具的革命中。

Log工具实践

普通打印

众所周知,最普通的系统打印方法为
Log.d(tag,message)
,然而由于懒是程序员第一生产力,一个参数能搞定的,为啥要用两个,那么自然而然的tag就得自动生成,这时聪明如你应该发现什么了吧,那就是
StackTraceElement
正是我们生成tag的关键。

String message = "hello,world";
StackTraceElement traceElement = element;
StringBuilder sb = new StringBuilder();
String className = traceElement.getClassName();
String fileName = traceElement.getFileName();
sb.append(className.substring(className.lastIndexOf(".") + 1)).append(".")
.append(traceElement.getMethodName())
.append(" (").append(fileName).append(":")
.append(traceElement.getLineNumber())
.append(") ");
String tag = sb.toString();
Log.d(tag,message);


输出为:

01-06 02:58:31.097 1955-1955/? D/MainActivity.onCreate (MainActivity.java:20): hello,word


可以看到我们打印出来了很多干货,有类名,方法名,甚至还有调用的行数。有了这些妈妈再也不用担心我们信息不够了。

打印json

很显然,如果我们仅仅是扩展了系统Log的话,那毫无什么逼格可言,至少也得加几个神奇的功能嘛。

json是我们经常打交道的,但每次查看服务器端返回的json数据时,一大坨,还得复制到专门的解析工具中去解析,好麻烦啊。这时聪明的你是不是可以想办法把返回的json数据按格式打印出来呢。

为了打印出来的东西美观漂亮,我们还得思考思考格式,毕竟长得漂亮看得舒心嘛,这里参考了logger

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_DOUBLE_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;


具体代码如下:

//打印json
private void printJson(StackTraceElement element,String json){

if (!KyLog.configAllowLog){
return;
}

String[] values = generateValues(element);
String tag = values[0];
String fileName = values[1];

if (TextUtils.isEmpty(json)){
Log.d(tag,"JSON{json is null}");
return;
}
try {
if (json.startsWith("{")){
JSONObject jsonObject = new JSONObject(json);
json = jsonObject.toString(4);
}else if (json.startsWith("[")){
JSONArray array = new JSONArray(json);
json = array.toString(4);
}
String[] lines = json.split(LINE_SEPARATOR);
StringBuilder stringBuilder = new StringBuilder();
for (String line: lines){
stringBuilder.append("║ ").append(line).append(LINE_SEPARATOR);
}
Log.d(fileName,TOP_BORDER);
Log.d(fileName,HORIZONTAL_DOUBLE_LINE + " " + tag);
Log.d(fileName,MIDDLE_BORDER);
Log.d(fileName,stringBuilder.toString());
Log.d(fileName,BOTTOM_BORDER);
}catch (JSONException e){
Log.e(tag,e.getMessage() );
}

}
//获取具体信息的封装
private String[] generateValues(StackTraceElement element){
String[] values = new String[2];

StackTraceElement traceElement = element;
StringBuilder sb = new StringBuilder();
String className = traceElement.getClassName();
String fileName = traceElement.getFileName();
sb.append(className.substring(className.lastIndexOf(".") + 1)).append(".")
.append(traceElement.getMethodName())
.append(" (").append(fileName).append(":")
.append(traceElement.getLineNumber())
.append(") ");
String tag = sb.toString();

values[0] = tag;
values[1] = fileName;
return values;
}


效果如下:



其实原理很简单,就把json获取到,按照我们自己想要的格式输出。其中json按标准格式打印需要用到的方法是
json = jsonObject.toString(4)
,按层次换行,并且间隔距离为4。

打印对象

既然json打印得了,对象也能打印这样才有意思嘛。在打印对象上LogUtils

的实现方法很好。其原理就是通过反射将类的属性以及值反射出来。

/**
* 将对象转化为String
*
* @param object
* @return
*/
public static <T> String objectToString(T object) {
if (object == null) {
return "Object{object is null}";
}
if (object.toString().startsWith(object.getClass().getName() + "@")) {
StringBuilder builder = new StringBuilder(object.getClass().getSimpleName() + " { ");
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
boolean flag = false;
for (String type : types) {
if (field.getType().getName().equalsIgnoreCase(type)) {
flag = true;
Object value = null;
try {
value = field.get(object);
} catch (IllegalAccessException e) {
value = e;
}finally {
builder.append(String.format("%s=%s, ", field.getName(),
value == null ? "null" : value.toString()));
break;
}
}
}
if(!flag){
builder.append(String.format("%s=%s, ", field.getName(), "Object"));
}
}
return builder.replace(builder.length() - 2, builder.length() - 1, " }").toString();
} else {
return object.toString();
}
}
// 基本数据类型
private final static String[] types = {"int", "java.lang.String", "boolean", "char",
"float", "double", "long", "short", "byte"};


接下来就是制作log的样式

String message = SystemUtil.objectToString(object);
Log.d(fileName,TOP_BORDER);
Log.d(fileName,HORIZONTAL_DOUBLE_LINE + " " + tag);
Log.d(fileName,MIDDLE_BORDER);
Log.d(fileName, HORIZONTAL_DOUBLE_LINE + " " + message);
Log.d(fileName,BOTTOM_BORDER);


效果图如下:



打印Collection和Map

LogUtils做的很不错,能打印很多东西,就是log样式稍微有点不好看,为了让它像logger那样,我决定改造改造。

if (object instanceof Collection){

Collection collection = (Collection) object;
String msg = " %s size = %d [\n";
msg = String.format(msg,simpleName,collection.size());
if (!collection.isEmpty()) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(TOP_BORDER).append(LINE_SEPARATOR)
.append(HORIZONTAL_DOUBLE_LINE).append(" ").append(tag).append(LINE_SEPARATOR)
.append(MIDDLE_BORDER).append(LINE_SEPARATOR)
.append(HORIZONTAL_DOUBLE_LINE).append(msg);
Iterator<Object> iterator = collection.iterator();
int index = 0;
while (iterator.hasNext()){
String itemString = HORIZONTAL_DOUBLE_LINE + " [%d]:%s%s";
Object item = iterator.next();
stringBuilder.append(String.format(itemString,index,
SystemUtil.objectToString(item),index++ < collection.size()-1?",\n":"\n"));
}
stringBuilder.append(HORIZONTAL_DOUBLE_LINE + " ]\n").append(BOTTOM_BORDER);
Log.d(fileName,stringBuilder.toString());
}else {
printLog(element,ERROR,msg + " and is empty ]");
}

}else if (object instanceof Map){

Map<Object,Object> map = (Map<Object, Object>) object;
Set<Object> keys = map.keySet();
if (keys.size() > 0) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(TOP_BORDER).append(LINE_SEPARATOR)
.append(HORIZONTAL_DOUBLE_LINE).append(" ").append(tag).append(LINE_SEPARATOR)
.append(MIDDLE_BORDER).append(LINE_SEPARATOR)
.append(HORIZONTAL_DOUBLE_LINE).append(" ").append(simpleName).append(" {\n");

for (Object key : keys){
stringBuilder.append(HORIZONTAL_DOUBLE_LINE).append(" ")
.append(String.format("[%s -> %s]\n",SystemUtil.objectToString(key),SystemUtil.objectToString(map.get(key))));
}
stringBuilder.append(HORIZONTAL_DOUBLE_LINE).append(" ").append("}\n")
.append(BOTTOM_BORDER);
Log.d(fileName,stringBuilder.toString());
}else {
printLog(element,ERROR,simpleName + " is Empty");
}

}


效果图为下:





总结

其实Log的制作是很简单的,我们只需要获得
StackTraceElement
里面的信息,更多的是考虑你自己想要的格式,只要发挥自己的想象力,就能做出各种漂亮的Log输出,另外本文的源码在Kylog这儿。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: