前言
本篇文章專注于 Timber 的進(jìn)階用法,包含一些稍微基礎(chǔ)的但有用的用法簡介,也包含一些自定義類實(shí)現(xiàn)不同的日志操作功能簡介。
如果還有不熟悉 Timber 的,可以先閱讀下我前面寫的一篇文章:Timber 源碼解析
-
tag用法簡介
tag() 的一般用法就是:Timber.tag("tag").d("message"),但是,前面我們分析源碼知道,Timber 中 tag 是用 ThreadLocal 存儲(chǔ)的,也就是 tag 是區(qū)分線程的,更坑爹的是,tag 的設(shè)置是一次性行為,也就是說,當(dāng)你使用 getTag() 時(shí)候,獲取成功后,這個(gè) tag 也就被刪除了。所以,當(dāng)你想實(shí)現(xiàn)一個(gè)常量 tag 時(shí),你只能按如下的方法進(jìn)行輸出:
Timber.tag("hello").d("one");
Timber.tag("hello").d("two");
Timber.tag("haha").d("three");
吐槽:其實(shí)我不是很明白為什么 Jake Wharton 要把 tag 設(shè)置成線程區(qū)分,感覺沒什么意義;而且 tag 是設(shè)置給所有樹的,那么當(dāng)我們只想在一條線程進(jìn)行 tag 設(shè)置時(shí),其他線程的下一個(gè)日志輸出也會(huì)被強(qiáng)制輸出前面設(shè)置的 tag,而這卻是我們不想要的行為,總之,我感覺 Timber 的 tag 設(shè)置是有待商榷的。當(dāng)然,我們可以在實(shí)現(xiàn)自定義Tree 時(shí),忽略父類 tag 設(shè)置行為,說到這里,我又要吐槽一下,因?yàn)?Tree.getTag() 是一個(gè)包級訪問權(quán)限,這也就是說我們沒辦法在其他包目錄對 getTag() 進(jìn)行復(fù)寫,改變它的行為 -_-,有點(diǎn)坑。
public static abstract class Tree {
final ThreadLocal<String> explicitTag = new ThreadLocal<>();
String getTag() {
String tag = explicitTag.get();
if (tag != null) {
explicitTag.remove();
}
return tag;
}
前面的路走不通,那我們只能另尋他路,辦法總比困難多嘛!通過查看源碼我們可以知道,tag() 函數(shù)設(shè)置的 tag 最終被傳遞給 Tree的純虛函數(shù) log()里面。
protected abstract void log(int priority, String tag, String message, Throwable t);
所以,我們在實(shí)現(xiàn) Tree 子類時(shí),忽略這個(gè) tag 就相當(dāng)于忽略了父類的 tag 行為,然后,實(shí)現(xiàn)我們自己想要的 tag 行為就行了。
進(jìn)階用法:1. 常量 Tag 實(shí)現(xiàn)
比如,我們想實(shí)現(xiàn)一個(gè)常量 tag 功能,使我們能只進(jìn)行一個(gè) tag 設(shè)置,就能一直使用,其不區(qū)分線程,不區(qū)分類····,使我們調(diào)用能像下面代碼所示一樣:
Timber.plant(new ConstTagTree().setTag("ConstTag"));
Timber.v("watch the tag"); //tag == "ConstTag"
Timber.v("watch the tag haha");//tag == "ConstTag"
Timber.v("watch the tag hehe");//tag == "ConstTag"
實(shí)現(xiàn)這個(gè)代碼很簡單,子類提供一個(gè) setTag(String tag) 函數(shù),復(fù)寫父類 log() 函數(shù)時(shí),使用前面 setTag(String tag) 的值就行了。
詳細(xì)代碼可以查看:ConstTagTree
ConstTagTree 實(shí)現(xiàn)了常量 tag設(shè)置,如果沒有設(shè)置 tag,默認(rèn)以調(diào)用者類名作為 tag。
使用方法如下:
Timber.plant(new ConstTagTree().setTag("ConstTag")); //set const tag
//Timber.plant(new ConstTagTree()); //const tag not set,uses class name instead.
Timber.w("Application haha");
進(jìn)階用法:2. Release版本調(diào)試:ReleaseTree
我們知道,Timber 默認(rèn)已經(jīng)為我們實(shí)現(xiàn)了一個(gè)調(diào)試版本的日志記錄類:DebugTree,但是 Release 版本的未提供。那么我們就自己實(shí)現(xiàn)一個(gè) Release版本的日志記錄類:ReleaseTree。
ReleaseTree 主要用于 Release 版本日志記錄,并且只記錄 warn,error,wtf 信息的日志。
public class ReleaseTree extends Timber.DebugTree {
@Override
protected boolean isLoggable(String tag, int priority) {
return priority == Log.WARN
|| priority == Log.ERROR
|| priority == Log.ASSERT;
}
}
好了,就是這么簡單,整個(gè) ReleaseTree 的完整代碼就是上面所示。
可以看到,ReleaseTree 是繼承了 DebugTree ,因此也就擁有了不設(shè)置 tag 就自動(dòng)使用調(diào)用者類名作為 tag 的功能,不過,Release 集成 Debug 這個(gè)繼承關(guān)系其實(shí)給人的感覺是不太友好的,這兩者沒有繼承關(guān)系這個(gè)概念(這里使用繼承僅僅是為了使用到自動(dòng)類名 tag 這個(gè)功能,與繼承沒有直接關(guān)系),但是,還是上面那個(gè)問題,由于 Timber 中 Tree.getTag() 是包級權(quán)限,子類沒辦法獲取,也就是沒辦法復(fù)寫 tag 的行為,那我們只能采用繼承的方法獲取同個(gè)包級下的 DebugTree 的 tag 功能了。
ReleaseTree 的代碼詳見:ReleaseTree
進(jìn)階用法:3. 文件日志記錄功能:FileTree
很多情況下,當(dāng)我們的app運(yùn)行在裸機(jī)上時(shí),我們沒辦法直接獲取到app運(yùn)行日志信息,那么,將app運(yùn)行日志信息保存到文件中或許就是一個(gè)良好的解決方法,通過查看文件日志,我們就可以看出app運(yùn)行中哪個(gè)方面出現(xiàn)了問題,這在項(xiàng)目中應(yīng)該是非常有用處的。
那下面我們就來實(shí)現(xiàn)一個(gè)簡單的 FileTree:FileTree
FileTree 代碼相對較多,這里就不進(jìn)行展示了,我們這里就看下使用方法:
String path = Environment.getExternalStorageDirectory().getAbsolutePath();
Timber.plant(new FileTree().name("FileTree.txt").storeAt(path));
Timber.v("log in file"); //this will be store in file
進(jìn)階用法:4. 集成其他日志框架
上面我們所介紹的都是通過自己直接或間接地繼承 Tree 來實(shí)現(xiàn)我們所需要的日志操作功能,如果你覺得自己去實(shí)現(xiàn)這些日志操作太麻煩,一件值得高興的事,由于 Timber 其實(shí)是一個(gè)封裝日志操作的一個(gè)框架,不涉及具體功能的實(shí)現(xiàn),所以,基于這樣的架構(gòu)設(shè)計(jì),我們就可以在 Github 上找到一些具體的執(zhí)行日志操作功能的項(xiàng)目,然后種植到 Timber 中就行了。
這里,我隨便找一份日志操作項(xiàng)目:Logger
很湊巧的是,該庫本身就考慮到集成到 Timber 中來,其 ReadMe.md 中就給我們直接指出了集成方法:
// Set methodOffset to 5 in order to hide internal method calls
Timber.plant(new Timber.DebugTree() {
@Override protected void log(int priority, String tag, String message, Throwable t) {
Logger.log(priority, tag, message, t);
}
});
進(jìn)階用法:5. 多 Tag 功能實(shí)現(xiàn):MultiTagTree
有些時(shí)候,我們希望日志輸出既包含類名,也包含我們自定義的其他 tag,這樣做的好處是一方面我們可以只關(guān)注部分日志(通過類名過濾),另一方面,我們也希望能查看一下完整的日志(通過自定義 tag 過濾)。
我這邊的實(shí)現(xiàn)思路是將自定義 tag 插入到 原來的 tag 前面,并以分隔符“-”進(jìn)行分割。
public class MultiTagTree extends Timber.DebugTree {
private static final String SEPARATOR = "-";
private List<String> mTags = new CopyOnWriteArrayList<>();
@Override
protected void log(int priority, String tag, String message, Throwable t) {
String newTag = produceTag(tag);
super.log(priority, newTag, message, t);
}
//override this method to produce your own multi tag
protected String produceTag(String originTag) {
StringBuilder builder = new StringBuilder();
for (int i = 0, lenth = mTags.size(); i < lenth; ++i) {
builder.append(mTags.get(i))
.append(i != lenth - 1 ? SEPARATOR :
(originTag == null ? "" : SEPARATOR + originTag));
}
return builder.toString();
}
···
詳細(xì)代碼可查看:[MultiTagTree]
···
}
Timber.plant(new MultiTagTree().addTag("Whyn111")); //then the tag is like "Whyn111-MainActivity"
這樣,我們就可以實(shí)現(xiàn)多 tag 功能。
最后,附上本章源碼:TimberExtendedDemo