天天看點

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

兩個方法:

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字數限制,事實上就是超過字數限制後。採用分次列印的方法來列印,其它代碼不做分析,比較簡單。

三個方法

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就更簡單了,僅僅有一個方法(本寶寶曾經還以為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);// 調用格式化方法
    }
}      
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的基礎上對這個問題進行優化的。