安卓to鴻蒙系列:Timber
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
目錄
- Guide
- 原理
- 知識(shí)點(diǎn)
- 移植到鴻蒙
Guide
本文基于https://gitee.com/andych008/timber_ohos 分析Timber的源碼,及移植到鴻蒙需要做的工作。
大神JakeWharton的Timber是我寫日志的最愛,幾乎在所有的項(xiàng)目中都用。當(dāng)然一般我會(huì)通過Timber使用Logger,原因很簡(jiǎn)單,因?yàn)門imber接口簡(jiǎn)潔,Logger的輸出樣式好看。常規(guī)套路:
- FormatStrategy formatStrategy = PrettyFormatStrategy.newBuilder()
- .tag("DwGG") // (Optional) Global tag for every log. Default PRETTY_LOGGER
- .build();
- Logger.addLogAdapter(new AndroidLogAdapter(formatStrategy));
- Timber.plant(new Timber.DebugTree() {
- @Override
- protected void log(int priority, String tag, String message, Throwable t) {
- Logger.log(priority, tag, message, t);
- }
- });
當(dāng)然它的內(nèi)部實(shí)現(xiàn)也一樣完美。咱們往下看。
原理
Timber英文翻譯為**“木材”**。靜態(tài)方法Timber.plant(Tree tree)即種樹。每種一棵樹,就擁有一種日志能力。
比如樹A表示輸出日志到控制臺(tái),樹B表示輸出日志到文件,樹C輸出到網(wǎng)絡(luò)。
代碼實(shí)現(xiàn)上,Timber使用了外觀(facade)模式。
- Tree類是外觀類,通過plant方法Timber持有Tree類的實(shí)例,Timber中的asTree、tag方法將它暴露出去,而對(duì)于調(diào)用者來說依賴的是抽象類Tree,而不是具體的Tree的實(shí)現(xiàn),如果要更換或者添加Tree類實(shí)例,只需要調(diào)用plant等相關(guān)方法即可,所有調(diào)用者使用Tree對(duì)象的地方不需要做任何修改,這是符合面向?qū)ο笠蕾嚨怪迷瓌t的一個(gè)很好的體現(xiàn)。
另外也使用了委托(delegate)模式。Tree TREE_OF_SOULS把所有的操作都委托給forestAsArray。
更詳細(xì)的分析請(qǐng)移步
知識(shí)點(diǎn)
1.臨時(shí)tag的實(shí)現(xiàn)方法
- 很簡(jiǎn)單,Timber.tag("臨時(shí)tag").d(xxx);設(shè)置臨時(shí)tag。使用一次就刪除。
- 為了性能,使用ThreadLocal 以空間換時(shí)間。
- public static abstract class Tree {
- final ThreadLocal<String> explicitTag = new ThreadLocal<>();
- String getTag() {
- String tag = explicitTag.get();
- if (tag != null) {
- explicitTag.remove();
- }
- return tag;
- }
- }
- public static class DebugTree extends Tree {
- @Override final String getTag() {
- String tag = super.getTag();
- if (tag != null) {
- return tag;
- }
- // DO NOT switch this to Thread.getCurrentThread().getStackTrace(). The test will pass
- // because Robolectric runs them on the JVM but on Android the elements are different.
- StackTraceElement[] stackTrace = new Throwable().getStackTrace();
- if (stackTrace.length <= CALL_STACK_INDEX) {
- throw new IllegalStateException(
- "Synthetic stacktrace didn't have enough elements: are you using proguard?");
- }
- return createStackElementTag(stackTrace[CALL_STACK_INDEX]);
- }
2.synchronized的使用,因?yàn)镕OREST為單例,所以對(duì)其讀寫要加鎖。
3.static volatile Tree[] forestAsArray ,volatile 保證了可見性
4.關(guān)于plant(Tree tree)方法中的forestAsArray = FOREST.toArray(new Tree[FOREST.size()]);
- public static void plant(@NotNull Tree tree) {
- if (tree == null) {
- throw new NullPointerException("tree == null");
- }
- if (tree == TREE_OF_SOULS) {
- throw new IllegalArgumentException("Cannot plant Timber into itself.");
- }
- synchronized (FOREST) {
- FOREST.add(tree);
- forestAsArray = FOREST.toArray(new Tree[FOREST.size()]);
- }
- }
- 為什么要把List
轉(zhuǎn)成Tree[]數(shù)組?
解釋這個(gè)問題可以參考 深度解析CopyOnWriteArrayList,線程安全的ArrayList!,從使用場(chǎng)景上看,Timber對(duì)于List
- 為什么要用List.toArray(T[] a),而不是List.toArray()?
不推薦使用 toArray() 無參方法,此方法返回值只能是Object[]類,若強(qiáng)轉(zhuǎn)將出現(xiàn)ClassCastException錯(cuò)誤。
移植到鴻蒙
如果Timber沒有默認(rèn)提供DebugTree,直接拿來就能在鴻蒙上使用。DebugTree這棵樹的能力是在Logcat中輸出日志。所以移植要做的就是把a(bǔ)ndroid.util.Log換成ohos.hiviewdfx.HiLog。
HiLog在tag的基礎(chǔ)上擴(kuò)展了HiLogLabel的概念。
label = new HiLogLabel(HiLog.DEBUG,0,tag);
如果每次都new一個(gè)label,太低效,所以這里可以優(yōu)化。比如如果和上次一樣,就使用上次的。或者使用對(duì)象池技術(shù)。
關(guān)鍵代碼:
- public static class DebugTree extends Tree {
- private final ThreadLocal<HiLogLabel> currentLabel = new ThreadLocal<>();
- private final ThreadLocal<String> currentTag = new ThreadLocal<>();
- @Override protected void log(int priority, String tag, @NotNull String message, Throwable t) {
- HiLogLabel label = getHiLogLabel(tag);
- if (message.length() < MAX_LOG_LENGTH) {
- if (priority == HiLog.FATAL) {
- HiLog.fatal(label,message);
- } else if (priority == HiLog.INFO){
- HiLog.info(label, message);
- }else if (priority == HiLog.WARN){
- HiLog.warn(label, message);
- }else if (priority == HiLog.ERROR){
- HiLog.error(label, message);
- }else if (priority == HiLog.DEBUG){
- HiLog.debug(label, message);
- }
- return;
- }
- // Split by line, then ensure each line can fit into Log's maximum length.
- for (int i = 0, length = message.length(); i < length; i++) {
- int newline = message.indexOf('\n', i);
- newline = newline != -1 ? newline : length;
- do {
- int end = Math.min(newline, i + MAX_LOG_LENGTH);
- String part = message.substring(i, end);
- if (priority == HiLog.FATAL) {
- HiLog.fatal(label,part);
- }else if (priority == HiLog.INFO){
- HiLog.info(label, part);
- }else if (priority == HiLog.WARN){
- HiLog.warn(label, part);
- }else if (priority == HiLog.ERROR){
- HiLog.error(label, part);
- }else if (priority == HiLog.DEBUG){
- HiLog.debug(label, part);
- }
- i = end;
- } while (i < newline);
- }
- }
- private HiLogLabel getHiLogLabel(String tag) {
- HiLogLabel label;
- if (tag.equals(currentTag.get())) {
- label = currentLabel.get();
- } else {
- label = new HiLogLabel(HiLog.DEBUG,0,tag);
- currentLabel.set(label);
- currentTag.set(tag);
- }
- return label;
- }
- }
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)