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里有。
在这里插入图片描述
在这里插入图片描述

发布了72 篇原创文章 · 获赞 28 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/changhuzichangchang/article/details/93167637