5

我将 AndroidAccessibilityService部署到运行 Android 5.0.1 的三星 Note 4 上。

我使用 WhatsApp 作为测试平台,但这适用于任何应用程序,并且更多的是关于无障碍服务如何触发事件的问题。

Android 触发的事件2048 (TYPE_WINDOW_CONTENT_CHANGED)不一致。如果我向我的 WhatsApp 发送消息,并且在 75% 的时间里,这个事件会被触发并且有时根本不会触发。

是否有一个原因?可访问性事件是否不可靠..?

此外,当用户滚动或 WhatsApps 的聊天窗口中出现新的通信时,似乎该事件4096 (TYPE_VIEW_SCROLLED)确实会持续触发,但是,似乎无论如何都无法确定设备的当前滚动位置是什么?AccessibilityEvent.getSource()提供对列表的某些元数据的访问(在本例中为 android:id/list),但是没有关于此列表或其子元素的滚动位置的信息。子列表与屏幕上显示的内容相关,boundsToScreen/Parent无论您查看列表底部还是中间或顶部,其值都是相同的。是否有任何线索可以帮助我从我看到的AccessibilityEventNodeInfo实例中确定滚动位置?

最后,当2048 (TYPE_WINDOW_CONTENT_CHANGED)事件触发时,有时新元素实际上并不可用AccessibiltyEvent.getSource()(即使您使用 while 循环迭代到根元素getParent(),然后再次向下扫描)。在将更改应用于 UI 之前,该事件似乎正在拍摄屏幕快照。Athread.sleep没有帮助 - 看起来AccessibilityEventNodeInfo更像是快照而不是对 UI 的实时访问?有什么办法吗?

4

1 回答 1

3

是否有一个原因?可访问性事件是否不可靠..?

我发现当一个事件被触发时,你更有可能误解TYPE_WINDOW_CONTENT_CHANGED,而不是它没有被触发或持续捕获。例如,此特定事件不会在屏幕刷新时触发,只会在新窗口内容时触发。应用程序开发人员可以选择在其 Activity 上重新绘制内容,而不是启动新的 Activity。在这种情况下,从用户的角度来看,窗口内容已经改变,然而,在应用程序的后端,他所发生的只是绘制了新的视图。

thread.sleep 无济于事 - 因为看起来 AccessibilityEventNodeInfo 更像是一个快照而不是对 UI 的实时访问?有什么办法吗?

这也是为什么事件源中缺少元素的解释。您在绘制动态元素之前收到事件。因此,启动了一个新的 Activity,并使用新内容进行了初始化,但是,应用程序可能会进入等待某种类型的网络/REST 资源的模式。在这些资源到来之前,事件被触发,然后不久之后新的内容被绘制。因此,在您看来,您获得了不完整的内容,但真正发生的是您在事件触发时获得了完整的内容。你的 thread.sleep 方法工作得很好。然而,在你睡眠之后,你不能检查 event.getSource() 的值,睡眠不会改变传递给这个函数的内容。相反,你想睡觉,然后爬取整个视图层次结构以找到您想要的信息。休眠然后使用

getRootInActiveWindow();

代替

event.getSource();

是否有任何线索可以帮助我从我看到的 AccessibilityEventNodeInfo 实例中确定滚动位置?

是和不是。不,无障碍服务无法检测滚动条的位置。但是,您可以通过执行以下操作来检测滚动事件的大致位置:

private float getScrollPosition(AccessibilityEvent event) {
    final AccessibilityRecordCompat record = new AccessibilityRecordCompat(event);
    final int itemCount = event.getItemCount();
    final int fromIndex = event.getFromIndex();

    // First, attempt to use (fromIndex / itemCount).
    if ((fromIndex >= 0) && (itemCount > 0)) {
        return (fromIndex / (float) itemCount);
    }

    final int scrollY = record.getScrollY();
    final int maxScrollY = record.getMaxScrollY();

    // Next, attempt to use (scrollY / maxScrollY). This will fail if the
    // getMaxScrollX() method is not available.
    if ((scrollY >= 0) && (maxScrollY > 0)) {
        return (scrollY / (float) maxScrollY);
    }

    // Finally, attempt to use (scrollY / itemCount).
    // TODO(alanv): Hack from previous versions -- is it still needed?
    if ((scrollY >= 0) && (itemCount > 0) && (scrollY <= itemCount)) {
        return (scrollY / (float) itemCount);
    }

    return 0.5f;
}

这直接来自 Google 的 TalkBack 代码。您可以在 EyesFree 项目的 ScrollFormatter.java 中找到它。这不是一个理想的解决方案。如果事件恰好来自大型布局(就像 chrome 中的网页经常发生的那样),那么对于大部分滚动,您最终会得到相同的结果。但是,API 不支持比这更精确的任何东西,因此如果您想知道大致的滚动位置,这是一个必要的技巧。我认为即使使用可用的 API,也可以对这种方法进行重大改进,尽管这需要一些工作。

于 2016-02-26T17:06:13.143 回答