Android開發Log最佳實踐-一個簡單、漂亮、功能強大的Android日誌程式:logger
Log最佳實踐
概要:使用更好的log來除錯應用。
本文會不定期更新,推薦watch下專案。如果喜歡請star,如果覺得有紕漏請提交issue,如果你有更好的點子可以提交pull request。本文的示例程式碼主要是基於logger、LogUtils和timber進行編寫的,如果想了解更多請檢視他們的詳細解釋。我很推薦大家多多進行對比,選擇適合你自己的庫來使用。
本文固定連線:https://github.com/tianzhijiexian/Android-Best-Practices
本文推薦的庫:https://github.com/tianzhijiexian/logger
一、需求背景
Android中log是這麼寫的:
Log.d(TAG, "This is a debug log");
我覺得不爽,而且tag連空校驗都沒做!
二、需求
- 我才不要每次打log都去想tag叫什麼名字呢
- 我希望可以自動把當前類名作為預設的tag
- 如果我真的要寫tag了,你就必須顯示我定義的tag
- 我希望我寫的模板程式碼越少越好,一個
logd
就能列印一切 - 我要我的log變的好看,直觀,就是美
- 我打出的log後面要根上這個log的檔案源頭的連線,我可以直接點選跳轉到log的位置
- log中還能提示我當前的執行緒名,方便我除錯
- 我還要列印出list,map,json,pojo這樣的物件
- release包中不能洩漏我高傲的log
- 只要我想讓我的log顯示,release版本也阻擋不了我
- log資訊過長後應該要自動換行,我不允許我的log列印不全
- 在release版本中殘留的log程式碼應該對效率無影響
注意:我希望只要寫真正有意義的內容!
回看這些需求,不合理麼?很合理,我們的宗旨就是讓無意義的重複程式碼去死,如果死不掉就交給機器來做。我們應該做那些真正需要我們做的事情,而不是像一個沒思想的猿猴一般整天寫模板式程式碼。這才是程式設計師思維,而不是程式猿思維!
三、實現
無論一個第三方庫有多好,我還是推薦不要直接使用它,因為你很有可能會去替換這個第三方庫,而且還可能會有各種意想不到的需求。對於網路請求、圖片請求和log,是應該事先考慮到後續的擴充套件和替換的。
建立包裝類
這個包裝類用來包裹logger(logger是本文介紹的一個log庫),下面是一個程式碼片段:
public static void d(@Nullable String info, Object... args) {
if (!mIsOpen) { // 如果把開關關閉了,那麼就不進行列印
return;
}
Logger.d(info, args);
}
對於包裝類的起名最好不要和“Log”這個名字類似,能有明顯的區別最好,一是防止自己手抖寫錯了,二是方便review的時候方便自己檢查有沒有誤用原始的Log。
自動打tag
現在索性把當前類名作為這樣一個TAG的標識。我們可以通過下面程式碼來設定tag:
private static String getClassName() {
String result;
// 這裡的陣列的index2是根據你工具類的層級做不同的定義,這裡僅僅是關鍵程式碼
StackTraceElement thisMethodStack = (new Exception()).getStackTrace()[2];
result = thisMethodStack.getClassName();
int lastIndex = result.lastIndexOf(".");
result = result.substring(lastIndex + 1, result.length());
return result;
}
這樣我們就輕易的擺脫了tag的糾纏。
這個方法來自於豪哥的建議,這裡感謝豪哥的意見。
自定義tag
我們想要偶爾打打log的tag方便做其他的處理:
public static void d(@NonNull String tag, String info, Object... args) {
Logger.t(tag).d(info, args);
}
我上面的做法是把tag用getSimpleName的方式來得到,但會因為混淆的問題在混淆的包裡出現a.b.c這樣的類名。如果你的log是要出現在混淆的包裡的,強烈建議去手動設定tag值,否則你完全就沒辦法過濾了。至於如何手動設定tag的值,下面會講到logt
這個快捷命令。
將Log程式碼快捷模板
有人說我們IDE不都有程式碼提示了麼,你還想怎麼簡化log的輸入呢?這裡可以利用as的一個模板提示的功能:
我們可以模仿這裡原有的模板來做自己的程式碼模板,簡化模板式程式碼的輸入。至於具體模仿的方式我就不手把手教了,簡單到爆。下面僅展示下自帶的log模板的使用方式:
寫tag:
自動填寫引數和方法名:
讓log更加美觀
我要美,要直觀,要夠酷!做到這點也簡單,就是在輸出前做點字串拼接的工作,比如加上下面這行橫線。
private static final String BOTTOM_BORDER = "╚═══════════════════════════";
因為做了很多拼接的工作,所以好看的log也是消耗效能的。我的習慣是除錯完畢後立刻刪除無用的log,這樣既能減少效能影響,也減少同事的閱讀程式碼的負擔,效果如下:
顯示當前方法名和所在類並加超鏈
這個功能其實ide是原生支援的,不相信的話你隨便用原生的log列印出onCreate: (MainActivity.java:39)
試試。只不過我們可以通過一些神奇的方法來做到更好的效果:
private static String callMethodAndLine() {
String result = "at ";
StackTraceElement thisMethodStack = (new Exception()).getStackTrace()[1];
result += thisMethodStack.getClassName()+ "."; // 當前的類名(全名)
result += thisMethodStack.getMethodName();
result += "(" + thisMethodStack.getFileName();
result += ":" + thisMethodStack.getLineNumber() + ") ";
return result;
}
這裡同樣需要注意的是在混淆後是得不到正確的類名的,所以可以酌情讓activity、fragment、view不被混淆,具體方案還是看自己的取捨。
支援POJO、Map、Collection、jsonStr、Array
這個需求實現起來也比較容易,如果是簡單的POJO的物件,我們可用反射得到物件的類變數,通過字串拼接的方式最終輸出值。如果是map等陣列結構,那麼就用其內部的遍歷依次輸出值和內容。如果是json的字串,就需要判斷json的{}
,[]
這樣的特殊字元進行換行處理。至於具體的程式碼是怎樣了,大家移步去看原始碼就好,這個不是重點。重點是結果:
增加自動化或強制開關
區分release和debug版本有系統自帶的BuildConfig.DEBUG變數,用這個就可以控制是否顯示log了。強制開關也很簡單,在log初始化的最後判斷強制開關是否開啟,如果開啟那麼就覆蓋之前的顯示設定,直接顯示log。轉為程式碼就是這樣:
public class BaseApplication extends Application {
// 定義是否是強制顯示log的模式
protected static final boolean LOG = false;
@Override
public void onCreate() {
Logger.initialize(
Settings.getInstance()
.setLogPriority(BuildConfig.DEBUG ? Log.VERBOSE : Log.ASSERT)
);
// 如果是強制顯示log,那麼無論在什麼模式下都顯示log
if (LOG) {
Logger.setLogPriority(Log.VERBOSE)
}
}
}
支援超長的log資訊
有時候網路的返回值是很長的,android.util.Log
類是有最大長度限制的,為了解決這個問題。我們只需要判斷這個字串的長度,然後手動讓其換行即可。
private static final int CHUNK_SIZE = 4000;
if (length <= CHUNK_SIZE) {
logContent(logType, tag, msg);
} else {
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(logType, tag, new String(bytes, i, count));
}
}
解決log字元拼接的效率影響
多引數log資訊應該這樣列印,避免拼接好後再列印。這樣在關閉log後就不會進行字串的拼接工作了,減少log語句在release版本中的效能影響。
Logger.d("test %s%s", "v", 5); // test v5
這條來自朋友helder的建議,感謝!
各種需求和應對方案
雖然提出了上面的思路和方案,但我並不能確保可以滿足所有的需求,我給出下面的思維流程,方便大家隨機應變:
說明:
1. 儘量用as的debug模式下的log系統,無入侵。不用寫程式碼就能打log,十分方便。(下文會介紹)
2. 如果真的要打log做除錯,先放在debug和error級別,提交程式碼時務必記得清除。
3. 如果提交的程式碼中需要在某個關鍵點打log,或者要給同事看這些log,可以放在在info級別以上。
4. 在realse中推薦用自己的log包裝類的開關做處理,這樣方便在公司內部測試時可以檢視到log。
5. 如果一些資訊需要在發出去的使用者版本中出現,優先考慮資料統計的方式進行關鍵點的資料打點。
6. 如果真的要在正式釋出的apk中還帶著log,只保留info級別以上的,不把info級別之下的資訊漏出去。
四、IDEA的超強debug技巧
上文中我就提到了可以利用as的除錯模式來加速debug,下面分享下兩個和log有關的經驗。
測試程式碼:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private int index = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(v-> {
index = 123;
Log.d(TAG, "onClick: index = " + index);
index++;
}
);
}
}
-
通過console熱部署列印log資訊
我通過debug工具,可以在任意位置列印出任意物件的值,通過這種方式就可以精準除錯一些資訊了。這回我是讓其在不中斷執行的情況下列印index的值。 -
動態設定值
有時候某種分支需要在某個情況下才能走到,我可以利用debug的setValue(F12)方法動態設定值,比如我把下面的123改成了520,最終在終端列印出的資訊也會變成520。整個過程對原本程式碼完全遮蔽,無入侵。
五、最終成果
依賴庫:
https://github.com/tianzhijiexian/logger
如果你覺得這個庫不好,請提交issue,萬不可冷嘲熱諷。要知道精品永遠是個位數,而中庸的東西永遠是層出不窮的。我希望大家多提意見齊心協力優化出一個精品,而不是花時間去在平庸的選項中做著選擇難題。
六、後記
我們可以看到即使一行程式碼的log都有很多點是可優化的,還明白了我們之前一直寫的模板式程式碼是多麼的枯燥乏味。
通過這篇文章,希望大家可以看到一個優化編碼的思維過程,也希望大家去嘗試下logger這個庫。當然,我知道還是有很多人不喜歡,那麼不妨提出更好的解決方案來一起討論,不滿意可以提嘛。
寧信書則不如無書,具體如何使用還得看自己的需求,歡迎通過郵件或者是gitter的方式進行交流。
在文章後面我也給出了通過idea的debug模式下列印log的方法,意思是即使你有了這個log庫,但是我仍舊希望你可以能找到更好的方法來達到目的,擁有技巧,使用技巧,最終化為無形才是最高境界。相信我們的最終目的是一致的,那就是讓開發越來越簡便,越來越優雅~
最後說下我沒直接用文章開頭那幾個庫的原因,logger的庫很漂亮,但是冗餘行數過多,除錯多行的資料就會受到資訊干擾,timber的本身設計就是一個log的框架,列印是交給開發者自定義的。所以我將timber的框架和logger的美觀實現進行了結合。這當然還要感謝logUtils的作者,讓log支援了object型別。
參考文章
http://ihongqiqu.com/blog/2014/10/16/android-log/
https://github.com/pengwei1024/LogUtils
https://github.com/orhanobut/logger
http://droidyue.com/blog/2015/11/01/thinking-about-android-log/
https://github.com/JakeWharton/timber
作者
developer_kale@foxmail.com
@天之界線2010
相關文章
- Android開發:日誌功能備忘Android
- Qt 實現 Logger 日誌的顯示QT
- Android開發中API層的最佳實踐AndroidAPI
- Android開發:Translucent System Bar 的最佳實踐Android
- 日誌最佳實踐
- Android日誌Log使用Android
- Logminer簡單分析日誌的實驗
- 《Android和PHP開發最佳實踐》一3.7 小結AndroidPHP
- 《Android和PHP開發最佳實踐》一2.6 Android資料儲存AndroidPHP
- Kubernetes日誌的6個最佳實踐
- Android 快速開發系列 ORMLite 框架最佳實踐AndroidORM框架
- 簡單實用的js除錯logger元件JS除錯元件
- 手寫一個Parser - 程式碼簡單而功能強大的Pratt Parsing
- Java 日誌管理最佳實踐Java
- Android 開發簡單記事本程式Android
- 十四個功能強大的 Android 引導檢視Android
- Android的log日誌知識點剖析Android
- 一個不需要Log4Net的寫日誌的簡單方法
- Android Emoji 最佳實踐Android
- Android MVP 最佳實踐AndroidMVP
- 【實踐思考】自己開發一個掘金黑名單功能外掛
- 直播平臺軟體開發,一個簡單的Android登入實現demoAndroid
- Docker容器日誌管理最佳實踐Docker
- 基於元件化開發,一個簡單的Android專案框架元件化Android框架
- 最佳實踐之 Android程式碼規範Android
- LogFX:JavaFX編寫一個漂亮、輕量級的日誌檢視器
- Android開發簡單教程.docAndroid
- django開發-log日誌的配置Django
- Android 最簡單的自定義Dialog之一Android
- Android SharedPreferences最佳實踐Android
- Android 元件化最佳實踐Android元件化
- 使用Android API最佳實踐AndroidAPI
- Android SharedPreference最佳實踐Android
- Python 日誌列印之自定義logger handlerPython
- 深入理解Logger日誌——框架繫結原理框架
- python logger 列印日誌錯誤行數Python
- 【Rust】使用日誌記錄利器flexi_loggerRustFlex
- Python強大的日誌模組loggingPython