上次写到 UiAutomator2 封装 Accessibility 接口时提到了 Root Node,不明白的可以先看这一篇: 。通过根节点的方式遍历整个 Window 的控件树从而寻找要查找的控件。那有时就会遇到异常的情况:明明截图中看得到控件,但控件查找时却找不到它,问题会出在哪里?今天继续从源码角度来分析一下,作为 Root Node 的载体,AccessibilityWindowInfo 是怎么创建的
我们从 UiDevice# getWindows 方法开始探索
1 2 3 4 5
| // UiDevice# getWindows ...
List<AccessibilityWindowInfo> windows = getUiAutomation().getWindows(); ...
|
调用了 UiAutomation 的方法,而 UiAutomation 又是调的 AccessibilityInteractionClient 的方法,然后远程调用系统服务 AccessibilityManagerService 的 getWindows 方法
1 2 3 4 5 6 7 8 9 10
| // AccessibilityManagerService$ Service# getWindows
final int windowCount = getSecurityPolicy(displayId).mWindows.size(); for (int i = 0; i < windowCount; i++) { AccessibilityWindowInfo window = getSecurityPolicy(displayId).mWindows.get(i); AccessibilityWindowInfo windowClone = AccessibilityWindowInfo.obtain(window); windowClone.setConnectionId(mId); windows.add(windowClone); }
|
这段代码中关键是 getSecurityPolicy(displayId) 的成员 mWindows,而 get 方法拿到的是 AccessibilityManagerService 的内部类 SecurityPolicy mWindows 是其一个 List
1 2 3 4 5
| // AccessibilityManagerService$ SecurityPolicy
// In Z order public List<AccessibilityWindowInfo> mWindows;
|
注释中 Z order 的意思是,这里存放的 AccessibilityWindowInfo 是三维空间中 Z 轴上的 Window,也即层叠起来的 Window,类似于 Photoshop 作图里的图层
搜索一下 mWindows.add,发现只有一处,并且属于方法 updateWindowsLocked(List windows)。所以得出结论,AccessibilityWindowInfo 是通过某处代码调用 SecurityPolicy# updateWindowsLocked 创建出来的
我们注意到这个 updateWindowsLocked 方法的参数泛型是 WindowInfo,那么它是如何转化为 AccessibilityWindowInfo 的呢?继续分析
1 2 3 4 5 6 7 8
| // AccessibilityManagerService$ SecurityPolicy# updateWindowsLocked
...
AccessibilityWindowInfo window = (mWindowsForAccessibilityCallback != null) ? mWindowsForAccessibilityCallback.populateReportedWindow(windowInfo) : null; ...
|
所以必须得先设置 mWindowsForAccessibilityCallback 才能够进行创建的动作,这个 callback 是 AccessibilityManagerService 的内部类 WindowsForAccessibilityCallback
,搜索一下它创建的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| // AccessibilityManagerService
private void updateWindowsForAccessibilityCallbackLocked(UserState userState) {
List<Service> boundServices = userState.mBoundServices; final int boundServiceCount = boundServices.size(); for (int i = 0; i < boundServiceCount; i++) { Service boundService = boundServices.get(i); if (boundService.canRetrieveInteractiveWindowsLocked()) { if (mWindowsForAccessibilityCallback == null) { mWindowsForAccessibilityCallback = new WindowsForAccessibilityCallback(); mWindowManagerService.setWindowsForAccessibilityCallback( mWindowsForAccessibilityCallback); } return; } } ... }
|
这个方法比较关键,首先 UserState 需要添加了 mBoundServices;其次 boundService 必须支持 canRetrieveInteractiveWindows 条件才能够创建 WindowsForAccessibilityCallback
关于内部类 UserState 如何满足上述条件,后续的文章再分析。现在假设满足了这个条件,那么看看方法 populateReportedWindow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| // AccessibilityManagerService$ WindowsForAccessibilityCallback# populateReportedWindow
...
AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain();
reportedWindow.setId(windowId); reportedWindow.setType(getTypeForWindowManagerWindowType(window.type)); reportedWindow.setLayer(window.layer); reportedWindow.setFocused(window.focused); reportedWindow.setBoundsInScreen(window.boundsInScreen); reportedWindow.setTitle(window.title); reportedWindow.setAnchorId(window.accessibilityIdOfAnchor); reportedWindow.setPictureInPicture(window.inPictureInPicture); reportedWindow.setDisplayId(window.displayId);
...
|
代码中可以看到,WindowInfo 的信息直接设置到了 AccessibilityWindowInfo 中
接下来的代码中就会执行到 mWindows.add(window) 将 AccessibilityWindowInfo 添加到 List 中。中间有一段对 Active Window 的设置要说一下。有两段
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| // method updateWindowsLocked // 第一段
boolean activeWindowGone = true;
if (window.isFocused()) { mFocusedWindowId = windowId; if (!mTouchInteractionInProgress) { mActiveWindowId = windowId; window.setActive(true); } else if (windowId == mActiveWindowId) { activeWindowGone = false; } }
|
一个获取了焦点的窗口,并且用户并没有在触摸屏幕(!mTouchInteractionInProgress),则设置该窗口为 Active Window
一个获取了焦点的窗口,并且已经设置为 Active Window,若用户正在触摸屏幕,则不会撤销它 Active Window 的设置(activeWindowGone = false
这里正在触摸屏幕可以理解为系统收到了 MotionEvent.ACTION_DOWN 事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| // updateWindowsLocked // 第二段
if (mTouchInteractionInProgress && activeWindowGone) { mActiveWindowId = mFocusedWindowId; }
final int accessibilityWindowCount = mWindows.size(); for (int i = 0; i < accessibilityWindowCount; i++) { AccessibilityWindowInfo window = mWindows.get(i); if (window.getId() == mActiveWindowId) { window.setActive(true); } if (window.getId() == mAccessibilityFocusedWindowId) { window.setAccessibilityFocused(true); } }
|
如果当前窗口不是 Active 窗口且用户正在触摸屏幕,则把焦点窗口设置为 Active 窗口
这一串的骚操作,目的就是为了设置 Active Window 供 UiAutomation# getRootInActiveWindow 使用
今天关于 AccessibilityWindowInfo 的创建做了介绍,了解了它是对应于 WindowInfo,并且在什么条件下的窗口能够成为 Active 窗口的内容。另外,如何将窗口更新信息发送出去,窗口在何时更新的内容,以后的文章会继续介绍