Android KLog源代码分析

Android KLog源代码分析

    • Android KLog源代码分析

        • 代码结构
        • 详细分析
          • BaseLog
          • FileLog
          • JsonLog
          • XmlLog
        • 核心文件KLogjava分析
        • 遇到的问题

一直使用这个库。但没有细致研究。今天就来研究一下。该库的地址:

KLog,在这里先感谢下作者。棒棒哒!

代码结构

整个代码的结构非常easy。例如以下:

library
    klog
        BaseLog.java
        FileLog.java
        JsonLog.java
        XmlLog.java
    KLog.java
    KLogUtil.java

共六个文件:

  • BaseLog.java,支持主要的log打印
  • FileLog.java。支持以文件的形式保存log
  • JsonLog.java,支持打印json对象和json数组
  • XmlLog.java,支持打印Xml形式的log

详细分析

BaseLog

两个方法:

public class BaseLog {

    private static final int MAX_LENGTH = 4000;

    public static void printDefault(int type, String tag, String msg) {

        int index = 0;
        int length = msg.length();
        int countOfSub = length / MAX_LENGTH;

        if (countOfSub > 0) {// 超过指定长度,粉刺打印。这样就避免了系统默认的log长度限制了
            for (int i = 0; i < countOfSub; i++) {
                String sub = msg.substring(index, index + MAX_LENGTH);
                printSub(type, tag, sub);
                index += MAX_LENGTH;
            }
            printSub(type, tag, msg.substring(index, length));// 打印余数部分
        } else {
            printSub(type, tag, msg);
        }
    }

    private static void printSub(int type, String tag, String sub) {
        switch (type) {
            case KLog.V:
                Log.v(tag, sub);
                break;
            case KLog.D:
                Log.d(tag, sub);
                break;
            case KLog.I:
                Log.i(tag, sub);
                break;
            case KLog.W:
                Log.w(tag, sub);
                break;
            case KLog.E:
                Log.e(tag, sub);
                break;
            case KLog.A:
                Log.wtf(tag, sub);
                break;
        }
    }

}

这里突破了android系统的log字数限制,事实上就是超过字数限制后。採用分次打印的方法来打印,其它代码不做分析,比較简单。

FileLog

三个方法

public class FileLog {

    private static final String FILE_PREFIX = "KLog_";
    private static final String FILE_FORMAT = ".log";

    /**
     * @param tag             log tag
     * @param targetDirectory log file save dir
     * @param fileName        log file name
     * @param headString      log file 文件头
     * @param msg             log file log内容主体
     */
    public static void printFile(String tag, File targetDirectory, @Nullable String fileName, String headString, String msg) {

        fileName = (fileName == null) ? getFileName() : fileName;
        if (save(targetDirectory, fileName, msg)) {
            Log.d(tag, headString + " save log success ! location is >>>" + targetDirectory.getAbsolutePath() + "/" + fileName);
        } else {
            Log.e(tag, headString + "save log fails !");
        }
    }

    /**
     * @param dic      log file save dir
     * @param fileName og file name
     * @param msg      log file log内容主体
     * @return true if save success
     */
    private static boolean save(File dic, @NonNull String fileName, String msg) {

        File file = new File(dic, fileName);

        try {
            OutputStream outputStream = new FileOutputStream(file);
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8");
            outputStreamWriter.write(msg);
            outputStreamWriter.flush();
            outputStream.close();
            return true;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return false;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return false;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 当未设置文件名称时,随机生成一个文件名称
     *
     * @return default file name
     */
    private static String getFileName() {
        Random random = new Random();
        return FILE_PREFIX + Long.toString(System.currentTimeMillis() + random.nextInt(10000)).substring(4) + FILE_FORMAT;
    }

}
JsonLog

JsonLog就更简单了,仅仅有一个方法(本宝宝曾经还以为Json打印会非常麻烦呢)

public class JsonLog {

    public static void printJson(String tag, String msg, String headString) {

        String message;

        try {
            if (msg.startsWith("{")) {// 处理json对象
                JSONObject jsonObject = new JSONObject(msg);
                message = jsonObject.toString(KLog.JSON_INDENT);
            } else if (msg.startsWith("[")) {// 处理json数组
                JSONArray jsonArray = new JSONArray(msg);
                message = jsonArray.toString(KLog.JSON_INDENT);
            } else {
                message = msg;
            }
        } catch (JSONException e) {
            message = msg;
        }

        KLogUtil.printLine(tag, true);// 调用格式化方法
        message = headString + KLog.LINE_SEPARATOR + message;
        String[] lines = message.split(KLog.LINE_SEPARATOR);
        for (String line : lines) {
            Log.d(tag, "║ " + line);
        }
        KLogUtil.printLine(tag, false);// 调用格式化方法
    }
}
XmlLog

两个方法:

public class XmlLog {
    /**
     * 打印xml
     *
     * @param tag        log tag
     * @param xml        xml content
     * @param headString 文件头
     */
    public static void printXml(String tag, String xml, String headString) {

        if (xml != null) {
            xml = XmlLog.formatXML(xml);
            xml = headString + "\n" + xml;
        } else {
            xml = headString + KLog.NULL_TIPS;
        }

        KLogUtil.printLine(tag, true);
        String[] lines = xml.split(KLog.LINE_SEPARATOR);
        for (String line : lines) {
            if (!KLogUtil.isEmpty(line)) {
                Log.d(tag, "║ " + line);
            }
        }
        KLogUtil.printLine(tag, false);
    }

    /**
     * @param inputXML xml content
     * @return 格式化后的xml String
     */
    private static String formatXML(String inputXML) {
        try {
            Source xmlInput = new StreamSource(new StringReader(inputXML));
            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);
            return xmlOutput.getWriter().toString().replaceFirst(">", ">\n");
        } catch (Exception e) {
            e.printStackTrace();
            return inputXML;
        }
    }

}

细心的你会发现。不管是Json形式还是XML形式。调用的都是系统原生的方法来处理数据,因此又时候对熟悉熟悉java(或android)提供的方法还是非常方便的。哈哈。

上面用到的KLogUtil,例如以下:

public class KLogUtil {

    public static boolean isEmpty(String line) {
        return TextUtils.isEmpty(line) || line.equals("\n") || line.equals("\t") || TextUtils.isEmpty(line.trim());
    }

    public static void printLine(String tag, boolean isTop) {
        if (isTop) {
            Log.d(tag, "╔═══════════════════════════════════════════════════════════════════════════════════════");
        } else {
            Log.d(tag, "╚═══════════════════════════════════════════════════════════════════════════════════════");
        }
    }

}

核心文件KLog.java分析

给方法主要提供了相似于系统log方法的不同重载,比較简单,我这里要讲的是那个获取log详细有关的类名、方法名、行号等。与之相关的就是wrapperContent这种方法了。

private static String[] wrapperContent(int stackTraceIndex, String tagStr, Object... objects) {

        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();

        StackTraceElement targetElement = stackTrace[stackTraceIndex];
        String className = targetElement.getClassName();// 得到类名
        String[] classNameInfo = className.split("\\.");// 第一种形式
        if (classNameInfo.length > 0) {
            className = classNameInfo[classNameInfo.length - 1] + SUFFIX;
        }

        if (className.contains("$")) {//另外一种形式
            className = className.split("\\$")[0] + SUFFIX;
        }

        String methodName = targetElement.getMethodName();//得到方法名
        int lineNumber = targetElement.getLineNumber();//得到所在的行号

        if (lineNumber < 0) {
            lineNumber = 0;
        }

        String tag = (tagStr == null ? className : tagStr);

        if (mIsGlobalTagEmpty && TextUtils.isEmpty(tag)) {
            tag = TAG_DEFAULT;
        } else if (!mIsGlobalTagEmpty) {
            tag = mGlobalTag;
        }
        // 得到消息主体
        String msg = (objects == null) ? NULL_TIPS : getObjectsString(objects);
        String headString = "[ (" + className + ":" + lineNumber + ")#" + methodName + " ] ";

        return new String[]{tag, msg, headString};
    }

/**
     * 得到消息主体
     *
     * @param objects 消息对象数组
     * @return 消息主体字符串
     */
    private static String getObjectsString(Object... objects) {

        if (objects.length > 1) {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("\n");
            for (int i = 0; i < objects.length; i++) {
                Object object = objects[i];
                if (object == null) {
                    stringBuilder.append(PARAM).append("[").append(i).append("]").append(" = ").append(NULL).append("\n");
                } else {
                    stringBuilder.append(PARAM).append("[").append(i).append("]").append(" = ").append(object.toString()).append("\n");
                }
            }
            return stringBuilder.toString();
        } else {
            Object object = objects[0];
            return object == null ?

NULL : object.toString();
        }
    }

分析完成,是不是非常easy,假设你看了这个库的代码。你会认为我的分析都是多余的。

遇到的问题

在使用KLog打印json形式信息时。假设网络请求时异步的,会导致KLog.json打印的格式出现错乱。即一个结果还没有全然打印出来。里外一个就開始打印了,这个应该是并发导致的问题,之后我会在KLog的基础上对这个问题进行优化的。

时间: 2024-10-11 07:39:59

Android KLog源代码分析的相关文章

Android init源代码分析(1)概要分析

功能概述 init进程是Android内核启动的第一个进程,其进程号(pid)为1,是Android系统所有进程的祖先,因此它肩负着系统启动的重要责任.Android的init源代码位于system/core/init/目录下,伴随Android系统多个版本的迭代,init源代码也几经重构. 目前Android4.4源代码中,init目录编译后生成如下Android系统的三个文件,分别是 /init /sbin/ueventd-->/init /sbin/watchdogd-->/init 其

Android init源代码分析(2)init.rc解析

本文描述init.rc脚本解析以及执行过程,读完本章后,读者应能 (1) 了解init.rc解析过程 (2) 定制init.rc init.rc介绍 init.rc是一个文本文件,可认为它是Android系统启动脚本.init.rc文件中定义了环境变量配置.系统进程启动,分区挂载,属性配置等诸多内容.init.rc具有特殊的语法.init源码目录下的readme.txt中详细的描述了init启动脚本的语法规则,是试图定制init.rc的开发者的必读资料. Android启动脚本包括一组文件,包括

Android 消息处理源代码分析(2)

Android 消息处理源代码分析(1)点击打开链接 继续接着分析剩下的类文件 Looper.java public final class Looper { final MessageQueue mQueue; //消息队列 final Thread mThread; //Looper联系的线程 public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { /

Android HandlerThread 源代码分析

HandlerThread 简单介绍: 我们知道Thread线程是一次性消费品,当Thread线程运行完一个耗时的任务之后.线程就会被自己主动销毁了.假设此时我又有一 个耗时任务须要运行,我们不得不又一次创建线程去运行该耗时任务.然而.这样就存在一个性能问题:多次创建和销毁线程是非常耗 系统资源的.为了解这样的问题,我们能够自己构建一个循环线程Looper Thread.当有耗时任务投放到该循环线程中时.线程运行耗 时任务,运行完之后循环线程处于等待状态,直到下一个新的耗时任务被投放进来.这样一

[Android] Volley源代码分析(五岁以下儿童)Q \\ u0026一个

Volley源代码分析系列那里一段时间,告诉我,有许多私人留言,同时一些问题抛出.对于一些简单的问题,我们跳,这两天被连接到朋友@smali提出的问题.告诉我你不得不赞叹查看源代码时的详细程度,大家一块思考一下. Q:在写入文件头数据的时候为何不直接写入Int而是通过移位的方式来完毕? 我们来看一下相应的源代码: writeInt(os, CACHE_MAGIC); static void writeInt(OutputStream os, int n) throws IOException {

[Android]Fragment源代码分析(三) 事务

Fragment管理中,不得不谈到的就是它的事务管理,它的事务管理写的很的出彩.我们先引入一个简单经常使用的Fragment事务管理代码片段: FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction(); ft.add(R.id.fragmentContainer, fragment, "tag"); ft.addToBackStack("<span style="f

android开发源代码分析--多个activity调用多个jni库的方法

有时候,我们在开发android项目时会遇到须要调用多个native c/jni库文件,下面是本人以前实现过的方法,假设有知道其它方法,还望不吝不吝赐教. 比如,在androidproject里有两个activity,各自是activity1和activity2.(能够进入project文件夹bin/classes路径下查看有哪些).在这两个activity里都有调用jni,过程例如以下: 1.  在activity1和activity2里分别声明native c 比如:activity1.ja

Android Gallery2源代码分析

打开图库中图片为什么从模糊变清晰 1. 有一点要明白,图片要进行显示,首先要先将图片进行decode,然后才干显示 2. 图片decode须要时间,越大的图片,细节越多的图片,那么它decode时间就越长 3. 最笨的做法就是,等图片decode完了,我们再显示,在decode完之前就看到黑色的背景.但 这种做法不太友好,尤其是大的图片的时候,等待的时间就越长 为了给客户更好的用户体验,我们会先decode一张图片的thumbnail即缩略图, 当我们点击一张 图片进来之后,我们首先看到的是这个

Android SystemUI源代码分析和修改

1.在导航栏中添加音量加减button 一些Android音量调节button.或者从保护实体按键的角度考虑,就须要在导航栏的虚拟按键中加入音量加减调节按键. 效果例如以下图所看到的: 实现步骤例如以下: 1.首先在SystemUI中加入音量加减的资源文件.路径例如以下: frameworks/base/packages/SystemUI/res/ 将图片放入相应的drawable目录,包含音量+.和音量-,见上图. 2.改动导航栏的布局文件.路径: frameworks/base/packag