Android AccessibilityService:如何记录和重放视图点击?

Android AccessibilityService:如何记录和重放视图点击?,android,accessibility,Android,Accessibility,我正在尝试实现一个AccessibilityService,该服务记录用户的操作(此时仅单击事件)并存储它们,以便可以在稍后的时间点重播它们 为此,我注册了AccessibilityEvent。键入\u VIEW\u CLICKED事件,并检查我是否可以稍后通过使用两种策略恢复它们(当我所拥有的只是对活动的窗口/根节点的引用时): 获取单击视图的id并在根节点的树中查找该id 获取单击视图的文本,并在根节点的树中查找该文本 我已经在各种应用程序和Android系统的不同部分进行了测试,结果非常混

我正在尝试实现一个AccessibilityService,该服务记录用户的操作(此时仅单击事件)并存储它们,以便可以在稍后的时间点重播它们

为此,我注册了
AccessibilityEvent。键入\u VIEW\u CLICKED
事件,并检查我是否可以稍后通过使用两种策略恢复它们(当我所拥有的只是对活动的窗口/根节点的引用时):

  • 获取单击视图的id并在根节点的树中查找该id
  • 获取单击视图的文本,并在根节点的树中查找该文本
  • 我已经在各种应用程序和Android系统的不同部分进行了测试,结果非常混乱。两种策略中的任何一种都无法恢复大约一半的视图,有些视图有时被报告为可恢复,有时则不可恢复。我发现后者是由于竞争条件造成的,因为可访问性服务运行在与单击的视图所属的应用程序不同的进程中

    我现在的问题是,是否有更好的方法在可访问性中获取视图的句柄,并在应用程序的后续执行中再次找到该视图

    下面是我的AccessiblityService类的代码:

    public class RecorderService extends AccessibilityService {
    
        private static final String TAG = "RecorderService";
    
        @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
    
            switch (event.getEventType()) {
            case AccessibilityEvent.TYPE_VIEW_CLICKED:
                AccessibilityNodeInfo node = event.getSource();
    
                if (node == null) {
                    Log.i(TAG, "node is null");
                    return;
                }
    
                AccessibilityNodeInfo root = getRootInActiveWindow();
                if (root == null) {
                    Log.i(TAG, "root is null");
                    return;
                }
    
                // Strategy #1: locate node via its id
                String id = node.getViewIdResourceName();
                if (id == null) {
                    Log.i(TAG, "id is null");
                } else {
    
                    List<AccessibilityNodeInfo> rootNodes = root.findAccessibilityNodeInfosByViewId(id);
                    if (rootNodes.size() == 1) {
                        Log.i(TAG, "success (via id)");
                        return;
                    } else {
                        Log.i(TAG, "multiple nodes with that id");
                    }
                }
    
                // Strategy #2: locate node via its text
                CharSequence text = node.getText();
                if (text == null) {
                    Log.i(TAG, "text is null");
                } else {
                    List<AccessibilityNodeInfo> rootNodes = root.findAccessibilityNodeInfosByText(text.toString());
                    if (rootNodes.size() == 1) {
                        Log.i(TAG, "success (via text)");
                        return;
                    }
                }
    
                Log.i(TAG, "failed, node was not recoverable");
            }
        }
    
        @Override
        protected boolean onKeyEvent(KeyEvent event) {
            Log.i("Key", event.getKeyCode() + "");
    
            return true;
            // return super.onKeyEvent(event);
        }
    
        @Override
        public void onInterrupt() {
        }
    }
    
    公共类记录器服务扩展了AccessibilityService{
    私有静态最终字符串TAG=“RecorderService”;
    @凌驾
    AccessibilityEvent上的公共无效(AccessibilityEvent事件){
    开关(event.getEventType()){
    案例可访问性事件类型\视图\单击:
    AccessibilityNodeInfo节点=event.getSource();
    if(node==null){
    Log.i(标记“node is null”);
    返回;
    }
    AccessibilityNodeInfo root=getRootInActiveWindow();
    if(root==null){
    Log.i(标记“root为null”);
    返回;
    }
    //策略#1:通过其id定位节点
    String id=node.getViewIdResourceName();
    if(id==null){
    Log.i(标记“id为null”);
    }否则{
    List rootNodes=root.findAccessibilityNodeInfosByViewId(id);
    if(rootNodes.size()==1){
    Log.i(标记“成功(通过id)”);
    返回;
    }否则{
    i(标记“具有该id的多个节点”);
    }
    }
    //策略2:通过文本定位节点
    CharSequence text=node.getText();
    if(text==null){
    Log.i(标记“文本为空”);
    }否则{
    List rootNodes=root.findAccessibilityNodeInfosByText(text.toString());
    if(rootNodes.size()==1){
    Log.i(标记“成功(通过文本)”);
    返回;
    }
    }
    Log.i(标记“失败,节点不可恢复”);
    }
    }
    @凌驾
    受保护的布尔onKeyEvent(KeyEvent事件){
    Log.i(“Key”,event.getKeyCode()+”);
    返回true;
    //返回super.onKeyEvent(事件);
    }
    @凌驾
    在中断时的公共无效(){
    }
    }
    

    我在SDK版本21(棒棒糖)上开发了这个软件,并在HTC Nexus M8和三星Galaxy Note2上进行了测试,两者都显示了类似的结果。

    是的,这差不多是你所能做到的。您也可以尝试使用祖先视图ID和子索引作为唯一标识符,尽管这对ListView之类的东西没有多大帮助(尽管在那里您可以依赖CollectionItemInfo)。我尝试在源节点上使用getParent()并结合getChild(…)来检查子节点是否可以通过父节点恢复,但是后者经常返回null,我不明白为什么。