abstract:LOG 是任何一種編程語(yǔ)言的第一個(gè)API,通常被初學(xué)者用來(lái)打印 Hello, World!。 有研究顯示,不使用 LOG 或者使用姿勢(shì)錯(cuò)誤的人,感情路都走得很辛苦,有七成的比例會(huì)在 34 歲的時(shí)候跟自己不愛的人結(jié)婚,而其余三成的人最后只能把遺產(chǎn)留給自己的貓。畢竟愛情需要書寫,不能是一整張白紙。LogCat是Android開發(fā)者們最熟悉不過(guò)的日志打印工具,幾乎每一個(gè)Android項(xiàng)目里面都包含著大量
LOG 是任何一種編程語(yǔ)言的第一個(gè)API,通常被初學(xué)者用來(lái)打印 Hello, World!。 有研究顯示,
不使用 LOG 或者使用姿勢(shì)錯(cuò)誤的人,感情路都走得很辛苦,有七成的比例會(huì)在 34 歲的時(shí)候跟自己不愛的人結(jié)婚,而其余三成的人最后只能把遺產(chǎn)留給自己的貓。畢竟愛情需要書寫,不能是一整張白紙。
LogCat是Android開發(fā)者們最熟悉不過(guò)的日志打印工具,幾乎每一個(gè)Android項(xiàng)目里面都包含著大量的Log相關(guān)代碼。不過(guò),或許是因?yàn)長(zhǎng)og實(shí)在是太過(guò)于普通,所以許多人在使用它的時(shí)候就顯得非常隨意,這些錯(cuò)誤的使用姿勢(shì)卻會(huì)在不經(jīng)意間給我們帶來(lái)不少的大坑。
Log相關(guān)的一些問(wèn)題
沒(méi)有關(guān)閉調(diào)試用的LOG
許多同學(xué)喜歡在開發(fā)階段用Log輸出當(dāng)前的一些環(huán)境數(shù)據(jù),用于調(diào)試代碼,但是在調(diào)試完成后卻忘了關(guān)閉這些Log,導(dǎo)致發(fā)版出去的應(yīng)用里面還會(huì)繼續(xù)輸出這些LOG,這樣不僅會(huì)造成不必要的性能丟失,也會(huì)暴露一些敏感的數(shù)據(jù),這些都是我們不愿看到的。
首先,我們要給Log進(jìn)行分級(jí),規(guī)定“DEBUG版本輸出哪一些級(jí)別的LOG并屏蔽哪一些級(jí)別的LOG,而RELEASE版本又輸出另一些級(jí)別的LOG并屏蔽另一些級(jí)別的LOG”,這樣在開發(fā)階段能夠輸出我們調(diào)試需要的LOG,而同時(shí)又能保證放送的版本能夠屏蔽這些敏感的LOG。但是在開發(fā)階段我們不應(yīng)該特意去注意這些細(xì)節(jié),所以必須開發(fā)一個(gè)Log工具庫(kù),在框架層級(jí)解決這個(gè)需求。
同時(shí),需要注意的是,用于作為“開啟/關(guān)閉Log”的開關(guān)必須是一個(gè)常量,而不能是一個(gè)變量(使用常量的話,在編譯代碼的時(shí)候,如果常量為false),編譯器會(huì)直接把調(diào)試部分的Log代碼直接去掉,而使用變量作為開關(guān)的話,這個(gè)判斷邏輯會(huì)繼續(xù)保留,一方面會(huì)造成性能丟失,另一方面在運(yùn)行時(shí)也可以通過(guò)Hack手段強(qiáng)行開啟這部分Log代碼。
另外,“開啟/關(guān)閉Log”的開關(guān)必須寫在Log方法外部,也就是說(shuō)必須先判斷“開啟/關(guān)閉Log”條件,再調(diào)用Log方法,因?yàn)樵谡{(diào)用Log方法的時(shí)候已經(jīng)造成了性能丟失,而且調(diào)用方法的時(shí)候,會(huì)先構(gòu)造好改方法需要的參數(shù)(按照參數(shù)順序從右往左),再調(diào)用方法,而許多人喜歡在調(diào)用Log方法的時(shí)候計(jì)算需要打印出來(lái)的內(nèi)容,這里是最容易造成性能丟失的地方。因此,如果為了圖方便,寫一個(gè)Log工具類,在工具類內(nèi)部去判斷是否應(yīng)該開啟或關(guān)閉Log,事實(shí)上已經(jīng)造成了不少的性能丟失。正確的使用姿勢(shì)應(yīng)該是:
public static final boolean DEBUG = true; if (DEBUG) { Log.v(TAG, "log something"); }
在循環(huán)體內(nèi)部打印LOG
盡管Log造成的性能損失很小,但是如果在循環(huán)體內(nèi)部循環(huán)調(diào)用Log方法的話,那總體的丟失的非常可觀了,所以不應(yīng)該在循環(huán)體內(nèi)部使用Log,正確的做法是在循環(huán)體內(nèi)部拼接需要打印的內(nèi)容,等跳出循環(huán)體再一次打印出來(lái)。
除了常見的循環(huán)體外,還要一個(gè)需要注意的場(chǎng)景就是Adapter。ListView/RecyclerView是Android開發(fā)中最常用的控件,因此Adapter使用的情景也很多。滾動(dòng)屏幕的時(shí)候,ListView/RecyclerView會(huì)在通過(guò)Adapter頻繁地綁定ItemView和數(shù)據(jù),而且這些都是在UI線程里進(jìn)行的,所以如果在綁定的過(guò)程中調(diào)用Log,可能會(huì)造成明顯的卡頓。
至于Log到底會(huì)丟失多少性能,一般情況下,Log的性能丟失很小,畢竟是這么常見的系統(tǒng)Api,肯定是身經(jīng)百戰(zhàn),早就是“best performance”了。不過(guò)我曾經(jīng)有個(gè)RecyclerView在MIUI上非常卡,一開始我是RecyclerView布局沒(méi)優(yōu)化好,最終定位到Adapter內(nèi)部的一處Log上,卡頓的地方出現(xiàn)在Log的Native實(shí)現(xiàn)。MIUI到底對(duì)用戶輸出的日志做了什么處理呢?非常神奇。
無(wú)法獲取重要LOG內(nèi)容
在調(diào)試代碼的時(shí)候,我們經(jīng)常通過(guò)LOG來(lái)定位Bug。同理,當(dāng)線上的版本出現(xiàn)問(wèn)題的時(shí)候,我們也希望能通過(guò)LOG來(lái)定位問(wèn)題所在。但是問(wèn)題是用戶的設(shè)備上的打印出來(lái)的LOG我們根本沒(méi)有方法獲取,唯一的手段就是當(dāng)用戶設(shè)備出現(xiàn)問(wèn)題的時(shí)候,把設(shè)備借過(guò)來(lái)連上IDE用LogCat查看輸出的LOG……顯然這是不可行的。
這種時(shí)候,我們可以在打印重要LOG(比如重要路徑的觸發(fā)點(diǎn)、或者一些異常類的信息)的時(shí)候,一并把這些信息記錄到文件里。在用戶反饋系統(tǒng)里面,一并將這些文件上傳到我們的用戶反饋服務(wù)器,這樣在處理反饋問(wèn)題的時(shí)候,就能拿到重要的參考日志了。
BLog
BLog 是 Android SDK 的 LOG 工具 {@Link android.util.Log} 的加強(qiáng)版,以方便在開發(fā)時(shí)用來(lái)
操作調(diào)試日志。
特點(diǎn)
簡(jiǎn)單易用的API;
支持輸出線程信息;
支持設(shè)置LogLevel,方便在生產(chǎn)環(huán)境關(guān)閉調(diào)試用的LOG;
支持將LOG內(nèi)容寫入文件,以便通過(guò)文件LOG定位用戶反饋的問(wèn)題;
注意,盡管BLog支持關(guān)閉Log的輸出,但是在你調(diào)用 BLog.v(String) 的時(shí)候,其實(shí)已經(jīng)造成了性能
丟失,所以請(qǐng)盡量使用正確的姿勢(shì)來(lái)使用BLog,比如
if (BuildConfig.DEBUG) { BLog.v(TAG, "log verbose"); }