Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Logger 日志框架源碼分析

Android Logger 日志框架源碼分析

編輯:關於Android編程

Logger框架使用

Logger框架是一個優雅的日志系統,通過Logger可以很漂亮的打印出Android系統日志。下面從用法開始逐層介紹。

在Gradle 依賴中添加,引入Logger庫

compile 'com.orhanobut:logger:1.15'

這裡寫圖片描述

 使用方法

初始化,一般在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);

    }
}

這裡寫圖片描述

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved