Android中LogCat输出日志的自定义
来源:互联网 发布:淘宝网页制作模板 编辑:程序博客网 时间:2024/05/14 10:29
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.MainActivityI/System.out: filesName: MainActivity.javaI/System.out: methodName: onCreateI/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这儿。
- Android中LogCat输出日志的自定义
- Android中如何控制LogCat的自定义输出
- 关于android的日志输出&LogCat
- 关于android的日志输出&LogCat
- 关于android的日志输出&LogCat
- Android学习第一课:自定义日志LogCat输出
- LogCat中不输出日志
- android中怎样让手机测试的日志在logCat输出?
- Android logcat调试工具的重定位输出日志
- android jni c++怎么输出logcat日志
- eclipse中显示日志输出栏logcat
- logcat不输出日志的解决办法
- Logcat只输出Error级别的日志
- Android中常用单位和日志(LogCat)的使用
- 第四学:logcat---android中LOG日志的读取过程
- Android中LogCat窗口没有输出的处理方法
- [Android 调试] LogCat中不输出任何的信息
- Android本地程序的printf输出到logcat中
- 百度云推送集成之Maven方式
- CentOS 修改mysql的密码
- Linux多进程编程学习(Part 1)
- iOS中const与static区别和联系
- C++学习知识点
- Android中LogCat输出日志的自定义
- 链表,队列,堆栈的区别
- Android常见的按钮监听器实现
- leetcode之Linked List Cycle
- 【bzoj2425】【HAOI2010】【计数】【组合数学】
- ubuntu 安装jre错误
- Spring mvc的整体流程(二)
- Linux下shell的学习--之杂七杂八
- CAST,CONVERT,