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里面有declaringClassmethodNamefileNamelineNumber这四个变量以及相应的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这儿。

2 0
原创粉丝点击