Android:設計模式-策略模式-關於Logger日誌工具的使用

1、Logger介紹

日誌對於開發來說是非常重要的,不管是調試數據查看、bug問題追蹤定位、數據信息收集統計,日常工作運行維護等等,都大量的使用到。
Logger是GitHub上目前有一萬多顆星的,一個簡單,漂亮,功能強大的android日誌記錄工具。
由於它是開源的,你可以直接下載源碼使用,也可以在項目中對其進行依賴。這部分我直接翻譯github上面的介紹並加上自己的理解。你們也可以直接去看原文。github入口
依賴

implementation 'com.orhanobut:logger:2.2.0'

初始化
初始化時,目前Logger工具僅支持兩種適配器,一個是AndroidLogAdapter,即顯示用的日誌(輸出在控制檯)

Logger.addLogAdapter(new AndroidLogAdapter());

另一種是DiskLogAdapter,將日誌保存到文件中。DiskLogAdapter這種適配器,目前的配置是,保存在根路徑下,logs_0.csv爲存儲名稱,500kb爲大小。

Logger.addLogAdapter(new DiskLogAdapter());

如果你想改變日誌在本地內存中的存儲路徑,日誌大小或者日誌存儲名稱。你可以實現logger的三個接口。自定義自己的適配器。具體實現方式,在後面給例子。這裏只寫初始化方式。

Logger.addLogAdapter(new 自定義的適配器名稱());

其中Logger還提供了適配器移除的方法。很明顯可以看出來,無論你加了幾個適配器,只要調用了這個方法所有的適配器都會被移除。不再記錄任何日誌。

Logger.clearLogAdapters();

clearLogAdapters,可以在你只想記錄某一部分日誌時使用。比如:

//任務開始
Logger.addLogAdapter(new DiskLogAdapter());//添加本地日誌記錄適配器
//具體任務的過程日誌記錄
//任務結束
Logger.clearLogAdapters();//移除所有日誌適配器。如果你只想移除DiskLogAdapter,你需要在下一步把其他的添加回去。這有點蠢。
Logger.addLogAdapter(new AndroidLogAdapter());//比如這樣

注意事項
Logger工具自己定義的DiskLogAdapter適配器,日誌大小爲500kb,即使你添加又移除,再添加再移除,只要日誌文件沒有達到500k,就會一直記錄在同一個文件下。

使用

Logger.d("hello");

如果只是查看的話,這樣就可以了。是不是很簡單。在Logcat上的輸出形式如下圖所示
在這裏插入圖片描述
日誌打印有這幾種等級

Logger.d("debug");
Logger.e("error");
Logger.w("warning");
Logger.v("verbose");
Logger.i("information");
Logger.wtf("What a Terrible Failure");

支持字符串格式的輸出

Logger.d("hello %s", "world");

支持集合格式的輸出(僅適用於調試日誌)

Logger.d(MAP);
Logger.d(SET);
Logger.d(LIST);
Logger.d(ARRAY);

Json和Xml支持(輸出將處於調試級別)

Logger.json(JSON_CONTENT);
Logger.xml(XML_CONTENT);

高級設置

FormatStrategy formatStrategy = PrettyFormatStrategy.newBuilder()
  showThreadInfo(false)   //(可選)是否顯示線程信息。默認值true 
  .methodCount(0)          //(可選)要顯示的方法行數。默認值2 
  .methodOffset(7)         //(可選)隱藏內部方法調用到偏移量。默認值5 
  .logStrategy(customLog)//(可選)更改要打印的日誌策略。默認LogCat (即android studio的日誌輸出Logcat)
  .tag("My custom tag")   //  //(可選)每個日誌的全局標記。默認PRETTY_LOGGER .build 
  .build();
  Logger.addLogAdapter(new AndroidLogAdapter(formatStrategy));

是否需要打印
日誌適配器通過檢查此功能來檢查是否應打印日誌。如果要禁用/隱藏輸出日誌,請覆蓋isLoggable方法。 true將打印日誌消息,false將忽略它。下圖是指,debug模式下打印日誌。

Logger.addLogAdapter(new AndroidLogAdapter() {
  @Override public boolean isLoggable(int priority, String tag) {
    return BuildConfig.DEBUG;
  }
});

將自定義標記添加到Csv格式策略


FormatStrategy formatStrategy = CsvFormatStrategy.newBuilder()
  .tag("custom")
  .build();
  
Logger.addLogAdapter(new DiskLogAdapter(formatStrategy));

2、策略模式

3.1策略模式(Strategy)和委託(Delegate)的比較

委託的本質
注:策略模式理解了,委託有點糊塗。先記錄下來。
委託時一種在C#中實現函數動態調用的方式,通過委託可以將一些相同類型的函數串聯起來依次執行。委託同時還是函數回調和事件機制的基礎。

3.2個人理解

我理解的策略就是,首先是一個行爲,做一件事。比如我想配個電腦(策略),那我的具體策略有兩種方式:直接配臺整機,或者自己組裝。
根據策略模式呢,我首先要把配電腦這件事抽象爲一個接口。然後配整機和組裝爲兩種具體策略分別實現該接口的方法,比如計算該種策略下的總價格(整機類的方法就直接return價格就好啦,組裝類的方法,return cpu+散熱+固態+內存條+機械硬盤+機箱+無線網卡+音響+顯示器)。
然後呢,我需要一個使用者去管理具體使用該使用哪種策略。
最後使用的時候,我只需要把具體策略告訴使用者,他就會去自動執行相應的方法啦。這樣的話,如果我又有其他的具體策略,比如鹹魚買個二手機啊之類的。我只要新建一個二手機策略實現配電腦接口就好了。這樣策略和使用者分開解耦,達到松耦合高聚合的目的

3.3策略模式概述

1.1概述
方法是類中最重要的組成部分,一個方法的方法體由一系列語句構成,也就是說一個方法的方法體是一個算法。在某些設計中,一個類的設計人員經常可能涉及這樣的問題:由於用戶需求的變化,導致經常需要修改類中某個方法的方法體,即需要不斷地變化算法。在這樣的情況下可以考慮使用策略模式。

策略模式是處理算法不同變體的一種成熟模式,策略模式通過接口或抽象類封裝算法的標識,即在接口中定義一個抽象方法,實現該接口的類將實現接口中的抽象方法。策略模式把針對一個算法標識的一系列具體算法分別封裝在不同的類中,使得各個類給出的具體算法可以相互替換在策略模式中,封裝算法標識的接口稱作策略,實現該接口的類稱作具體策略。

1.2模式的結構
策略模式的結構包括三種角色:

(1)策略(Strategy):策略是一個接口,該接口定義若干個算法標識,即定義了若干個抽象方法。
(2)具體策略(ConcreteStrategy):具體策略是實現策略接口的類。具體策略實現策略接口所定義的抽象方法,即給出算法標識的具體算法。
(3)上下文(Context):上下文是依賴於策略接口的類,即上下文包含有策略聲明的變量。上下文中提供了一個方法,該方法委託策略變量調用具體策略所實現的策略接口中的方法。

策略模式接口的類圖如下所示:

1.3策略模式的優點
(1)上下文和具體策略是松耦合關係。因此上下文只知道它要使用某一個實現Strategy接口類的實例,但不需要知道具體是哪一個類。
(2)策略模式滿足“開-閉原則”。當增加新的具體策略時,不需要修改上下文類的代碼,上下文就可以引用新的具體策略的實例。

1.4適合使用策略模式的情景
(1)一個類定義了多種行爲,並且這些行爲在這個類的方法中以多個條件語句的形式出現,那麼可以使用策略模式在類中使用大量的條件語句。
(2)程序不希望暴露覆雜的、與算法有關的數據結構,那麼可以使用策略模式來封裝算法。
(3)需要使用一個算法的不同變體。

3.4工廠模式=?策略模式=?模版方法模式

在這裏插入圖片描述

以下這段摘自唐子玄的掘金博客,其實我是在郭嬸的公衆號裏看到的。哈哈。郭嬸推的都是乾貨,比如這哥們的這篇設計模式的分析簡單明瞭,感人肺腑。入口在這
1. 變化是什麼
對策略模式來說,變化就是一組行爲,舉個例子:

public class Robot{
    public void onStart(){
        goWorkAt9Am();
    }
    public void onStop(){
        goHomeAt9Pm();
    }
}

機器人每天早上9點工作。晚上9點回家。公司推出了兩款新產品,一款早上8點開始工作,9點回家。另一款早上9點工作,10點回家。
面對這樣的行爲變化,繼承是可以解決問題的,不過你需要新建兩個Robot的子類,重載一個子類的onStart(),重載另一個子類的onStop()。如果每次行爲變更都通過繼承來解決,那子類的數量就會越來越多(膨脹的子類)。更重要的是,添加子類是在編譯時新增行爲, 有沒有辦法可以在運行時動態的修改行爲?
什麼叫編譯時和運行時
2. 如何應對變化
通過將變化的行爲封裝在接口中,就可以實現動態修改:

//抽象行爲
public interface Action{
    void doOnStart();
    void doOnStop();
}

public class Robot{
    //使用組合持有抽象行爲
    private Action action;
    //動態改變行爲
    public void setAction(Action action){
        this.action = action;
    }
    
    public void onStart(){
        if(action!=null){
            action.doOnStart();
        }
    }
    public void onStop(){
        if(action!=null){
            action.doOnStop();
        }
    }
}

//具體行爲1
public class Action1 implements Action{
    public void doOnStart(){
        goWorkAt8Am();
    }
    public void doOnStop(){
        goHomeAt9Pm();
    }
}

//具體行爲2
public class Action2 implements Action{
    public void doOnStart(){
        goWorkAt9Am();
    }
    public void doOnStop(){
        goHomeAt10Pm();
    }
}

//將具體行爲注入行爲使用者(運行時動態改變)
public class Company{
    public static void main(String[] args){
        Robot robot1 = new Robot();
        robot1.setAction(new Action1());
        robot1.setAction(new Action2());
    }
}

策略模式將具體的行爲和行爲的使用者隔離,這樣的好處是,當行爲發生變化時,行爲的使用者不需要變動。

Android 中的各種監聽器都採用了策略模式,比如View.setOnClickListener(),但下面這個更偏向於觀察者模式,他們的區別是策略模式的意圖在於動態替換行爲,當第二次調用setOnClickListener()時,之前的行爲被替換,而觀察者模式是動態添加觀察者:

public class RecyclerView{
    //使用組合持有抽象滾動行爲
    private List<OnScrollListener> mScrollListeners;
    
    //抽象滾動行爲
    public abstract static class OnScrollListener {
        public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
        public void onScrolled(RecyclerView recyclerView, int dx, int dy){}
    }
    
    //動態修改滾動行爲
    public void addOnScrollListener(OnScrollListener listener) {
        if (mScrollListeners == null) {
            mScrollListeners = new ArrayList<>();
        }
        mScrollListeners.add(listener);
    }
    
    //使用滾動行爲
    void dispatchOnScrollStateChanged(int state) {
        if (mLayout != null) {
            mLayout.onScrollStateChanged(state);
        }
        onScrollStateChanged(state);

        if (mScrollListener != null) {
            mScrollListener.onScrollStateChanged(this, state);
        }
        if (mScrollListeners != null) {
            for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
                mScrollListeners.get(i).onScrollStateChanged(this, state);
            }
        }
    }
}

列表滾動後的行爲各不相同,所以使用抽象類將其封裝起來(其實和接口是一樣的)。

3、Logger源碼分析

首先放一下Logger的運作方式
在這裏插入圖片描述
從上圖可以看出Logger工具使用的是策略模式(Strategy)。第一列是日誌的生命中期,主要是五個過程(接口)

1、獲取原始數據,將其委託給打印機
2、準備好日誌信息,確定是否記錄日誌
3、確定需要記錄的日誌邏輯
4、確定如何顯示/保存日誌輸出
5、打印或保存日誌消息

第二列是對接口的實現。我們可以看到Logger目前提供的兩種日誌記錄方式的具體實現類,即輸出到控制檯(Logcat)和輸出到文件(Disk)。前面說當我們需要改變日誌在本地內存中的存儲路徑,日誌大小或者日誌存儲名稱,我們需要自定義實現的三個接口就是LogAdapter、FormatStrategy、LogStrategy。

但是我不理解的是,爲什麼說這是策略模式。我理解的策略模式,在前文已經說過。所有具體策略實現同一個策略接口,而調用者根據傳入的策略實體去調用不同的具體策略方法。當新增具體策略時,不改變使用者,只是新建實現策略接口的類就可以了。

就源碼來看,頂多LogAdapter算是一個策略接口。
Logger作爲管理類,首先會調用具體Printer對象(LoggerPrinter)的addAdapter方法,相當於將打印的一個具體策略註冊到Printer中。
然後當調用打印方法log.i的時候,LoggerPrinter會遍歷adpter,去調用具體的打印策略的方法。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
之後呢,就是對應具體策略下的具體實現方法了。接下來讓我們看看Disk這種日誌記錄的實現方式,以便參考。在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述

4、自定義Logger日誌名稱、大小以及存儲位置

首先就是創建一個具體的策略ExpDiskLogAdapter implements LogAdapter
在這裏插入圖片描述
可以看到具體策略裏,更改了打印日誌對象爲我自己新定義的ExpCsvFormatStrategy。
在這個類裏,我修改了日誌的打印內容與格式
在這裏插入圖片描述
然後修改了日誌的存放路徑。(也可以在這邊修改文件大小)
在這裏插入圖片描述
日誌的具體寫出方式也改爲了新建的ExpLogStrategy去做實現
在這裏插入圖片描述

github上放置了實現demo
增加打印日誌的新策略:文件名爲hsw+32位UUID;路徑爲根目錄下hsw文件夾;擴展名爲txt。
master分支,是直接clone源碼,在其基礎上修改的,depence分支是依賴了遠程logger庫。第二種方式,需要拷貝logger庫中的Utils工具類。
另外做存儲處理,一定要獲取讀取權限。
還有不在Manifest使用相應權限,動態請求相應權限就總是會失敗。
就是那種去申請權限,也不彈個框框等用戶確認直接申請失敗的情況,所以要加上,原因我也不知道,待後期考察。

  <!--允許程序設置內置sd卡的寫權限-->
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

動態申請權限代碼我就不寫了,demo裏有。
在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章