(注意:本文基于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()方法中……