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这儿。
相关文章推荐
- android设置全屏时,windowSoftInputMode设置无效,软键盘遮挡问题
- Android Studio修改grade版本
- Android自定义View的实现方法,带你一步步深入了解View
- Android常见的按钮监听器实现
- Android 签名机制
- android studio 常用快捷键
- 使用Android-PullToRefresh库中的PullToRefreshListView添加headerView
- Android自定义ViewGroup之子控件的自动换行和添加删除
- 利用HTML5开发Android
- 【Android】BaseAdapter示例
- 结合源码分析android的消息机制
- android调用摄像头拍照并显示
- 关于清除Android Studio中清除代理设置
- Android Intent传递数据
- 我的360
- Android获取其他进程加载的模块基地址
- 使用 Xcode 和 Android Studio 管理 iOS 和 Android 项目版本
- 细说Android事件传递机制(dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent)
- Android studio 启动的时候遇到tool.jar 没有找到的问题z
- Android ContentProvider UnderStanding