还未发布过话题
  • 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

  • Robotium 处理跨进程的问题 at 2015年03月16日

    #14 楼 @young
    不复杂,上面已经提供获取 window dump 的源码;
    server 端还需要自己封装,可以参考 AppiumBootstrap 源码

  • Robotium 处理跨进程的问题 at 2015年03月02日
    //获取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;
    
        }
    
  • Robotium 处理跨进程的问题 at 2015年03月02日

    通过 uiautomator 脚本封装一个 server 端,robotium 和它通信,来实现跨进程的操作。
    uiautomator 通过反射获取 AccessibilityNodeInfo 信息,递归获取控件树信息;
    这样效率比 uiautomator dump 快好多倍,只需要几毫秒的时间;