You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android多屏场景下如何访问副屏AccessibilityNode并实现点击

问题描述

我正在开发一款支持双屏的Android AccessibilityService,需求是:

  • 主屏设置可拖动触摸区域(类似触控栏),用户拖动该区域时,副屏上的光标随之移动
  • 副屏运行独立应用,不使用Presentation模式

目前已实现的功能:
通过拥有android.permission.SYSTEM_ALERT_WINDOW权限(已通过Settings.canDrawOverlays()验证)的Service,完成了主屏触控区域绘制、副屏光标绘制,拖动触控栏时光标同步移动,且能实时获取副屏光标的坐标。

当前待解决问题:
需要实现点击交互——用户点击主屏触控栏时,触发副屏对应位置的点击。计划借助AccessibilityService实现,但无法访问副屏的AccessibilityNode:

  • rootInActiveWindow仅返回当前活跃屏幕(始终为主屏)的根节点
  • windowsOnAllDisplays始终返回空列表,无法获取副屏窗口

相关代码如下:

private fun click(action: Int = AccessibilityNodeInfo.ACTION_CLICK) {
    val nodeInfo = if(displayManager.displays.size > 1){
        windowsOnAllDisplays.get(displayManager.displays[1].displayId, emptyList()).firstOrNull()?.root ?: rootInActiveWindow
    } else {
        rootInActiveWindow
    } ?: return
    val nearestNodeToMouse = findSmallestNodeAtPoint(nodeInfo, cursorLayout.x, cursorLayout.y)
    if (nearestNodeToMouse != null) {
        logNodeHierachy(nearestNodeToMouse, 0)
        nearestNodeToMouse.performAction(action)
    }
    nodeInfo.recycle()
}

private fun findSmallestNodeAtPoint(sourceNode: AccessibilityNodeInfo, x: Int, y: Int): AccessibilityNodeInfo? {
    val bounds = Rect()
    sourceNode.getBoundsInScreen(bounds)
    if (!bounds.contains(x, y)) {
        return null
    }
    for (i in 0 until sourceNode.childCount) {
        val child = sourceNode.getChild(i)
        if(child != null) {
            val nearestSmaller = findSmallestNodeAtPoint(child, x, y)
            if (nearestSmaller != null) {
                return nearestSmaller
            }
        }
    }
    return sourceNode
}
可行解决方案

方案1:利用AccessibilityService的getWindows()结合坐标转换

Android的AccessibilityNodeInfo坐标基于全局屏幕坐标系,可以尝试:

  1. 调用getWindows()获取所有窗口,遍历窗口通过AccessibilityWindowInfo.getDisplayId()匹配副屏的displayId
  2. 获取副屏的显示区域(Display.getBounds()),将副屏内的光标坐标转换为全局坐标系坐标:
    全局X = 副屏左上角X + 光标在副屏的X坐标
    全局Y = 副屏左上角Y + 光标在副屏的Y坐标
  3. 用转换后的全局坐标调用findSmallestNodeAtPoint查找副屏窗口根节点下的对应控件

注意:需确保AccessibilityService配置中声明了TYPE_WINDOW_STATE_CHANGED等必要事件类型,且该方式在Android 10+设备上兼容性更好。

方案2:模拟系统触摸事件(需ROOT/系统签名)

如果无法通过AccessibilityNode实现,可直接向副屏注入触摸事件:

val inputManager = getSystemService(INPUT_SERVICE) as InputManager
val displayId = displayManager.displays[1].displayId

// 构造按下事件
val downEvent = MotionEvent.obtain(
    SystemClock.uptimeMillis(),
    SystemClock.uptimeMillis(),
    MotionEvent.ACTION_DOWN,
    cursorXInDisplay, // 光标在副屏内的X坐标
    cursorYInDisplay, // 光标在副屏内的Y坐标
    0
)
downEvent.displayId = displayId
inputManager.injectInputEvent(downEvent, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH)

// 构造抬起事件
val upEvent = MotionEvent.obtain(
    SystemClock.uptimeMillis(),
    SystemClock.uptimeMillis(),
    MotionEvent.ACTION_UP,
    cursorXInDisplay,
    cursorYInDisplay,
    0
)
upEvent.displayId = displayId
inputManager.injectInputEvent(upEvent, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH)

downEvent.recycle()
upEvent.recycle()

限制:普通应用无法直接调用injectInputEvent,需要ROOT权限或应用拥有系统签名,部分厂商可能限制该接口使用。

方案3:副屏应用主动配合(跨进程通信)

如果副屏运行的是自研应用,可通过IPC实现点击:

  1. 主屏AccessibilityService检测到点击后,通过Messenger、AIDL或ContentProvider将光标坐标发送给副屏应用
  2. 副屏应用接收坐标后,通过View.hitTest()找到对应控件,直接调用performClick()触发交互

该方案无需特殊权限,稳定性最高,但前提是副屏应用处于可控状态。


内容的提问来源于stack exchange,提问作者Tomislav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 05:03:31