• 看了一下源码,但还没搭建好调试环境,所以跟踪到某一步后就跟踪不下去了。在此仅分享一下找的过程:
    先接着 doctorq 的思路,去找 android 源码中的getRootInActiveWindow
    android.app.UiAutomation

    /**
     * Gets the root {@link AccessibilityNodeInfo} in the active window.
     *
     * @return The root info.
     */
    public AccessibilityNodeInfo getRootInActiveWindow() {
        final int connectionId;
        synchronized (mLock) {
            throwIfNotConnectedLocked();
            connectionId = mConnectionId;
        }
        // Calling out without a lock held.
        return AccessibilityInteractionClient.getInstance()
                .getRootInActiveWindow(connectionId);
    }
    

    接着找 return 中的getRootInActiveWindow
    android.view.acessibility.AccessibilityInteractionClient

    /**
     * Gets the root {@link AccessibilityNodeInfo} in the currently active window.
     *
     * @param connectionId The id of a connection for interacting with the system.
     * @return The root {@link AccessibilityNodeInfo} if found, null otherwise.
     */
    public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) {
        return findAccessibilityNodeInfoByAccessibilityId(connectionId,
                AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
                false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
    }
    

    继续找findAccessibilityNodeInfoByAccessibilityId:
    android.view.acessibility.AccessibilityInteractionClient

    /**
         * Finds an {@link AccessibilityNodeInfo} by accessibility id.
         *
         * @param connectionId The id of a connection for interacting with the system.
         * @param accessibilityWindowId A unique window id. Use
         *     {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
         *     to query the currently active window.
         * @param accessibilityNodeId A unique view id or virtual descendant id from
         *     where to start the search. Use
         *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
         *     to start from the root.
         * @param bypassCache Whether to bypass the cache while looking for the node.
         * @param prefetchFlags flags to guide prefetching.
         * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
         */
        public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId,
                int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache,
                int prefetchFlags) {
            try {
                IAccessibilityServiceConnection connection = getConnection(connectionId);
                if (connection != null) {
                    if (!bypassCache) {
                        AccessibilityNodeInfo cachedInfo = sAccessibilityNodeInfoCache.get(
                                accessibilityNodeId);
                        if (cachedInfo != null) {
                            return cachedInfo;
                        }
                    }
                    final int interactionId = mInteractionIdCounter.getAndIncrement();
                    final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
                            accessibilityWindowId, accessibilityNodeId, interactionId, this,
                            prefetchFlags, Thread.currentThread().getId());
                    // If the scale is zero the call has failed.
                    if (success) {
                        List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                                interactionId);
                        finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
                        if (infos != null && !infos.isEmpty()) {
                            return infos.get(0);
                        }
                    }
                } else {
                    if (DEBUG) {
                        Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                    }
                }
            } catch (RemoteException re) {
                if (DEBUG) {
                    Log.w(LOG_TAG, "Error while calling remote"
                            + " findAccessibilityNodeInfoByAccessibilityId", re);
                }
            }
            return null;
        }
    

    注意中间的connection.findAccessibilityNodeInfoByAccessibilityId,我用的 IDE 是 Android Studio,findAccessibilityNodeInfoByAccessibilityId方法显示为红色,表示找不到它的声明位置。
    然后我们看看 connection 的声明:
    ·IAccessibilityServiceConnection connection = getConnection(connectionId);·
    它是通过getConnection(connectionId)获得的实例,继续找:

    public IAccessibilityServiceConnection getConnection(int connectionId) {
        synchronized (sConnectionCache) {
            return sConnectionCache.get(connectionId);
        }
    }
    

    再找sConnectionCache

    // The connection cache is shared between all interrogating threads.
    private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
        new SparseArray<IAccessibilityServiceConnection>();
    

    继续IAccessibilityServiceConnection,这时候只能找到一个相关的 import 语句了:

    import android.accessibilityservice.IAccessibilityServiceConnection;
    

    到这里为止,寻找方法声明位置的这种方法找不下去了,因为源码里找不到IAccessibilityServiceConnection声明的位置。
    大致猜测是本来每个 Node 其实都带有一定的信息 (具体有哪些信息可以在android.accessibilityservice.AccessibilityServiceInfo找到) 表示它是否影响显示,--compress 插入的标志位会在存储 Node 时不存储这些不影响显示的 Node。具体是如何实现的等研究好如何搭建调试环境后继续研究。

  • 看完以后,我觉得我压根就没学会设计模式。用过 singleton,但没试过探究到内存这么深入。赞一个

  • @ice87875494 这个库是用来给其他应用调用的。你应该下载下来后用其他 py 文件 import pylib 来使用。
    举个例子:

    folder
    ├─pylib(pylib源码)
    │   test.py
    

    此时 test.py 里面使用import pylib就能 import。
    在 module xxx 里面 import xxx 是会提示" No module named xxx"的,因为这个 module 不在查找范围内。具体 import 过程可以看看import system

  • 别人家的测试在做什么? at February 05, 2015

    12 年实习,一开始主要做政府部门的验收测试(公司拿到了对应的资质证书),2~3 天把项目的招标、需求、概设、详设等看完并根据需求写用例,然后又 2~3 天到客户现场把用例执行完(功能测试和压力测试,使用 LoadRunner 录脚本然后根据需求确认性能点是否达标),最后出报告。基本上 1 周 1 个项目的节奏。后面公司需要人手做公司的操作系统的兼容性测试,于是学习了 linux,开始用 shell 脚本来跑一部分测试(脚本都是别人写好的,当时对 linux 的东西还不是太了解),测试安装、驱动性能等。
    13 年毕业,进入一个做海外外包的外企,进入了一个产品线包括嵌入式、web、android、ios 的项目。第一年先是做 ios 测试,然后 android,然后 web,都是纯手工测试。业余学了一些 ios 和 android 调试工具的使用,知道了 appium 的存在(当时 appium 刚出来,还是挺热门的),学会了用 python 写一些简单的监控脚本。正准备开始自己弄一些脚本来让手工测试不那么枯燥的时候,公司需要开发一个测试第三方配件的测试工具,刚好用的是 python,leader 和我也比较熟,然后很幸运地就加入了。现在基本完成了工具的开发,正在写对应这个工具用例作为演示。
    过程中也有人建议我转做开发,因为我有一定的编程经验(以前在学校搞过 J2EE 和 web 的一些东西),但我还是更想做测试,一方面做开发做久了也会很枯燥(开发也有重复性的工作,也有不少 copy/paste),会让我失去对计算机的兴趣,另一方面如同《Google 软件测试之道》里面 Chrome 测试工程经理 Joel Hynoski 所说,“测试是开发过程里面工程师能涉及的最远的地方”。我喜欢对所有事情都一探究竟,直到我完全理解,测试正好可以做到。
    后面还会继续专注自动化测试,当然作为基础的开发也会继续学习。

  • 根据 log 的描述,无法创建新 session 的原因是旧 session 没有被关闭。
    个人建议:

    1. 检查出错的 case 是否真的 quit() 了(如打开的应用是否被关闭了)。
    2. 不同 case 之间加上 10s 左右的等待时间,有时候 quit() 也需要一定时间的。
  • 开源项目召集令[已结束] at February 04, 2015

    @lihuazhang github 账号:chenhengjie123

  • 开源项目召集令[已结束] at February 04, 2015

    @doctorq 有兴趣,求加入,学习 appium 同时改进它。

  • 希望留下,虽然主要都是潜水,但是视野比以前开阔了很多。

  • 程序员如何保持身体健康 at September 27, 2014

    一般公司隔一段时间会组织一些活动的吧,多参加这些活动就有一定锻炼效果了。
    最好还是有一项运动方面的爱好,坚持每周至少参加一次,那么身体相对就好一些。