版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u011733869/article/details/78820041
目录
- 整体流程概览
- Main
- Console
- 总结
从这篇文章往后开始介绍整个框架的运行流程原理。
1.整体流程概览
这里是整个测试框架的大纲流程图,其中主要涉及到四个线程:
- 1. main – 启动入口
- 2. Console – 处理命令
- 3. CommandScheduler – 命令调度
- 4. InvocationThrad – 执行命令
这个图是整体运行的大纲流程,可以先看下有个大致的认识,等后面看完了每个细节再回来重新捋一遍。
2.main
行号1088
整个测试框架作为一个java程序,在eclipse中可以直接运行,入口在com.android.tradefed.command包下的Console中:
public static void main(final String[] mainArgs) throws InterruptedException,
ConfigurationException {
Console console = new Console();
startConsole(console, mainArgs);
}
可以看到这里做的事情很简单,在main线程中启动了一个console,那么这个Console到底是什么呢?213行
protected Console() {
this(getReader());
}
/**
* Create a {@link Console} with provided console reader.
* Also, set up console command handling.
* <p/>
* Exposed for unit testing
*/
// 构造方法中初始化了一系列变量
Console(ConsoleReader reader) {
super("TfConsole");
mConsoleStartTime = System.currentTimeMillis();
mConsoleReader = reader;
if (reader != null) {
mConsoleReader.addCompletor(
new ConfigCompletor(getConfigurationFactory().getConfigList()));
}
// HelpList初始化
List<String> genericHelp = new LinkedList<String>();
// helpString:放入每个command支持的使用方式
Map<String, String> commandHelp = new LinkedHashMap<String, String>();
// 添加默认支持的命令,使用的就是前面介绍的RegexTrie
addDefaultCommands(mCommandTrie, genericHelp, commandHelp);
// 这个是个空方法,主要是为了方便子类复写添加自己的命令
setCustomCommands(mCommandTrie, genericHelp, commandHelp);
// 生成HelpList
generateHelpListings(mCommandTrie, genericHelp, commandHelp);
}
//代码在,main后面
public static void startConsole(Console console, String[] args) throws InterruptedException,
ConfigurationException {
// 创建GlobalConfiguration
List<String> nonGlobalArgs = GlobalConfiguration.createGlobalConfiguration(args);
console.setArgs(nonGlobalArgs);
// 创建命令调度器
console.setCommandScheduler(GlobalConfiguration.getInstance().getCommandScheduler());
console.setKeyStoreFactory(GlobalConfiguration.getInstance().getKeyStoreFactory());
将console线程设为daemon线程
console.setDaemon(true);
// 启动console线程
console.start();
// Wait for the CommandScheduler to get started before we exit the main thread. See full
// explanation near the top of #run()
// 等待CommandSchedler启动,然后退出
console.awaitScheduler();
}
上面主要作用就是初始化了一些配置,然后启动了console线程,下面来看下这个GlobalConfiguration
public static List<String> createGlobalConfiguration(String[] args)
throws ConfigurationException {
synchronized (sInstanceLock) {
if (sInstance != null) {
throw new IllegalStateException("GlobalConfiguration is already initialized!");
}
List<String> nonGlobalArgs = new ArrayList<String>(args.length);
// 初始化ConfigurationFactory的单例
// GlobalConfiguration以及后面的Congiguration共用
IConfigurationFactory configFactory = ConfigurationFactory.getInstance();
String globalConfigPath = getGlobalConfigPath();
// 重点是这个方法,从参数创建configuration
sInstance = configFactory.createGlobalConfigurationFromArgs(
ArrayUtil.buildArray(new String[] {globalConfigPath}, args), nonGlobalArgs);
//
if (!DEFAULT_EMPTY_CONFIG_NAME.equals(globalConfigPath)) {
// Only print when using different from default
System.out.format("Success! Using global config \"%s\"\n", globalConfigPath);
}
// Validate that madatory options have been set
sInstance.validateOptions();
return nonGlobalArgs;
}
}
private static String getGlobalConfigPath() {
String path = System.getenv(GLOBAL_CONFIG_VARIABLE);
if (path != null) {
System.out.format(
"Attempting to use global config \"%s\" from variable $%s.\n",
path, GLOBAL_CONFIG_VARIABLE);
return path;
}
File file = new File(GLOBAL_CONFIG_FILENAME);
if (file.exists()) {
path = file.getPath();
System.out.format("Attempting to use autodetected global config \"%s\".\n", path);
return path;
}
return DEFAULT_EMPTY_CONFIG_NAME;
}
private static final String GLOBAL_CONFIG_VARIABLE = "TF_GLOBAL_CONFIG";
private static final String GLOBAL_CONFIG_FILENAME = "tf_global_config.xml";
// Empty embedded configuration available by default
private static final String DEFAULT_EMPTY_CONFIG_NAME = "empty";
// configurtionFactory中的重点方法,解析参数创建Iconfiguration
public IGlobalConfiguration createGlobalConfigurationFromArgs(String[] arrayArgs,
List<String> remainingArgs) throws ConfigurationException {
List<String> listArgs = new ArrayList<String>(arrayArgs.length);
IGlobalConfiguration config = internalCreateGlobalConfigurationFromArgs(arrayArgs,
listArgs);
remainingArgs.addAll(config.setOptionsFromCommandLineArgs(listArgs));
return config;
}
private IGlobalConfiguration internalCreateGlobalConfigurationFromArgs(String[] arrayArgs,
List<String> optionArgsRef) throws ConfigurationException {
if (arrayArgs.length == 0) {
throw new ConfigurationException("Configuration to run was not specified");
}
optionArgsRef.addAll(Arrays.asList(arrayArgs));
// first arg is config name
final String configName = optionArgsRef.remove(0);
ConfigurationDef configDef = getConfigurationDef(configName, true, null);
return configDef.createGlobalConfiguration();
}
// 由于这里默认的为空,详细的解析逻辑这里不再过多介绍
// 因为后面对配置文件创建configuration的时候也走这个逻辑
// 会根据具体的xml文件详细介绍解析以及装载的流程
// 目前只需要知道其实还是调用了GlobalConfiguration的构造方法
IGlobalConfiguration createGlobalConfiguration() throws ConfigurationException {
IGlobalConfiguration config = new GlobalConfiguration(getName(), getDescription());
for (Map.Entry<String, List<ConfigObjectDef>> objClassEntry : mObjectClassMap.entrySet()) {
List<Object> objectList = new ArrayList<Object>(objClassEntry.getValue().size());
for (ConfigObjectDef configDef : objClassEntry.getValue()) {
Object configObject = createObject(objClassEntry.getKey(), configDef.mClassName);
objectList.add(configObject);
}
config.setConfigurationObjectList(objClassEntry.getKey(), objectList);
}
for (OptionDef optionEntry : mOptionList) {
config.injectOptionValue(optionEntry.name, optionEntry.key, optionEntry.value);
}
return config;
}
其实就是初始化的时候去查找了看默认的xml配置文件tf_global_config.xml是否存在,不存在的情况下就使用默认的配置:
GlobalConfiguration(String name, String description) {
mName = name;
mDescription = description;
// 配置map,key为全局配置的组件的string,value为组件
mConfigMap = new LinkedHashMap<String, List<Object>>();
// 配置map,key为支持的option
mOptionMap = new MultiMap<String, String>();
setHostOptions(new HostOptions());
setDeviceRequirements(new DeviceSelectionOptions());
// 设备管理
setDeviceManager(new DeviceManager());
// 初始化命令调度器
setCommandScheduler(new CommandScheduler());
setKeyStoreFactory(new StubKeyStoreFactory());
setShardingStrategy(new StrictShardHelper());
}
// 其中最重要的命令调度器的初始化
public CommandScheduler() {
// Thread的子类,设置线程名称
super("CommandScheduler"); // set the thread name
// 已经解析完毕准备执行的command
mReadyCommands = new LinkedList<>();
// 正在执行的command,主要作用是输出警告
mUnscheduledWarning = new HashSet<>();
// 定时调度的command
mSleepingCommands = new HashSet<>();
// 正在执行的command
mExecutingCommands = new HashSet<>();
// 正在执行的命令的map
mInvocationThreadMap = new HashMap<IInvocationContext, InvocationThread>();
// use a ScheduledThreadPoolExecutorTimer as a single-threaded timer.
// 定时器
mCommandTimer = new ScheduledThreadPoolExecutor(1);
// CountDownLatch,main线程中等待
mRunLatch = new CountDownLatch(1);
}
main这里看起来对GlobalConfiguration的操作很复杂,不过在默认情况下,配置文件不存在,也就是使用了默认的配置,最终就是调用了GlobalConfiguration的构造方法而已。不管是GlobalConfiguration还是Configuration,构建都是通过ConfigurationFactory,通过解析xml文件封装对象。在后面介绍具体的configuration的创建的时候就会根据具体的xml文件详细介绍装载过程。
main中的逻辑主要是创建了GlobalConfiguration,启动了console线程,其中有一点很重要:设置console为daemon线程
daemon线程就是在虚拟机中没有其他线程的时候会自动退出的线程。
因为Console线程主要是为了读取用户输入的,设为daemon线程就能保证在其他调度以及运行线程都退出时,Console线程也跟着退出。但是为了防止main线程退出之后,还没有其他线程的启动,Console线程会直接退出,因此最后main会等待CommandScheduler的启动,然后在退出,这里使用的是CountDownLatch。
3.Console线程
public void run() {
List<String> arrrgs = mMainArgs;
if (mScheduler == null) {
throw new IllegalStateException("command scheduler hasn't been set");
}
try {
// 判断控制台
if (!isConsoleFunctional()) {
if (arrrgs.isEmpty()) {
printLine("No commands for non-interactive mode; exiting.");
// FIXME: need to run the scheduler here so that the things blocking on it
// FIXME: will be released.
mScheduler.start();
mScheduler.await();
return;
} else {
printLine("Non-interactive mode: Running initial command then exiting.");
mShouldExit = true;
}
}
// 先把CommandScheduler启动起来,当CommandScheduler启动,main就会退出
mScheduler.start();
mScheduler.await();
String input = "";
CaptureList groups = new CaptureList();
String[] tokens;
do { // 循环
if (arrrgs.isEmpty()) {
// 读取控制台的输入
input = getConsoleInput();
if (input == null) {
// Usually the result of getting EOF on the console
printLine("");
printLine("Received EOF; quitting...");
mShouldExit = true;
break;
}
tokens = null;
try {
// 格式化输入的命令,用空格分割,装进一个数组
tokens = QuotationAwareTokenizer.tokenizeLine(input);
} catch (IllegalArgumentException e) {
printLine(String.format("Invalid input: %s.", input));
continue;
}
if (tokens == null || tokens.length == 0) {
continue;
}
} else {
printLine(String.format("Using commandline arguments as starting command: %s",
arrrgs));
if (mConsoleReader != null) {
final String cmd = ArrayUtil.join(" ", arrrgs);
mConsoleReader.getHistory().addToHistory(cmd);
}
tokens = arrrgs.toArray(new String[0]);
if (arrrgs.get(0).matches(HELP_PATTERN)) {
// if started from command line for help, return to shell
mShouldExit = true;
}
arrrgs = Collections.emptyList();
}
// 很重要的一步,从command的RegexTrie中根据命令取出一个Runnable
Runnable command = mCommandTrie.retrieve(groups, tokens);
if (command != null) {
// 执行这个Runnable的run方法
executeCmdRunnable(command, groups);
} else {
printLine(String.format(
"Unable to handle command '%s'. Enter 'help' for help.", tokens[0]));
}
RunUtil.getDefault().sleep(100);
} while (!mShouldExit);//当输入退出命令时,循环退出
} catch (Exception e) {
printLine("Console received an unexpected exception (shown below); shutting down TF.");
e.printStackTrace();
} finally {
mScheduler.shutdown();
// Make sure that we don't quit with messages still in the buffers
System.err.flush();
System.out.flush();
}
}
这里就能一目了然的看出来,前面我们已经介绍了RegexTrie,初始化的时候把支持的命令给装载进去,这里就通过命令行输入的参数去trie中取,如果匹配到,则最后取出的就是一个Runnable对象,去执行它。
后面我们就以run cts.xml这个为例,这里虽然我用到了cts.xml,这个是在CTS测试框架才会使用,而这里是基础框架,不过因为主要是用xml文件举例,不会涉及到具体cts相关的内容,只需要把cts.xml当做一个普通的配置文件即可。
前面已经说明了,对于run这个命令主要是将其添加到CommandScheduler的队列中。
// Run commands
ArgRunnable<CaptureList> runRunCommand = new ArgRunnable<CaptureList>() {
@Override
public void run(CaptureList args) {
// The second argument "command" may also be missing, if the
// caller used the shortcut.
int startIdx = 1;
if (args.get(1).isEmpty()) {
// Empty array (that is, not even containing an empty string) means that
// we matched and skipped /(?:singleC|c)ommand/
startIdx = 2;
}
String[] flatArgs = new String[args.size() - startIdx];
for (int i = startIdx; i < args.size(); i++) {
flatArgs[i - startIdx] = args.get(i).get(0);
}
try {
// 将命令添加至CommandSchedler的命令队列,等待调度
mScheduler.addCommand(flatArgs);
} catch (ConfigurationException e) {
printLine("Failed to run command: " + e.toString());
}
}
};
这里看上去只有一个简单的addCommand,但是需要注意的是此时参数还是我们从命令行输入的参数,也就是说,现在到这个命令被调度并执行,还需要很重要的一步:解析装载,这个也是这个框架完成注入的特别重要的一环。
4.总结
这篇文件主要介绍了基础框架的启动,作为一个java程序,从main入口开始,初始化全局配置,到后面启动Console读取控制台的输出,进行解析从CommandRegexTrie中取出命令开始执行。
下篇文章重点介绍配置文件的解析与装载。
---------------------
版权声明:本文为CSDN博主「glearn.」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011733869/article/details/78820041