鸿蒙开源第三方组件——日志工具组件Timber_ohos 原创 精华

朱伟ISRC
发布于 2021-6-30 20:00
浏览
8收藏

 前言 

        基于安卓平台的日志工具组件Timber ( https://github.com/JakeWharton/timber), 实现鸿蒙的功能化迁移和重构。代码已经开源到(https://gitee.com/isrc_ohos/timber_ohos),欢迎各位开发者提出宝贵意见。

背景

        Timber_ohos是一个带有小型可扩展API的日志工具组件,它可以给开发者提供统一的API接口,来记录不同类型的日志,帮助开发者管理不同类型的log。同时,Timber_ohos是项目开发时的log开关,通过此开关控制log的打印与关闭,从而形成不同的软件版本。该组件功能丰富且使用简单高效,可以被广泛应用于软件项目开发中。

组件效果展示

1、测试界面。

       如图1所示,这是一个为了测试Timber_ohos功能而简单构建的UI页面。点击“测试”按钮即可输出相应的log。

鸿蒙开源第三方组件——日志工具组件Timber_ohos-鸿蒙开发者社区

图1 测试界面UI图

2、Log打印

         Timber类的静态方法调用如图2中的(a)图所示。运行项目后查看HiLog显示,可以看到实时打印出来的日志,如图2中的(b)图所示。

鸿蒙开源第三方组件——日志工具组件Timber_ohos-鸿蒙开发者社区

鸿蒙开源第三方组件——日志工具组件Timber_ohos-鸿蒙开发者社区

图2 HiLog日志打印

Sample解析

1、Tree的使用

        Timber_ohos将不同的日志操作以树(Tree)的概念进行表示,种植一种树就拥有一种日志记录功能,种植多种树就拥有多种日志记录的功能,树的种类有很多,常见的树有:DebugTree、RealeseTree、FileTree、CrashReportingTree等,这些树都是继承自Tree类。

  • DebugTree:对所有的日志进行记录。
  • RealeseTree:只对 warn,error,wtf 信息进行记录。
  • FileTree:在运行时将日志记录到文件中。
  • CrashReportingTree:对应用崩溃时的信息进行记录。

        Timber_ohos中默认已经种植了DebugTree,由于Timber_ohos本身是一个可扩展的框架,因此开发者想得到其他类型的Log日志时,就需要自己实现一个日志记录类 ,然后种植到Timber_ohos中即可。

2 、 Sample的实现

        Sample部分需要添加日志记录种类,并负责整体显示布局的搭建。首先为Timber_ohos组件添加想要的任何Tree子类实例(这里使用的是DebugTree),然后设置简单的按钮监听器,当按动按钮时在鸿蒙常规HiLog中出现调试日志。下面将详细介绍组件的使用方法。

步骤1. 种树(添加Tree子类实例)。 

步骤2. 创建整体的显示布局。

步骤3. 导入相关类并设置按钮监听。

步骤4. 使用Tree实例。

(1)种树(添加Tree子类实例)

       本步骤是在ExampleApp类的onInitialize()方法中实现的。首先需要创建Tree子类实例,然后调用Timber的plant()方法,同时将实例作为plant()方法的参数,这个过程叫做“种树”。

Timber.plant(new Timber.DebugTree(0x001f00));

(2)创建整体的显示布局 在XML文件中创建一个DirectionalLayout作为整体显示布局,宽度和高度都跟随父控件变化而调整。创建两个组件,分别是Text组件和Button组件,用于控制组件效果显示。整体显示布局如图1所示。

<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:orientation="vertical"
    ohos:padding="32vp"
    ohos:background_element="#ffffff"
    ohos:alignment="horizontal_center">
    <Text   //“测试”提示
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:layout_alignment="horizontal_center"
        ohos:text="Timber测试"
        ohos:text_size="35fp"/>
    <Button  //控制按钮
        ohos:id="$+id:btn1"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:top_margin="35vp"
        ohos:text_size="25fp"
        ohos:background_element="#FF51A8DD"
        ohos:padding="10vp"
        ohos:text="测试"/>
</DirectionalLayout>

(3)导入显示布局并设置按钮监听

       在MainAbilitySlice中,整体显示布局也需要通过super.setUIContent()方法进行设置,才能生效并成功显示。然后给按钮设置点击事件,当用户需要使用Tree子类实例时,可通过手指进行点击。

super.setUIContent(ResourceTable.Layout_ability_main);//设置整体显示布局
findComponentById(ResourceTable.Id_btn1).setClickedListener(new Component.ClickedListener() {
	...//按钮的点击事件
}

(4)使用Tree实例

      当用户需要打印调试日志的时候,调用Timber的静态方法,就会在鸿蒙常规HiLog上出现调试日志。调试日志如组件效果展示部分的图2所示。

Timber.e  ("Timber.e 测试成功!!!");
Timber.d  ("Timber.d 测试成功!!!");
Timber.i  ("Timber.i 测试成功!!!");
Timber.w   ("Timber.w 测试成功!!!");
Timber.wtf   ("Timber.wtf测试成功!!!");

Library解析

       Library主要为Timber_ohos组件提供日志输出的统一接口。以Sample中种植的调试树(DebugTree)为例,当使用Timber的静态方法Timber.e时,从MainAbilitySlice到Timber.e打印log的地方可以分为5个步骤,整体调用的流程如图3所示。

鸿蒙开源第三方组件——日志工具组件Timber_ohos-鸿蒙开发者社区

图3 调用顺序图

        下面我们着重介绍树(Tree类)在Library中的实现,核心算法prepareLog()内部的逻辑结构这两个方面的内容。

1.树(Tree)的实现

        Tree类是一种概念形式的日志操作,具体可分为(DebugTree、ReleaseTree、FileTree等)。而在Library内部,Tree类也实现了一系列方法,以便于对森林中的各类树进行增加、删除、修改等操作。

(1)在Timber_ohos组件中维护一个森林对象(FOREST)。

       森林对象由不同类型的日志树组合而成,并提供对外的接口进行日志的打印。每种类型的树都可以通过种植操作来把自己添加到森林对象中,或者通过移除操作从森林对象中删除,从而实现该类型日志记录的开启和关闭。

private static final List<Tree> FOREST = new ArrayList<>(); 

(2)种树。

       调用plant()方法,把Tree实例添加进FOREST里面 可以种植一棵树,也可以种植多棵树。这里以种一棵树为例。可以看到,树的种植是在plant()静态方法的synchronized 同步代码块中进行的。具体流程是先将树对象添加到 FOREST 列表中,然后将日志树保存到 forestAsArray 数组中(将树种植到森林中)。

        需要注意的是,如果树为空,则抛出空指针异常的错误;如果开发者手动种植灵魂之树(TREE_OF_SOULS),Timber_ohos将会抛出非法数据异常。

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()]);
  }
}

(3)移除Tree实例

      同样的,树的移除也是在静态方法uproot()中的synchronized 同步代码块中进行的。如果没有该树可以移除,则Timber_ohos组件将抛出一个非法数据异常;反之,Timber_ohos组件将根据移除该树后的 FOREST列表生成 新的forestAsArray 数组。

public static void uproot(@NotNull Tree tree) {
  synchronized (FOREST) {
    if (!FOREST.remove(tree)) {
      throw new IllegalArgumentException("Cannot uproot tree which is not planted: " + tree);
    }
    forestAsArray = FOREST.toArray(new Tree[FOREST.size()]);
  }
}

(4)清除森林里面全部的Tree实例

       移除森林里所有的Tree实例,首先使用FOREST的clear()方法清除所有的Tree实例,将会自动生成一个对应的新的Tree数组,而forestAsArray就是这个数组的引用。因此forestAsArray 数组被设置为空数组。

public static void uprootAll() {
  synchronized (FOREST) {
    FOREST.clear();
    forestAsArray = TREE_ARRAY_EMPTY;
  }
}

(5) 灵魂之树(TREE_OF_SOULS)

        估计很多同学好奇上述TREE_OF_SOULS。代码实现中,在这里运用的是经典设计模式中的代理模式,TREE_OF_SOULS 本质上是一个代理对象,森林中所有其他普通的树对象都是被代理对象,代理对象通过 for 循环来依次调用被代理对象的同名方法,从而实现不同类型的日志记录,如下所示。

private static final Tree TREE_OF_SOULS = new Tree() {
  @Override public void v(String message, Object... args) {
    Tree[] forest = forestAsArray;
    for (Tree tree : forest) {
      tree.v(message, args);
    }
  }

2.核心算法( prepareLog)

       Timber_ohos组件的日志记录功能的核心算法在抽象类 Tree 的私有化 prepareLog()方法中,该方法接收四个参数,如图4所示:

鸿蒙开源第三方组件——日志工具组件Timber_ohos-鸿蒙开发者社区

图4 参数表

prepareLog()中首先判断了打log的条件,然后将要打印的message信息进行了处理,最后调用了抽象方法log进行日志输出。总体而言 prepareLog()算法流程如下:

(1)获取当前线程的 tag。

(2)当正常信息message不为null且信息长度为0时,这时正常信息message为null。

(3)当正常信息message和异常信息t都是 null 时,说明没有信息可以记录,方法直接返回。

(4)异常信息t通过getStackTraceString方法转换为字符串。

(5)正常信息message和可选格式化参数 args 通过formatMessage方法拼装成一个字符串。

(6)调用抽象方法 log 进行日志记录,这个方法由Tree的子类来实现。

private void prepareLog(int priority, Throwable t, String message, Object... args) {
      //获取当前线程的 tag
      String tag = getTag();
      //当正常信息message不为null且信息长度为0时,这时正常信息message为null
      if (message != null && message.length() == 0) {
        message = null;
      }
      //当正常信息 message 和异常信息 t 都是 null 时,说明没有信息可以记录,方法直接返回
      if (message == null) {
        if (t == null) {
          return; // Swallow message if it's null and there's no throwable.
        }
        //异常信息 t 通过 getStackTraceString 方法转换为字符串
        message = getStackTraceString(t);
      } else {
        if (args != null && args.length > 0) {
	//正常信息 message 和可选格式化参数 args 通过 formatMessage 方法拼装成一个字符串
          message = formatMessage(message, args);
        }
        if (t != null) {
          message += "\n" + getStackTraceString(t);
        }
      }
      //调用抽象方法 log 进行日志记录,这个方法由 Tree 的子类来实现
      log(priority, tag, message, t);
    }

项目贡献人

陈丛笑 郑森文 朱伟 陈美汝 蔡志杰 

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
15
收藏 8
回复
举报
15条回复
按时间正序
/
按时间倒序
鱼儿会飞啦啦啦
鱼儿会飞啦啦啦

加油哦,帮

1
回复
2021-6-30 20:05:45
大白兔的耳朵
大白兔的耳朵

支持支持,棒

回复
2021-6-30 20:48:27
jfztaq
jfztaq

不错不错,谢谢分享,希望有视频教程

回复
2021-7-1 04:47:08
朱伟ISRC
朱伟ISRC 回复了 鱼儿会飞啦啦啦
加油哦,帮

谢谢支持~

回复
2021-7-1 09:37:03
朱伟ISRC
朱伟ISRC 回复了 大白兔的耳朵
支持支持,棒

谢谢支持~

回复
2021-7-1 09:37:16
Junfeng0613
Junfeng0613

能把日志保存起来,很实用啊

回复
2021-7-1 09:45:29
朱伟ISRC
朱伟ISRC 回复了 Junfeng0613
能把日志保存起来,很实用啊

谢谢支持~

回复
2021-7-1 09:47:00
SummerRic
SummerRic

日常追朱老师文✌️

1
回复
2021-7-1 10:01:08
朱伟ISRC
朱伟ISRC 回复了 SummerRic
日常追朱老师文✌️

回复
2021-7-1 10:26:44
XY道衍
XY道衍

请问我遇到鸿蒙的Hlog以及其他logger框架都会有打印不出来日志的情况,该框架会出现么?

回复
2021-7-1 10:38:10
没用的喵叔
没用的喵叔 回复了 XY道衍
请问我遇到鸿蒙的Hlog以及其他logger框架都会有打印不出来日志的情况,该框架会出现么?

关闭并重新打开调试模式, 一般就好了。你试试。这应该是鸿蒙系统的问题。

回复
2021-7-1 11:07:24
朱伟ISRC
朱伟ISRC 回复了 没用的喵叔
关闭并重新打开调试模式, 一般就好了。你试试。这应该是鸿蒙系统的问题。

厉害~

回复
2021-7-1 11:09:06
XY道衍
XY道衍 回复了 没用的喵叔
关闭并重新打开调试模式, 一般就好了。你试试。这应该是鸿蒙系统的问题。

我试试,谢谢大佬!

回复
2021-7-1 11:20:15
wx5ffd018cae88e
wx5ffd018cae88e

很实用的组件!

1
回复
2021-7-1 14:27:25
朱伟ISRC
朱伟ISRC 回复了 wx5ffd018cae88e
很实用的组件!

谢谢支持!

回复
2021-7-2 09:33:46
回复
    相关推荐