android 程式設計小技巧(1)---超實用的LogUtil
LogUtil,這個單詞,相信很多做android開發的同學並不陌生。畢竟,系統的Log相對來說功能過於簡單,而且管理起來也很是麻煩,所以,一般我們都會自己封裝一個Utli用來管理Log。
那麼今天,為大家推薦一個新的LogUtli工具類,希望能喜歡。
好啦,先上效果圖片:
可以看到,控制檯輸出的Log資訊直接對應到了哪個類的哪個方法哪一行。
看到這樣的Log資訊,是不是對於我們除錯程式碼,更加的方便呢?
那麼,接下來就介紹這個類的實現吧。
1.StackTraceElement
首先,我們需要先了解一下StackTraceElement這個類,它是由java.lang提供的,主要作用是獲取方法的呼叫棧資訊。
可以通過Thread.currentThread().getStackTrace()可以獲得一個StackTraceElement的陣列,裡面包含了執行緒中執行呼叫了哪些方法。
我們可以通過程式碼來檢查一下這個陣列的一些資訊。
首先,新建一個專案,xml檔案如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft ="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="quietuncle.logutli.MainActivity">
<ScrollView
android:layout_weight="10"
android:layout_width ="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
<Button
android:onClick="sendLog"
android:id="@+id/sendLog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="傳送Log資訊" />
</LinearLayout>
然後,在activity裡執行如下方法。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView content= (TextView) findViewById(R.id.content);
String info="";
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
info+="stacktrace len :"+stacktrace.length;
for (int i = 0; i < stacktrace.length; i++) {
info+="\n\t"+"---- the " + i + " element ----";
info+="\n\t"+"toString: " + stacktrace[i].toString();
info+="\n\t"+"ClassName: " + stacktrace[i].getClassName();
info+="\n\t"+"FileName: " + stacktrace[i].getFileName();
info+="\n\t"+"LineNumber: " + stacktrace[i].getLineNumber();
info+="\n\t"+"MethodName: " + stacktrace[i].getMethodName();
}
content.setText(info);
}
進行執行,我們可以看到如下效果:
我們可以看到,陣列下標為2的物件,打印出了我們想要的資訊,
ClassName:quietuncle.logutli.MainActivity
FileName: MainActivity.java
LineNumber:16
MethodName:onCreat
那麼,這是否意味著我們可以開始構建我們的LogUtil了呢。
為了檢驗下標為2的是否是我們想要的資訊,我們新建一個測試類,然後執行該方法。
public class Test {
public static String getInfo(){
String info="";
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
info+="stacktrace len :"+stacktrace.length;
for (int i = 0; i < stacktrace.length; i++) {
info+="\n\t"+"---- the " + i + " element ----";
info+="\n\t"+"toString: " + stacktrace[i].toString();
info+="\n\t"+"ClassName: " + stacktrace[i].getClassName();
info+="\n\t"+"FileName: " + stacktrace[i].getFileName();
info+="\n\t"+"LineNumber: " + stacktrace[i].getLineNumber();
info+="\n\t"+"MethodName: " + stacktrace[i].getMethodName();
}
return getInfo();
}
}
然後在MainActivity的onCreate方法中呼叫
String info = Test.getInfo();
content.setText(info);
然後我們再檢視顯示內容,會發現如下:
下標為2的StackTraceElement的資訊變成了Test類下getInfo的資訊
而下標為3StackTraceElement的資訊變成了MainActivity下onCreat資訊。
那麼這是為什麼呢?在經過多次測試之後,作者發現,Thread.currentThread().getStackTrace()的
StackTraceElement入棧應該是由從最裡層的方法開始加入的。
而前兩行,則是固定呼叫的VM和Thread的方法。
而 getInfo在onCreat內執行,這樣就可以理解為什麼,onCreate方法由原先的下標2變成了下標3了。
大致明白了Thread.currentThread().getStackTrace()的排列方法,那麼我們就可以開始構造我們的LogUtil了。
2.構造LogUtli
首先我們先分析一下我們的log的層級:
I/QT:MainActivity.sendLog(Line:32): info
可以看出
QT:MainActivity.sendLog(Line:32) 這一部分是屬於TAG部分。
這裡我們可以拆分一下:
QT : tag的字首,自定義
MainActivity : log所在類名 對應方法 StackTraceElement.getClassName();
sendLog : 列印log的方法 對應方法: StackTraceElement.getMethodName();
line:32 : log所在行數 對應方法: StackTraceElement.getLineNumber();
這下,我們可以正式編寫我們的LogUtil了。
這裡就直接貼上程式碼了。
/**
* 作者: quietUncle on 2016/2/26
* 一個Log工具類
* 輸出格式:
* tagPrefix :className.methodName(Line:lineNumber),
* tagPrefix 為空時只輸出:className.methodName(Line:lineNumber)。
*/
public class QTLog {
public static String tagPrefix = "QT";//log字首
public static boolean debug = true;
public static void d(Object o) {
logger("d", o);
}
public static void e(Object o) {
logger("e", o);
}
public static void i(Object o) {
logger("i", o);
}
public static void w(Object o) {
logger("w", o);
}
/**
*
* @param type logger級別
* @param o logger內容
*/
private static void logger(String type, Object o) {
if (!debug) {
return;
}
String msg=o+"";
String tag = getTag(getCallerStackTraceElement());
switch (type){
case "i":
Log.i(tag,msg);
case "d":
Log.d(tag,msg);
break;
case "e":
Log.e(tag,msg);
break;
case "w":
Log.w(tag,msg);
break;
}
}
private static String getTag(StackTraceElement element) {
String tag = "%s.%s(Line:%d)"; // 佔位符
String callerClazzName = element.getClassName(); // 獲取到類名
callerClazzName = callerClazzName.substring(callerClazzName
.lastIndexOf(".") + 1);
tag = String.format(tag, callerClazzName, element.getMethodName(),
element.getLineNumber()); // 替換
tag = TextUtils.isEmpty(tagPrefix) ? tag : tagPrefix + ":"
+ tag;
return tag;
}
/**
* 獲取執行緒狀態
* @return
*/
private static StackTraceElement getCallerStackTraceElement() {
return Thread.currentThread().getStackTrace()[5];
}
}
這裡主要說一下Thread.currentThread().getStackTrace()[5]; 為什麼用5
根據第一節的推論,我們可以知道0,1是vm,和thread呼叫的方法,從2開始才是我們呼叫的方法
Thread.currentThread().getStackTrace()[5]所在的getCallerStackTraceElement方法是在logger()中呼叫的,而logger是在public static void d/i/e/w 中呼叫。
我們要獲取的是呼叫TQLog.i/w/e/d() 所在的類的資訊。
那麼,我們可以推論一下。
vm->thread->getCallerStackTraceElement->logger->i/e/w/d->目標層
0 1 2 3 4 5
所以這裡使用Thread.currentThread().getStackTrace()[5];
其他部分相對比較簡單,大家可以自行理解一下。
demo已上傳github,有意可點選下載
另:部落格會每週更新,帶來一些關於android的理解,如果喜歡可以關注一下,所有demo均會上傳到github,有意關注一下github,賞個star就更好啦~~