Android之UiAutomator测试框架源码分析(第二十八篇:线程堆栈日志输出的封装,Tracer类分析)

(注意:本文基于UI Automator测试框架版本为2.2.0)

前言

    看了好多UiAutomator测试框架的源码,为了能在控制台更好的输出插桩线程执行的日志Log,Google工程师封装一个Trace类,Trace类专门用于调试并输出log,今天一起学习Google大牛对于日志的管理,它们是如何做好输出日志的……

Trace类结构

public class Tracer {
}

    Trace类位于androidx.test.uiautomator包中,一个继承Object类的功能类,它的用途,我们先看看官方说明与我的解释

官方:Trace类用于对UiAutomator的API调用进行跟踪,并输出Trace(调用线程堆栈)到logcat或者日志文件中,UiAutomator中的每个公共方法需要Trace,它应该包括对跟踪器的调用。跟踪的

开始。Trace在默认情况下是关闭的,需要明确启用。

个人:Trace对象用于对象UiAutomator测试框架中的API调用进行日志输出,它可以输出到logcat中、或者持久化到文件中,也可以同时指定输出到logcat和文件中,默认情况下Trace功能是关闭的,需要我们开启,开启的方式,是通过一个命令行参数,这个参数就是启动UiAutomator时的adb shell am instrament 中的traceOutputMode参数!!原来如此……

内部接口TracerSink

    private interface TracerSink {
        public void log(String message);

        public void close();
    }

此接口规范了日志输出、日志关闭能力的API,任何一个类实现了此接口,就表示具备输出log、关闭log的能力,它的两个实现子类是:

1、LogcatSink

2、FileSink

看来LogcatSink与FileSink具备一样的能力,只是具体的实现不同,一个是向logcat输出日志、另一个是向文件中写入日志

两个内部类

1、FileSink 

    private class FileSink implements TracerSink {
     
         …………省略很多源码…………

    }

用于向logcat中输出日志

2、LogcatSink

    private class LogcatSink implements TracerSink {

         …………省略很多源码…………

    }

用于向文件中输出日志

一个内部枚举类Mode

    public enum Mode {
        NONE,
        FILE,
        LOGCAT,
        ALL
    }

定义了日志输出的方式(枚举类的四个对象,表示不同的含义)

1、None:表示什么都不输出

2、FILE:表示输出到文件

3、LOGCAT:表示输出到logcat

4、ALL:表示同时输出到文件与locat

Trace类加载

    private static final String UNKNOWN_METHOD_STRING = "(unknown method)";
    private static final String UIAUTOMATOR_PACKAGE = "androidx.test.uiautomator";
    private static final int CALLER_LOCATION = 6;
    private static final int METHOD_TO_TRACE_LOCATION = 5;
    private static final int MIN_STACK_TRACE_LENGTH = 7;
    private static Tracer mInstance = null;

Trace类加载时,这些代码将会执行,看下Trace类持有的这些字段,都是干什么的……

UNKNOWN_METHOD_STRING:常量值表示"未知方法"

UIAUTOMATOR_PACKAGE:常量值表示UiAutomator测试框架的包名

CALLER_LOCATION:常量值表示StackTraceElement数组对象中的下标,此下标对应的信息是方法调用的下标位置(行数)

METHOD_TO_TRACE_LOCATION:常量值表示StackTraceElement数组对象中的下标,此值为5,表示方法调用堆栈的下标位置

MIN_STACK_TRACE_LENGTH:常量值表示StackTraceElement数组对象的表示调用堆栈的最小元素数,即调用堆栈最少7个元素,这个与StackTraceElement对象有关

mInstance:静态变量mInstance表示Trace类持有一个Trace对象,说明Trace也为单例对象

Tracer对象的创建

    public static Tracer getInstance() {
        if (mInstance == null) {
            mInstance = new Tracer();
        }
        return mInstance;
    }

静态方法getInstance(),用于获取一个Tracer对象,当Tracer对象没有创建时,会首先创建Tracer对象

1、检查Tracer对象是否已经创建

通过判断Tracer类持有的mInstance是否为null,用来判断Tracer对象的创建,当未创建时,执行new Tracer(),由Tracer类持有的mInstance负责持有Tracer对象

2、向调用者返回Tracer对象

return语句返回Tracer对象,此单例对象由静态变量mInstance持有

备注:Tracer对象表示一个分析者,直接使用的默认的无参构造方法

Tracer对象创建时执行的代码

    private Mode mCurrentMode = Mode.NONE;
    private List<TracerSink> mSinks = new ArrayList<TracerSink>();
    private File mOutputFile;

mCurrentMode:Tracer对象持有的日志模式对象,默认值为NONE,表示不输出任何日志,Mode为枚举类型,NONE是Mode类产生的对象

mSinks:Tracer对象持有的一个List对象,该List用于持有多个TracerSink对象,每个TracerSink对象都具备输出Log和关闭Log的能力

mOutputFile:Tracer对象持有的一个File对象,该File对象表示存储日志的文件

静态方法trace()分析

    public static void trace(Object... arguments) {
        Tracer.getInstance().doTrace(arguments);
    }

这个静态方法常常在UiAutomator测试框架中某个类的某个方法的开头处被调用,传入的参数为可变参数,可以传入0个,也可以传入多个,类型是Object

1、获取Tracer对象

通过Tracer的getInstance()方法获取到Tracer对象

2、使用doTrace方法执行任务,同时将可变参数arguments传入,可变参数arguments本身为一个Object数组对象

doTrace()方法分析

    private void doTrace(Object[] arguments) {
        if (mCurrentMode == Mode.NONE) {
            return;
        }

        String caller = getCaller();
        if (caller == null) {
            return;
        }

        log(String.format("%s (%s)", caller, join(", ", arguments)));
    }

用于执行收集线程堆栈的方法,传入的参数表示什么呢?

1、检查当前日志的模式

如果发现当前模式为NONE,NONE表示关闭,所以此时什么都不作……

2、获取调用者的名字

通过getCaller()方法可以获取到调用者的很多信息,调用者的信息会存储到局部变量caller中

3、检查调用者的信息

如果没有获取到调用者的信息,方法直接中断,没有必要再输出日志了……

4、通过log()方法、join()方法等等,最后通过Log类的静态方法i()将信息输出到控制台(Log类最后会调用c++层的println_native()方法,完成日志的输出)

getCaller()方法分析

    private static String getCaller() {
        StackTraceElement stackTrace[] = Thread.currentThread().getStackTrace();
        if (stackTrace.length < MIN_STACK_TRACE_LENGTH) {
            return UNKNOWN_METHOD_STRING;
        }

        StackTraceElement caller = stackTrace[METHOD_TO_TRACE_LOCATION];
        StackTraceElement previousCaller = stackTrace[CALLER_LOCATION];

        if (previousCaller.getClassName().startsWith(UIAUTOMATOR_PACKAGE)) {
            return null;
        }

        int indexOfDot = caller.getClassName().lastIndexOf('.');
        if (indexOfDot < 0) {
            indexOfDot = 0;
        }

        if (indexOfDot + 1 >= caller.getClassName().length()) {
            return UNKNOWN_METHOD_STRING;
        }

        String shortClassName = caller.getClassName().substring(indexOfDot + 1);
        return String.format("%s.%s from %s() at %s:%d", shortClassName, caller.getMethodName(),
                previousCaller.getMethodName(), previousCaller.getFileName(),
                previousCaller.getLineNumber());
    }

用于获取调用者信息的方法,展示的是线程堆栈,封装的牛逼!

1、获取线程堆栈

通过Thread的静态方法currentThread()获取到当前Thread对象,接着调用Thread的getStackTrace()方法,获取到一个StackTraceElement数组对象(备注:StackTraceElement是Josh Bloch大佬封装表示栈trace的元素对象,元素对象一般持有类名、方法名、文件名、还有行数,详细见StackTraceElement的源码)

2、与最小范围进行比较

如果获取到线程栈元素数量小于最低的阈值,直接返回一个(unknown method)

3、获取调用者对象

METHOD_TO_TRACE_LOCATION的下标值对应着固定的调用者信息,从数组对象中提取,获取到的StackTraceElement对象存储在局部变量caller中

4、获取调用者的上一个调用者?

CALLER_LOCATION的下标值对应着调用者的上一个调用者,同样存储在局部变量previousCaller中

5、检查调用者的上一个调用者

如果调用者的上一个调用者的类名开头是androidx.test.uiautomator,直接返回一个null

6、检查类名,无效也会返回

(unknown method)

7、获取类名的短名称

8、String的静态方法format(),返回一个包括短类名、调用者方法名称、调用者上一个调用者的方法名称,调用者上一个调用者的文件名,调用者上一个调用者的行数

String.format("%s.%s from %s() at %s:%d", shortClassName, caller.getMethodName(),
        previousCaller.getMethodName(), previousCaller.getFileName(),
        previousCaller.getLineNumber());

setOutputMode()方法分析

用于设置模式的方法,可以指定单纯文件存储日志、或者单纯控制台输出日志、或者同时、或者干脆不需要日志

setOutputFilename()方法分析

用于设置存储日志的文件名的方法

回顾FileSink类

用于向磁盘的文件中持久化日志的类,只需要创建FileSink对象即可使用,它当然实现了TracerSink接口,它定义在Tracer的内部(普通内部类隐式持有外部类对象)

回顾LogcatSink类

用于向控制台输出日志的类,仅需创建LogcatSink对象即可使用,它同样实现了TracerSink接口,它也定义在Tracer的内部(作为普通内部类)

总结

1、Tracer类是精心设计的,内部既有枚举、也有接口、还有两个内部普通类,设计的特别的巧妙,针对不同的输出日志模式,只需要传入不同的模式

2、在UiAutomator测试框架中,并没有默认开启Tracer功能,所以Tracer.trace();这样的代码什么也没有做,唯一开启Tracer功能的地方是UiAutomatorInstrumentationTestRunner下的onStart()方法中……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值