目录

Guide

本文基于https://gitee.com/andych008/timber_ohos 分析Timber的源码,及移植到鸿蒙需要做的工作。

大神JakeWharton的Timber是我写日志的最爱,几乎在所有的项目中都用。当然一般我会通过Timber使用Logger,原因很简单,因为Timber接口简洁,Logger的输出样式好看。常规套路:

  1. FormatStrategy formatStrategy = PrettyFormatStrategy.newBuilder()
  2. .tag("DwGG") // (Optional) Global tag for every log. Default PRETTY_LOGGER
  3. .build();
  4. Logger.addLogAdapter(new AndroidLogAdapter(formatStrategy));
  5. Timber.plant(new Timber.DebugTree() {
  6. @Override
  7. protected void log(int priority, String tag, String message, Throwable t) {
  8. Logger.log(priority, tag, message, t);
  9. }
  10. });

当然它的内部实现也一样完美。咱们往下看。

原理

Timber英文翻译为**“木材”**。静态方法Timber.plant(Tree tree)即种树。每种一棵树,就拥有一种日志能力。

比如树A表示输出日志到控制台,树B表示输出日志到文件,树C输出到网络。

代码实现上,Timber使用了外观(facade)模式

Tree类是外观类,通过plant方法Timber持有Tree类的实例,Timber中的asTree、tag方法将它暴露出去,而对于调用者来说依赖的是抽象类Tree,而不是具体的Tree的实现,如果要更换或者添加Tree类实例,只需要调用plant等相关方法即可,所有调用者使用Tree对象的地方不需要做任何修改,这是符合面向对象依赖倒置原则的一个很好的体现。

另外也使用了委托(delegate)模式Tree TREE_OF_SOULS把所有的操作都委托给forestAsArray

更详细的分析请移步

  1. Timber 源码解析
  2. Timber源码解析及涉及知识点总结

知识点

  1. 临时tag的实现方法

    很简单,Timber.tag("临时tag").d(xxx);设置临时tag。使用一次就删除。

    为了性能,使用ThreadLocal 以空间换时间。

    1. public static abstract class Tree {
    2. final ThreadLocal<String> explicitTag = new ThreadLocal<>();
    3. String getTag() {
    4. String tag = explicitTag.get();
    5. if (tag != null) {
    6. explicitTag.remove();
    7. }
    8. return tag;
    9. }
    10. }
  1. public static class DebugTree extends Tree {
  2. @Override final String getTag() {
  3. String tag = super.getTag();
  4. if (tag != null) {
  5. return tag;
  6. }
  7. // DO NOT switch this to Thread.getCurrentThread().getStackTrace(). The test will pass
  8. // because Robolectric runs them on the JVM but on Android the elements are different.
  9. StackTraceElement[] stackTrace = new Throwable().getStackTrace();
  10. if (stackTrace.length <= CALL_STACK_INDEX) {
  11. throw new IllegalStateException(
  12. "Synthetic stacktrace didn't have enough elements: are you using proguard?");
  13. }
  14. return createStackElementTag(stackTrace[CALL_STACK_INDEX]);
  15. }
    • 为什么要把List<Tree>转成Tree[]数组?

      解释这个问题可以参考 深度解析CopyOnWriteArrayList,线程安全的ArrayList!,从使用场景上看,Timber对于List<Tree> FOREST读多写少,所以只对写操作加锁,读操作(遍历时)不需要加锁。其本质上也是读写分离的思想,和CopyOnWriteArrayList类似,也是为了性能。

    • 为什么要用List.toArray(T[] a),而不是List.toArray()

      不推荐使用 toArray() 无参方法,此方法返回值只能是Object[]类,若强转将出现ClassCastException错误。

移植到鸿蒙

如果Timber没有默认提供DebugTree,直接拿来就能在鸿蒙上使用。DebugTree这棵树的能力是在Logcat中输出日志。所以移植要做的就是把android.util.Log换成ohos.hiviewdfx.HiLog

HiLog在tag的基础上扩展了HiLogLabel的概念。

label = new HiLogLabel(HiLog.DEBUG,0,tag);

如果每次都new一个label,太低效,所以这里可以优化。比如如果和上次一样,就使用上次的。或者使用对象池技术。

关键代码:

  1. public static class DebugTree extends Tree {
  2. private final ThreadLocal<HiLogLabel> currentLabel = new ThreadLocal<>();
  3. private final ThreadLocal<String> currentTag = new ThreadLocal<>();
  4. @Override protected void log(int priority, String tag, @NotNull String message, Throwable t) {
  5. HiLogLabel label = getHiLogLabel(tag);
  6. if (message.length() < MAX_LOG_LENGTH) {
  7. if (priority == HiLog.FATAL) {
  8. HiLog.fatal(label,message);
  9. } else if (priority == HiLog.INFO){
  10. HiLog.info(label, message);
  11. }else if (priority == HiLog.WARN){
  12. HiLog.warn(label, message);
  13. }else if (priority == HiLog.ERROR){
  14. HiLog.error(label, message);
  15. }else if (priority == HiLog.DEBUG){
  16. HiLog.debug(label, message);
  17. }
  18. return;
  19. }
  20. // Split by line, then ensure each line can fit into Log's maximum length.
  21. for (int i = 0, length = message.length(); i < length; i++) {
  22. int newline = message.indexOf('\n', i);
  23. newline = newline != -1 ? newline : length;
  24. do {
  25. int end = Math.min(newline, i + MAX_LOG_LENGTH);
  26. String part = message.substring(i, end);
  27. if (priority == HiLog.FATAL) {
  28. HiLog.fatal(label,part);
  29. }else if (priority == HiLog.INFO){
  30. HiLog.info(label, part);
  31. }else if (priority == HiLog.WARN){
  32. HiLog.warn(label, part);
  33. }else if (priority == HiLog.ERROR){
  34. HiLog.error(label, part);
  35. }else if (priority == HiLog.DEBUG){
  36. HiLog.debug(label, part);
  37. }
  38. i = end;
  39. } while (i < newline);
  40. }
  41. }
  42. private HiLogLabel getHiLogLabel(String tag) {
  43. HiLogLabel label;
  44. if (tag.equals(currentTag.get())) {
  45. label = currentLabel.get();
  46. } else {
  47. label = new HiLogLabel(HiLog.DEBUG,0,tag);
  48. currentLabel.set(label);
  49. currentTag.set(tag);
  50. }
  51. return label;
  52. }
  53. }
  • synchronized的使用,因为FOREST为单例,所以对其读写要加锁。

  • static volatile Tree[] forestAsArray ,volatile 保证了可见性

  • 关于plant(Tree tree)方法中的forestAsArray = FOREST.toArray(new Tree[FOREST.size()]);

    1. public static void plant(@NotNull Tree tree) {
    2. if (tree == null) {
    3. throw new NullPointerException("tree == null");
    4. }
    5. if (tree == TREE_OF_SOULS) {
    6. throw new IllegalArgumentException("Cannot plant Timber into itself.");
    7. }
    8. synchronized (FOREST) {
    9. FOREST.add(tree);
    10. forestAsArray = FOREST.toArray(new Tree[FOREST.size()]);
    11. }
    12. }

    作者:没用的喵叔

    想了解更多内容,请访问51CTO和华为合作共建的鸿蒙社区:https://harmonyos.51cto.com/

安卓to鸿蒙系列:Timber的更多相关文章

  1. 【资源下载】安卓VS鸿蒙第三方件切换宝典 V1.0

    下载<安卓VS鸿蒙第三方件切换宝典> 由于字数较多,本文仅展示部分,查看完整版请点击上方下载 众所周知,安卓应用开发经过这么多年的发展相对成熟和稳定,鸿蒙OS作为后来者兼容一个成熟的开发体 ...

  2. 纯C++安卓开发 (ndk)系列之 ---- 常见问题

    常见问题1:run as Android Application运行时提示无法识别到模拟器 解决步骤如下: (1)首先查看安卓模拟器是否已经打开 (2)如果安卓模拟器已经打开,则操作步骤为:点击Ecl ...

  3. 【安卓中的缓存策略系列】安卓缓存策略之综合应用ImageLoader实现照片墙的效果

    在前面的[安卓缓存策略系列]安卓缓存之内存缓存LruCache和[安卓缓存策略系列]安卓缓存策略之磁盘缓存DiskLruCache这两篇博客中已经将安卓中的缓存策略的理论知识进行过详细讲解,还没看过这 ...

  4. 华为鸿蒙OS发布!方舟支持混合编译,终将可替换安卓?

    前言 有关于鸿蒙的消息之前也有说过,就在昨天下午,华为举行了2019开发大会,正式推出了鸿蒙os系统(Harmony).其相关负责人表示,也是基于微软内核的全场景分布式OS   鸿蒙凭借微内核的优势, ...

  5. 最全华为鸿蒙 HarmonyOS 开发资料汇总

    开发 本示例基于 OpenHarmony 下的 JavaScript UI 框架,进行项目目录解读,JS FA.常用和自定义组件.用户交互.JS 动画的实现,通过本示例可以基本了解和学习到 JavaS ...

  6. 安卓开发30:AsyncTask的用法

    http://blog.csdn.net/initphp/article/details/10392093 安卓开发笔记系列(43)  在开发Android应用时必须遵守单线程模型的原则: Andro ...

  7. 华为 鸿蒙系统(HarmonyOS)

    HarmonyOS Ⅰ. 鸿蒙系统简介 鸿蒙系统(HarmonyOS),是第一款基于微内核的全场景分布式OS,是华为自主研发的操作系统.2019年8月9日,鸿蒙系统在华为开发者大会<HDC.20 ...

  8. [github] 关于华为鸿蒙OS

    English Docs | 中文文档 | Türkçe Dökümanlar HarmonyOS Ⅰ. 鸿蒙系统简介 鸿蒙系统(HarmonyOS),是第一款基于微内核的全场景分布式OS,是华为自主 ...

  9. 【python基础】第02回 计算机基础2

    上节内容回顾 1.绝对路径与相对路径 1.路径的概念 用来标识资源的位置 2.绝对路径 类似于全球GPS定位(给到任何人都可以顺利的找到相应的资源) eg: D:\aaa\a.txt 3.相对路径 需 ...

随机推荐

  1. Vue脚手架中默认的margin怎么清除

    问题情景:开发中发现我的项目四周有白边,但是并没有设置样式 问题原因:vue脚手架中静态文件夹public中的index.html造成的 解决方案:找到vue脚手架中index.html页面,设置ma ...

  2. Apache支持Vue router使用 HTML5History 模式

    一.前言 前端Vue router 使用history模式,URL会比hash模式好看,这种模式要玩好,还需要后端配置支持,否则会报404错误. 注:1.前端代码省略. 2.此处后台使用Apache服 ...

  3. 卧槽,好强大的魔法,竟能让Python支持方法重载

    1. 你真的了解方法重载吗? 方法重载是面向对象中一个非常重要的概念,在类中包含了成员方法和构造方法.如果类中存在多个同名,且参数(个数和类型)不同的成员方法或构造方法,那么这些成员方法或构造方法就被 ...

  4. kubernetes生产实践之redis-cluster

    方案一 自定义yaml文件安装redis cluster 背景 在Kubernetes中部署Redis集群面临挑战,因为每个Redis实例都依赖于一个配置文件,该文件可以跟踪其他集群实例及其角色.为此 ...

  5. 导出文件,responseType设置了blob,实际返回了JSON格式的错误信息的处理方式

    需求:导出文件 问题描述:由于后台直接返回的文件流,在请求下载的方法中将XHR 的 responseType 指定为 blob 或者 arraybuffer.但并不是每次的操作都是成功的,所以在接口错 ...

  6. 07、列表list

    列表(list) 是一个有序且可变的容器,在里面可以存放多个不同类型的元素 list = ['阿斯顿','阿发师','收发室'] list = [98,88,66,-1] list = [1,True ...

  7. 京东数科面试真题:常见的 IO 模型有哪些?Java 中的 BIO、NIO、AIO 有啥区别?

    本文节选自<Java面试进阶指北 打造个人的技术竞争力> 面试中经常喜欢问的一个问题,因为通过这个问题,面试官可以顺便了解一下你的操作系统的水平. IO 模型这块确实挺难理解的,需要太多计 ...

  8. Java8的新特性--Optional

    目录 Optional 一.Optional类是什么? 二.Optional类常用的方法 1. 创建Optional实例 1.1 Optional.of(T) 1.2 Optional.empty() ...

  9. MySQL数据库与python交互

    1.安装引入模块 安装mysql模块 pip install PyMySQL; 文件中引入模块 import pymysql 2.认识Connection对象 用于建立与数据库的连接 创建对象:调用c ...

  10. Java例题_19 打印菱形图案

    1 /*19 [程序 19 打印菱形图案] 2 题目:打印出如下图案(菱形) 3 * 4 *** 5 ***** 6 ******* 7 ***** 8 *** 9 * 10 */ 11 12 /*分 ...