dumpWindowHierarchy 的效率太低了。尝试使用反射,通过 AccessibilityNodeInfo 获取控件树信息
可能和 EditorInfo.TYPE 有关。不过可以改写 AndroidIM,使用该输入法完成文字的输入。
uiautomator 启动失败,导致创建 session 失败。为什么启动失败,可以手工执行该命令结合 cmd output 确认问题。
D:\Android\android-sdk-windows\platform-tools\adb.exe -s 71MBBKT22ACK shell uiautomator runtest AppiumBootstrap.jar -c io.appium.android.bootstrap.Bootstrap -e pkg com.xsw.teacher -e disableAndroidWatchers false
//获取window_dump.xml
public String getWindowInfo() {
try {
Object bridge = ReflectionUtils.invokeMethod(this.getUiDevice(),
"getAutomatorBridge", new Class[] {}, new Object[] {});
if (bridge == null) {
return "AutomatorBridge is Null";
}
Object queryController = ReflectionUtils.invokeMethod(bridge,
"getQueryController", new Class[] {}, new Object[] {});
if (queryController == null) {
return "queryController is Null.";
}
Object rootNode = ReflectionUtils
.invokeMethod(queryController, "getAccessibilityRootNode",
new Class[] {}, new Object[] {});
AccessibilityNodeInfo root = (AccessibilityNodeInfo) rootNode;
if (root != null) {
return Utils.getWindowInfo(root, this.getUiDevice()
.getDisplayWidth(), this.getUiDevice()
.getDisplayHeight());
} else {
return "Root Node is Null";
}
} catch (Exception e) {
e.printStackTrace();
Log.e(tag, "Error", e);
return e.getMessage();
}
}
//根据root获取控件数信息
public static String getWindowInfo(AccessibilityNodeInfo root, int screenW, int screenH) {
if (root == null) {
return "";
}
final long startTime = SystemClock.uptimeMillis();
try {
XmlSerializer serializer = Xml.newSerializer();
StringWriter stringWriter = new StringWriter();
serializer.setOutput(stringWriter);
serializer.startDocument("UTF-8", true);
serializer.startTag("", "hierarchy");
dumpNodeRec(root, serializer, 0, screenW, screenH);
serializer.endTag("", "hierarchy");
serializer.endDocument();
return stringWriter.toString();
} catch (IOException e) {
Log.e("UITestin", "failed to dump window to file", e);
}
final long endTime = SystemClock.uptimeMillis();
Log.w("UITestin", "Fetch time: " + (endTime - startTime) + "ms");
return "";
}
//参考uiautomator部分源码
private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer,
int index, int screenW, int screenH) throws IOException {
serializer.startTag("", "node");
if (!nafExcludedClass(node) && !nafCheck(node))
serializer.attribute("", "NAF", Boolean.toString(true));
serializer.attribute("", "index", Integer.toString(index));
serializer.attribute("", "text", safeCharSeqToString(node.getText()));
serializer.attribute("", "class", safeCharSeqToString(node.getClassName()));
serializer.attribute("", "package", safeCharSeqToString(node.getPackageName()));
serializer.attribute("", "content-desc", safeCharSeqToString(node.getContentDescription()));
serializer.attribute("", "checkable", Boolean.toString(node.isCheckable()));
serializer.attribute("", "checked", Boolean.toString(node.isChecked()));
serializer.attribute("", "clickable", Boolean.toString(node.isClickable()));
serializer.attribute("", "enabled", Boolean.toString(node.isEnabled()));
serializer.attribute("", "focusable", Boolean.toString(node.isFocusable()));
serializer.attribute("", "focused", Boolean.toString(node.isFocused()));
serializer.attribute("", "scrollable", Boolean.toString(node.isScrollable()));
serializer.attribute("", "long-clickable", Boolean.toString(node.isLongClickable()));
serializer.attribute("", "password", Boolean.toString(node.isPassword()));
serializer.attribute("", "selected", Boolean.toString(node.isSelected()));
serializer.attribute("", "bounds",
AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(node, screenW, screenH).toShortString());
int count = node.getChildCount();
for (int i = 0; i < count; i++) {
AccessibilityNodeInfo child = node.getChild(i);
if (child != null) {
if (child.isVisibleToUser()) {
dumpNodeRec(child, serializer, i, screenW, screenH);
child.recycle();
} else {
// Log.i(tag, String.format("Skipping invisible child: %s", child.toString()));
}
} else {
// Log.i(tag, String.format("Null child %d/%d, parent: %s",
// i, count, node.toString()));
}
}
serializer.endTag("", "node");
}
//反射相关方法
public static Object invokeMethod(Object object, String methodName,
Class<?>[] parameterTypes, Object[] parameters) {
// 根据 对象、方法名和对应的方法参数 通过反射 调用上面的方法获取 Method 对象
Method method = getDeclaredMethod(object, methodName, parameterTypes);
// 如果Method为空,则肯定没有结果,直接返回null即可。
if (method == null){
return null;
}
// 抑制Java对方法进行检查,主要是针对私有方法而言
method.setAccessible(true);
try {
if (null != method) {
// 调用object 的 method 所代表的方法,其方法的参数是 parameters
return method.invoke(object, parameters);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
public static Method getDeclaredMethod(Object object, String methodName,
Class<?>... parameterTypes) {
Method method = null;
for (Class<?> clazz = object.getClass(); clazz != Object.class; clazz = clazz
.getSuperclass()) {
try {
method = clazz.getDeclaredMethod(methodName, parameterTypes);
return method;
} catch (Exception e) {
// 这里甚么都不要做!并且这里的异常必须这样写,不能抛出去。
// 如果这里的异常打印或者往外抛,则就不会执行clazz =
// clazz.getSuperclass(),最后就不会进入到父类中了
}
}
return null;
}
通过 uiautomator 脚本封装一个 server 端,robotium 和它通信,来实现跨进程的操作。
uiautomator 通过反射获取 AccessibilityNodeInfo 信息,递归获取控件树信息;
这样效率比 uiautomator dump 快好多倍,只需要几毫秒的时间;