前几天稍微改装了一下 UIAutoatorview 生成脚本,刚发完帖子发现论坛已经有人发出来了,闭门造车不是个好习惯哈,我做这个东西的初衷呢?就是是这段时间学习 APPIUM 觉得写脚本调式费劲,就是想能确定元素是否真的可以定位到,可以执行,最终的一切都是为了支持 Appium 的执行,目前程序完成的比较粗糙,健壮性也不太好,主要是为了给不是特别深入的朋友提供些思路和参考
我这里面的检查执行呢?底层调用时通过 Appium 的 Bootstrap 实现,这样才能保持和 Appium 环境一致,如果用坐标点确定的话,生成的脚本放到 Appium 里面也不一定是有效的,执行环境变了
具体实现,大家看过 Bootstrap 的会知道,Bootstrap 其实是发布了一个 Socket 服务端,并且是长连接,所以当 Bootstrap 运行起来我们可以通过 Socket 去连接发送请求
/**
* @Descrition:执行初始化连接
* */
public void init() {
try {
String host = "127.0.0.1";
int port = 4724;
InetSocketAddress remote = new InetSocketAddress(host, port);
sc = SocketChannel.open();
sc.connect(remote);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @Descrition:发送请求
* */
public void sendMsg(){
boolean run = true;
while (run) {
String sendMsg = QueueMsg.getSendMsg(); //这里简单弄了个队列信息
if (null!=sendMsg&&!sendMsg.equals("")) {
printMsg(sendMsg, sendMsg);
InputStream inputStream = new ByteArrayInputStream(sendMsg.getBytes());
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
String str = br.readLine();
printMsg(sendMsg, "读入一行数据,开始发送...");
w_bBuf = ByteBuffer.wrap(str.getBytes("UTF-8"));
//将缓冲区中数据写入通道
sc.write(w_bBuf);
printMsg(sendMsg, "数据发送成功...");
w_bBuf.clear();
printMsg(sendMsg, "接收服务器端响应消息...");
try {
Thread.currentThread();
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
r_bBuf.clear();
//将字节序列从此通道中读入给定的缓冲区r_bBuf
sc.read(r_bBuf);
r_bBuf.flip();
String resultMsg = Charset.forName("UTF-8").decode(r_bBuf).toString();
QueueMsg.setResultMsg(resultMsg);
}
}
}
我们看 UIAutomatorView 这个工具去 Dump 控件文件会发现它是执行的 UIAutoamtor,Boostrap 也是执行的 UIAutoamtor,那么我们启动 Boostrap 之后,线程是阻塞的,所以在用 UIAutomatorView 去 Dump 是无法 Dump 的,所以我们需要对 Boostrap 进行改造一下,通过 Boostrap 去 Dump 控件文件
首先在 Boostrap 中的 AndroidCommandExecutor.java 类要加入一行命令
map.put("dump", new Dump());
public class Dump extends CommandHandler {
private static final File dumpFolder = new File(Environment.getDataDirectory(), "local/tmp");
private static final String dumpFileName = "myuidump.xml";
@Override
public AndroidCommandResult execute(AndroidCommand command) {
File file = new File(dumpFolder, dumpFileName);
try {
QueryController queryController = UiAutomatorBridge.getInstance().getQueryController();
AccessibilityNodeInfo accessibilityRootNode = queryController.getAccessibilityRootNode();
String msg = "success";
try {
AccessibilityNodeInfoDumper.dumpWindowToFile(accessibilityRootNode, file);
} catch (Exception e) {
msg = e.toString();
}
return getSuccessResult(msg);
} catch (final Exception e) {
Logger.debug("Dump Exception: " + e);
return getErrorResult(e.getMessage());
}
}
}
UIAutomator 里面的 UiAutomatorHelper 类的 getUiHierarchyFile 方法要改动一下,通过 Bootstrap 去 Dump 控件文件
monitor.subTask("Taking UI XML snapshot...");
//原来的Dunp方法
//try {
// device.executeShellCommand(
// command,
// new CollectingOutputReceiver(commandCompleteLatch),
// XML_CAPTURE_TIMEOUT_SEC * 1000);
// commandCompleteLatch.await(XML_CAPTURE_TIMEOUT_SEC, TimeUnit.SECONDS);
//
// monitor.subTask("Pull UI XML snapshot from device...");
// device.getSyncService().pullFile(UIDUMP_DEVICE_PATH,
// dst.getAbsolutePath(), SyncService.getNullProgressMonitor());
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
//修改后的Dunp方法
try {
QueueMsg.setSendMsg("{\"cmd\":\"action\",\"action\":\"dump\",\"params\":{}} ");
monitor.subTask("Pull UI XML snapshot from device...");
String value = QueueMsg.getResultMsg();
BootstrapCmdDomain bootstrapCmdDomain = JSON.parseObject(value, BootstrapCmdDomain.class);
int status = bootstrapCmdDomain.getStatus();
if (0 == status) {
device.getSyncService().pullFile(MyUIDUMP_DEVICE_PATH,
dst.getAbsolutePath(), SyncService.getNullProgressMonitor());
} else {
throw new Exception(bootstrapCmdDomain.getValue());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
剩下就是封装一些点击事件,输入文本脚本生成的事情了
完成后的结果,我们可以手动把 APP 启动起来,然后打开 UIAtuomator,弄一个输入的事件
选择一个元素,鼠标右键
确定后设备里面的输入被执行了,右侧的文本框打印出了脚本,这里的打印信息,只有事件被正确执行才能生成脚本,生成的脚本规则是自己定义的,所以这里按照自己的需求写自己的算法
点击事件,我们点击下【搜索按钮】
UI 界面什么的都比较粗糙,其实就把它做一个辅助工具,为 Appium 服务,所有功能 OK 就可以了,当然大家可以自己加一些拖拽、JS 执行、长按等事件的操作