編輯:關於Android編程
Logger框架是一個優雅的日志系統,通過Logger可以很漂亮的打印出Android系統日志。下面從用法開始逐層介紹。
在Gradle 依賴中添加,引入Logger庫
compile 'com.orhanobut:logger:1.15'
1 初始化,一般在Application中完成。
Logger .init(YOUR_TAG) // default PRETTYLOGGER or use just init() .methodCount(3) // default 2 .hideThreadInfo() // default shown .logLevel(LogLevel.NONE) // default LogLevel.FULL .methodOffset(2) // default 0 .logAdapter(new AndroidLogAdapter()); //default AndroidLogAdapter
2 調用Logger
Logger.d("hello"); Logger.e(exception, "message"); Logger.json(JSON_CONTENT);
Logger類圖
從Logger.init()開始
可以看到Logger.init()最終會調到
/** * It is used to change the tag * * @param tag is the given string which will be used in Logger as TAG */ public static Settings init(String tag) { printer = new LoggerPrinter(); return printer.init(tag); }
這個方法初始化Printer類,所有的配置信息通過Setting類進行維護,可以看到Setting類中有許多默認的成員變量,這些都是設置的默認值
public final class Settings { private int methodCount = 2; private boolean showThreadInfo = true; private int methodOffset = 0; private LogAdapter logAdapter; /** * Determines to how logs will be printed */ private LogLevel logLevel = LogLevel.FULL; // 省略 }
初始化完成了,下面看下如何打印出log,下面以debug模式進行輸出.
LoggerPrinter是真正的日志輸出類,下面看先實現.
@Override public void d(String message, Object... args) { log(DEBUG, null, message, args); } @Override public void d(Object object) { String message; if (object.getClass().isArray()) { // 如果輸出對象是數組的化,將數組打印 message = Arrays.deepToString((Object[]) object); } else { message = object.toString(); } log(DEBUG, null, message); }
繼續看log方法
/** * This method is synchronized in order to avoid messy of logs' order. * log方法是一個同步方法,防止多線程調用時順序混亂 */ private synchronized void log(int priority, Throwable throwable, String msg, Object... args) { if (settings.getLogLevel() == LogLevel.NONE) { return; } String tag = getTag(); // 此處獲取用戶設置的TAG String message = createMessage(msg, args); log(priority, tag, message, throwable); }
Logger將Tag設置到ThreadLocal中進行保存
這樣可以給不同的線程設置不同的Tag,下面簡單介紹下ThreadLocal,這也是本框架的關鍵點之一
/** * @return the appropriate tag based on local or global * * 這個方法的意思是,從當前線程的ThreadLocal中取得Tag,取到的話清 * 除ThreadLocal中的Tag;沒有設置線程的tag的話,使用系統或用戶設置 * 的Tag */ private String getTag() { String tag = localTag.get(); if (tag != null) { localTag.remove(); return tag; } return this.tag; }
ThreadLocal詳解
ThreadLocal 是一個線程內部的存儲類,它可以在指定線程中存儲數據,對於其他線程來說則無法獲取到數據.一般來說,當某些數據是以線程為作用域並且不同線程具有不同的數據副本的時候,就可以考慮采用ThreadLocal。
在Java的Thread類中,有一個localValues字段,是用來存儲數據的,這個也是ThreadLocal的實際存儲容器.
// Thread 類中 /** * Normal thread local values. */ ThreadLocal.Values localValues; /** * Inheritable thread local values. */ ThreadLocal.Values inheritableValues;
看先ThreadLocal中的get方法(從Thread中取出容器,從容器中取出對象)
/** * Returns the value of this variable for the current thread. If an entry * doesn't yet exist for this variable on this thread, this method will * create an entry, populating the value with the result of * {@link #initialValue()}. * * @return the current value of the variable for the calling thread. */ @SuppressWarnings("unchecked") public T get() { // Optimized for the fast path. Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values != null) { Object[] table = values.table; int index = hash & values.mask; if (this.reference == table[index]) { return (T) table[index + 1]; } } else { values = initializeValues(currentThread); } return (T) values.getAfterMiss(this); }
接著往回看Logger,實際的Log方法
@Override public synchronized void log(int priority, String tag, String message, Throwable throwable) { if (settings.getLogLevel() == LogLevel.NONE) { return; } // 拼接Log信息 if (throwable != null && message != null) { message += " : " + Helper.getStackTraceString(throwable); } if (throwable != null && message == null) { message = Helper.getStackTraceString(throwable); } if (message == null) { message = "No message/exception is set"; } int methodCount = getMethodCount(); if (Helper.isEmpty(message)) { message = "Empty/NULL log message"; } // 打印頂部 logTopBorder(priority, tag); logHeaderContent(priority, tag, methodCount); //get bytes of message with system's default charset (which is UTF-8 for Android) byte[] bytes = message.getBytes(); int length = bytes.length; if (length <= CHUNK_SIZE) { if (methodCount > 0) { logDivider(priority, tag); } logContent(priority, tag, message); logBottomBorder(priority, tag); return; } if (methodCount > 0) { logDivider(priority, tag); } for (int i = 0; i < length; i += CHUNK_SIZE) { int count = Math.min(length - i, CHUNK_SIZE); //create a new String with system's default charset (which is UTF-8 for Android) logContent(priority, tag, new String(bytes, i, count)); } // 打印底部 logBottomBorder(priority, tag); }
// 調用LogChunk進行輸出,輸出使用的Adapter private void logChunk(int logType, String tag, String chunk) { String finalTag = formatTag(tag); switch (logType) { case ERROR: settings.getLogAdapter().e(finalTag, chunk); break; case INFO: settings.getLogAdapter().i(finalTag, chunk); break; case VERBOSE: settings.getLogAdapter().v(finalTag, chunk); break; case WARN: settings.getLogAdapter().w(finalTag, chunk); break; case ASSERT: settings.getLogAdapter().wtf(finalTag, chunk); break; case DEBUG: // Fall through, log debug by default default: settings.getLogAdapter().d(finalTag, chunk); break; } }
// 底層仍然使用Android提供的Log方法進行輸出 class AndroidLogAdapter implements LogAdapter { @Override public void d(String tag, String message) { Log.d(tag, message); } @Override public void e(String tag, String message) { Log.e(tag, message); } @Override public void w(String tag, String message) { Log.w(tag, message); } @Override public void i(String tag, String message) { Log.i(tag, message); } @Override public void v(String tag, String message) { Log.v(tag, message); } @Override public void wtf(String tag, String message) { Log.wtf(tag, message); } }
Logger在Header中可以打印出Log的位置,這個是怎麼做到的呢?接著往下分析
@SuppressWarnings("StringBufferReplaceableByString") private void logHeaderContent(int logType, String tag, int methodCount) { // 獲取虛擬機棧幀,通過這個獲取調用方法的行數 StackTraceElement[] trace = Thread.currentThread().getStackTrace(); if (settings.isShowThreadInfo()) { logChunk(logType, tag, HORIZONTAL_DOUBLE_LINE + " Thread: " + Thread.currentThread().getName()); logDivider(logType, tag); } String level = ""; int stackOffset = getStackOffset(trace) + settings.getMethodOffset(); //corresponding method count with the current stack may exceeds the stack trace. Trims the count if (methodCount + stackOffset > trace.length) { methodCount = trace.length - stackOffset - 1; } for (int i = methodCount; i > 0; i--) { int stackIndex = i + stackOffset; if (stackIndex >= trace.length) { continue; } StringBuilder builder = new StringBuilder(); builder.append("║ ") .append(level) .append(getSimpleClassName(trace[stackIndex].getClassName())) .append(".") .append(trace[stackIndex].getMethodName()) .append(" ") .append(" (") .append(trace[stackIndex].getFileName()) .append(":") .append(trace[stackIndex].getLineNumber()) .append(")"); level += " "; logChunk(logType, tag, builder.toString()); } }
這裡我們直接調用的StackTraceElement的toString方法
StackTraceElement表示一個虛擬機棧幀,通過這個棧幀可以獲取如下信息
getClassName getMethodName getFileName getLineNumber看名字就知道什麼意思了,我們可以根據這些信息拼接要打印的信息。
下面簡單寫一個Demo,實現一個類似的功能.
public class MyLog { public static String TAG = "yang"; public static void d(Class clazz, String msg) { StackTraceElement[] elements = Thread.currentThread().getStackTrace(); String trace = null; int i = 0; // 和MyLog相同的下一個棧幀為MyLog調用的棧幀信息 for (i = 0; i < elements.length; i++) { if (elements[i].getClassName().equals(MyLog.class.getName())) { break; } } StackTraceElement targetElement = elements[++i]; trace = "(" + targetElement.getFileName() + ":" + targetElement.getLineNumber() + ")"; Log.d(TAG, trace); } }
最近,看了好多的APP的等待對話框,發現自己的太lower,於是就研究了一番,最後經過苦心努力,實現一個。 自定義一個LoadingIndicatorView(exte
Android 開發工具升級到22.6.2在創建工程時只要選擇的最低版本低於4.0,就會自動生成一個項目appcompat_v7,沒創建一個新的項目都會自動創建,很是煩惱
anddroid studio的內存修改昨天有位朋友問到了下面的一個問題這個判斷為android studio的分配的內存不夠用。據我的了解造成這個的原因主要有以下幾個方
今天來寫一個關於圖片請求的小例子,我們用NetworkImageView這個類來實現,這個類可以直接用在xml控件中,當作imageview,而且內部原理也是使用的Ima