1. 程式人生 > >Log日誌框架-對原生Log進行簡單封裝

Log日誌框架-對原生Log進行簡單封裝

閒來無事,對以前專案中使用的對Log的封裝使用抽取出來,寫成一個Demo供博友參考。 
Demo是以Gradle構建的專案。其中涉及到打Release包跟Debug包的時候對於日誌輸出的控制,由gradle指令碼進行控制,下文我再細說。 
先看下日誌的輸出效果: 
這裡寫圖片描述
效果比較簡潔,主要展示日誌的具體來源及日誌訊息,並提供點選快速定位程式碼的功能。 
下面直接看程式碼:

package com.csf.simplelog.utils;

import android.text.TextUtils;
import android.util.Log;

/**
 * ClassName: LogUtil
 * Description:日誌工具類
 * Created by chensf on 2016-7-27 9:53.
 */
public class LogUtil {

    private static final String TAG = LogUtil.class.getSimpleName();
    /**
     * 日誌輸出等級
     */
    private static int LOG_LEVEL = Log.VERBOSE;

    /**
     * 是否顯示日誌
     */
    private static boolean isShowLog = true;

    private static final String DOUBLE_DIVIDER = "-----------------------------------------------------------------------------------------------------------";

    public static void init(boolean isShowLog) {
        LogUtil.isShowLog = isShowLog;
    }

    public static void init(boolean isShowLog, int logLevel) {
        LogUtil.isShowLog = isShowLog;
        LogUtil.LOG_LEVEL = logLevel;
    }

    public static int v(Object msg) {
        return v(TAG, msg);
    }

    public static int v(String tag, Object msg) {
        return v(tag, msg, null);
    }

    public static int v(String tag, Object msg, Throwable tr) {
        return printLog(Log.VERBOSE, tag, msg, tr);
    }

    public static int d(Object msg) {
        return d(TAG, msg);
    }

    public static int d(String tag, Object msg) {
        return d(tag, msg, null);
    }

    public static int d(String tag, Object msg, Throwable tr) {
        return printLog(Log.DEBUG, tag, msg, tr);
    }

    public static int i(Object msg) {
        return i(TAG, msg);
    }

    public static int i(String tag, Object msg) {
        return i(tag, msg, null);
    }

    public static int i(String tag, Object msg, Throwable tr) {
        return printLog(Log.INFO, tag, msg, tr);
    }

    public static int w(Object msg) {
        return w(TAG, msg);
    }

    public static int w(String tag, Object msg) {
        return w(tag, msg, null);
    }

    public static int w(String tag, Object msg, Throwable tr) {
        return printLog(Log.WARN, tag, msg, tr);
    }

    public static int e(Object msg) {
        return e(TAG, msg);
    }

    public static int e(String tag, Object msg) {
        return e(tag, msg, null);
    }

    public static int e(String tag, Object msg, Throwable tr) {
        return printLog(Log.ERROR, tag, msg, tr);
    }

    private static int printLog(int type, String tag, Object msgObj, Throwable tr) {
        if (!isShowLog) {
            return 0;
        }
        String msg;
        StringBuilder builder = new StringBuilder(DOUBLE_DIVIDER).append('\n').append(getFunctionName())
                .append(DOUBLE_DIVIDER).append('\n').append("  ");
        if (msgObj == null) {
            msg = "";
        } else {
            msg = msgObj.toString();
        }
        if (!TextUtils.isEmpty(msg)) {
            builder.append(msg);
        }
        if (tr != null) {
            builder.append('\n').append(Log.getStackTraceString(tr));
        }
        builder.append('\n').append(DOUBLE_DIVIDER);
        switch (type) {
            case Log.VERBOSE:
                if (LOG_LEVEL <= Log.VERBOSE) {
                    return Log.v(tag, builder.toString());
                }
                break;
            case Log.DEBUG:
                if (LOG_LEVEL <= Log.DEBUG) {
                    return Log.d(tag, builder.toString());
                }
                break;
            case Log.INFO:
                if (LOG_LEVEL <= Log.INFO) {
                    return Log.i(tag, builder.toString());
                }
                break;
            case Log.WARN:
                if (LOG_LEVEL <= Log.WARN) {
                    return Log.w(tag, builder.toString());
                }
                break;
            case Log.ERROR:
                if (LOG_LEVEL <= Log.ERROR) {
                    return Log.e(tag, builder.toString());
                }
                break;
        }
        return 0;
    }

    private static String getFunctionName() {
        StackTraceElement[] elements = Thread.currentThread().getStackTrace();
        if (elements == null) {
            return "";
        }
        for (StackTraceElement ste : elements) {
            if (ste.isNativeMethod()) {
                continue;
            }
            if (ste.getClassName().equals(Thread.class.getName())) {
                continue;
            }
            if (ste.getClassName().equals(LogUtil.class.getName())) {
                continue;
            }
            return "  " + ste.getFileName().substring(0, ste.getFileName().indexOf(".")) + "." + ste.getMethodName()
                    + " (" + ste.getFileName() + ":" + ste.getLineNumber() + ")\n";
        }
        return "";


    }

}

167行的程式碼,其中提供快速定位功能的程式碼在getFunctionName這個方法中,通過迴圈獲取Stack Trace Element中的element,分析獲取到具體的呼叫類方法及對應行數。程式碼如下:

private static String getFunctionName() {
        StackTraceElement[] elements = Thread.currentThread().getStackTrace();
        if (elements == null) {
            return "";
        }
        for (StackTraceElement ste : elements) {
            if (ste.isNativeMethod()) {
                continue;
            }
            if (ste.getClassName().equals(Thread.class.getName())) {
                continue;
            }
            if (ste.getClassName().equals(LogUtil.class.getName())) {
                continue;
            }
            return "  " + ste.getFileName().substring(0, ste.getFileName().indexOf(".")) + "." + ste.getMethodName()
                    + " (" + ste.getFileName() + ":" + ste.getLineNumber() + ")\n";
        }
        return "";

下面我們將目光回到對Log的封裝類LogUtil上,LogUtil只是對原生Log類進行簡單的封裝,通過增加屬性isShowLog及LOG_LEVEL對我們的log進行控制,方便在釋出Release包的時候快速關閉日誌輸出。 
LogUtil提供兩個初始函式,

public static void init(boolean isShowLog) {
        LogUtil.isShowLog = isShowLog;
    }

    public static void init(boolean isShowLog, int logLevel) {
        LogUtil.isShowLog = isShowLog;
        LogUtil.LOG_LEVEL = logLevel;
    }

這兩個函式二選一,最好是在應用的入口進行初始化,一般我們會自定義一個Application的繼承類。在其方法onCreate中就能進行應用的初始工作。 
程式碼如下:

這裡寫圖片描述 
然後在AndroidManifest中引用這個MyApplication類

   <application
        android:name=".config.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >

在MyApplication的onCreate方法中,我們看到,我這裡通過 
BuildConfig.LOG_DEBUG這個值來控制日誌是否輸出。 
我們來看這個語句LogUtil.init(true, Log.VERBOSE);其中參一是是否開啟日誌輸出,參二顧名思義,就是日誌的級別,這裡我將級別控制設定為最低的VERBOSE,意思就是所有的日誌資訊,VERBOSE,DEBUG,INFO,WARN,ERROR這些級別的資訊都會輸出。如果傳遞引數Log.ERROR,那就只會輸出錯誤日誌,因為我們的Log.ERROR的級別比VERBOSE,DEBUG,INFO,WARN都要高。具體的大家去看到Demo的效果。 
回到上面的BuildConfig.LOG_DEBUG,機智的小夥伴會發現BuildConfig中並沒有LOG_DEBUG這個屬性,那麼我們來講下開文提到 
“涉及到打Release包跟Debug包的時候對於日誌輸出的控制,由gradle指令碼進行控制” 
通過Gradle指令碼來進行控制日誌的輸出,這樣我們就不用在打包的時候手動去修改程式碼。 
下面貼出部分Gradle指令碼:

 buildTypes {
        debug {
            //顯示日誌
            buildConfigField("boolean","LOG_DEBUG","true");
        }
        release {
            //不顯示日誌
            buildConfigField("boolean","LOG_DEBUG","false");
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在主工程的Gradle指令碼的buildTypes下,在debug跟release中增加buildConfigField(“boolean”,”LOG_DEBUG”,”true”); 
這個語句的意思是在BuildConfig中增加欄位LOG_DEBUG,debug下LOG_DEBUG的值為true,release下則為false.然後再通過該值去初始化LogUtil,達到控制日誌輸出。