Android 15下Accessibility Service获取可见按钮BoundsInScreen超出屏幕高度的解决咨询
我理解你在Android 15设备上遇到的这个困扰——明明按钮在屏幕上可见,但通过getBoundsInScreen()拿到的坐标却超出了屏幕高度,而且Android 14及以下都正常,这确实很棘手。结合你提到的手势导航、系统对话框场景,我来分享几个可能的排查和解决方向:
1. 先确认屏幕尺寸的正确获取方式
你当前用getCurrentWindowMetrics()拿到的是当前应用窗口的尺寸,但当系统对话框弹出时,对话框是一个独立的顶层窗口,这时候应用窗口的尺寸可能不是整个屏幕的真实尺寸。你需要获取物理屏幕的完整尺寸,而不是应用窗口的:
// Android 11+ 获取物理屏幕的真实尺寸 WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); // 方式1:使用getMaximumWindowMetrics获取屏幕最大可用范围 WindowMetrics maxMetrics = windowManager.getMaximumWindowMetrics(); Rect realScreenBounds = maxMetrics.getBounds(); int realScreenHeight = realScreenBounds.height(); // 方式2:兼容旧API的真实尺寸获取(Android 17+弃用,但可用于对比) Display display = windowManager.getDefaultDisplay(); DisplayMetrics realDisplayMetrics = new DisplayMetrics(); display.getRealMetrics(realDisplayMetrics); int realScreenHeight2 = realDisplayMetrics.heightPixels;
对比这两个值和你之前拿到的screenHeight,就能知道是否是屏幕尺寸获取的问题。
2. 针对系统对话框,通过AccessibilityWindowInfo获取窗口上下文
系统对话框属于独立的窗口,你可以通过AccessibilityService的getWindows()方法拿到窗口列表,定位到对话框窗口后,再转换节点坐标:
List<AccessibilityWindowInfo> windowList = getWindows(); for (AccessibilityWindowInfo window : windowList) { // 筛选系统对话框窗口 if (window.isActive() && window.getType() == AccessibilityWindowInfo.TYPE_SYSTEM_DIALOG) { Rect windowScreenBounds = new Rect(); window.getBoundsInScreen(windowScreenBounds); // 获取按钮节点的屏幕坐标 Rect nodeScreenBounds = new Rect(); aniChild.getBoundsInScreen(nodeScreenBounds); // 转换为相对于对话框窗口的坐标 Rect nodeWindowBounds = new Rect( nodeScreenBounds.left - windowScreenBounds.left, nodeScreenBounds.top - windowScreenBounds.top, nodeScreenBounds.right - windowScreenBounds.left, nodeScreenBounds.bottom - windowScreenBounds.top ); // 此时nodeWindowBounds的范围应该在对话框窗口的0到windowScreenBounds.height()之间 Log.d("Node in dialog", "Relative bounds: " + nodeWindowBounds.toShortString()); } }
这个方法可以帮你排除应用窗口和对话框窗口的坐标混淆问题。
3. 尝试切换使用getBoundsInWindow()替代getBoundsInScreen()
getBoundsInScreen()是相对于整个物理屏幕的坐标,而getBoundsInWindow()是相对于节点所在窗口的内部坐标。在系统对话框场景下,窗口本身可能已经是全屏可用区域,所以这个坐标可能更接近你需要的真实可见位置:
Rect nodeWindowBounds = new Rect(); aniChild.getBoundsInWindow(nodeWindowBounds); Log.d("Node window bounds", nodeWindowBounds.toShortString());
如果这个值在屏幕高度范围内,那直接用这个就可以了;如果需要转换为屏幕坐标,再结合窗口的getBoundsInScreen()结果进行偏移计算。
4. 重新检查Insets的处理逻辑
你之前用了getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()),但在手势导航场景下,应该关注系统手势区域的Insets,而不是系统栏:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { WindowMetrics windowMetrics = windowManager.getCurrentWindowMetrics(); Insets gestureInsets = windowMetrics.getWindowInsets() .getInsets(WindowInsets.Type.systemGestures()); // 可用高度 = 窗口高度 - 底部手势区域高度 int usableHeight = windowMetrics.getBounds().height() - gestureInsets.bottom; Log.d("Usable height with gestures", String.valueOf(usableHeight)); }
另外,Android 15可能对Insets的计算逻辑有调整,建议同时打印出gestureInsets、systemBarsInsets的各个方向值,对比后找到坐标偏移的来源。
5. 验证坐标偏移规律
既然你发现按钮是可见的,那getBoundsInScreen()返回的坐标和真实可见坐标之间应该有一个固定的差值。你可以在测试中打印出以下值,找到这个偏移量:
- 物理屏幕真实高度
- 按钮的
getBoundsInScreen().bottom - 按钮的可见位置(比如手动观察的底部距离屏幕底部的像素值)
通过对比这几个值,就能算出需要减去的偏移量,临时解决问题的同时,也能定位到根源。
内容来源于stack exchange




