ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

第 46 章:辅助功能

2026/9/1 7:17:35 拓冰建站 浏览量
第 46 章:辅助功能 46.1 辅助功能架构Android 的辅助功能框架是平台中最复杂的子系统之一。该框架为残障用户(包括视觉、运动、听觉以及认知障碍)提供交互能力,使其能够与设备上的任意应用交互,即便这些应用的开发者并未针对该场景做适配。整套架构围绕三大支柱构建:事件观测、内容内省、动作注入。从高层视角看,辅助功能框架连接三类参与对象:应用(View 与 ViewGroup):产生辅助功能事件,并将自身内容以 AccessibilityNodeInfo 对象树对外暴露。AccessibilityManagerService(AMS):集中式系统服务,负责事件路由、服务绑定管理以及安全策略执行。AccessibilityService:消费事件、检查视图树,并代用户执行操作。TalkBack、Switch Access、Voice Access 是应用最广泛的几个实现。46.1.1 高层数据流下图展示从视图状态变更,到辅助功能服务做出响应的完整核心数据流:46.1.2 三大核心类辅助功能框架围绕三个核心类展开,所有 AOSP 开发者都需要理解:AccessibilityManagerService是总协调者。定义路径:frameworks/base/services/accessibility/java/com/android/server/accessibility/ AccessibilityManagerService.java它运行在 system_server 内部,实现 IAccessibilityManager AIDL 接口。类的声明体现了它承担的众多职责:// AccessibilityManagerService.java,第256行 public class AccessibilityManagerService extends IAccessibilityManager.Stub implements AbstractAccessibilityServiceConnection.SystemSupport, AccessibilityUserState.ServiceInfoChangeListener, AccessibilityWindowManager.AccessibilityEventSender, AccessibilitySecurityPolicy.AccessibilityUserManager, SystemActionPerformer.SystemActionsChangedListener, SystemActionPerformer.DisplayUpdateCallBack, ProxyManager.SystemSupport {在 Android 17 中该文件约 7600 行,属于代码量较大的系统服务之一。AccessibilityService:所有辅助功能服务需要继承的抽象基类。定义路径:frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java服务运行在独立进程,通过 Binder 和 AMS 通信。每个服务在 XML 元数据文件中声明自身能力,仅接收与自身配置的事件类型、包过滤器相匹配的事件。AccessibilityNodeInfo:代表辅助功能树上的单个节点。定义路径:frameworks/base/core/java/android/view/accessibility/AccessibilityNodeInfo.javaAndroid 17 中该文件约 9200 行,是辅助功能框架中数据最丰富的数据结构,承载文本内容、边界、动作、集合信息、范围信息以及树关系等信息。46.1.3 组件架构图46.1.4 详解 AccessibilityNodeInfoAndroid UI 层级中的每一个 View 都可以生成一份自身的 AccessibilityNodeInfo 快照。辅助功能服务查询窗口内容时,拿到的就是这份快照。节点携带大量信息:表格属性分类示例标识viewIdResourceName、className、packageName文本text、contentDescription、hintText、tooltipText状态isChecked、isEnabled、isFocused、isSelected、isPassword几何信息boundsInScreen、boundsInParent、boundsInWindow树结构parentNodeId、childNodeIds、labeledBy、labelFor动作AccessibilityAction 列表(点击、长按、滚动等)集合信息用于列表 / 网格的 CollectionInfo、CollectionItemInfo范围信息用于拖动条、进度条的 RangeInfo扩展数据用于自定义键值对的 Bundle 附加数据节点 ID 使用 64 位数值,由两个 32 位 ID 组合而成:// AccessibilityNodeInfo.java public static final long UNDEFINED_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID); public static final long ROOT_NODE_ID = makeNodeId(ROOT_ITEM_ID, AccessibilityNodeProvider.HOST_VIEW_ID);makeNodeId函数将 View ID 和虚拟后代 ID 打包为一个 long 类型。这套机制支撑 AccessibilityNodeProvider,允许单个 View 对外暴露为一棵由多个虚拟节点组成的树,对于绘制多个可交互元素的自定义 View 至关重要。46.1.5 AccessibilityNodeInfo 动作AccessibilityNodeInfo 的动作系统允许辅助功能服务与 UI 交互。为兼容旧版本,标准动作同时提供位掩码常量,新 API 使用 AccessibilityAction 对象:// AccessibilityNodeInfo.java -- 遗留动作常量 public static final int ACTION_FOCUS = 1; // 1 0 public static final int ACTION_CLICK = 1 4; // 0x00000010 public static final int ACTION_LONG_CLICK = 1 5; // 0x00000020 public static final int ACTION_SELECT = 1 2; // 0x00000004 public static final int ACTION_SCROLL_FORWARD = 1 12; public static final int ACTION_SCROLL_BACKWARD = 1 13; public static final int ACTION_SET_TEXT = 1 21;AccessibilityAction 类封装动作 ID 与可选标签,支持向服务暴露自定义动作,与标准动作共存。46.1.6 预取策略现代 Android 为辅助功能节点遍历提供完善的预取策略,通过 AccessibilityNodeInfo 的标志位进行声明:public static final int FLAG_PREFETCH_ANCESTORS = 1; public static final int FLAG_PREFETCH_SIBLINGS = 1 1; public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 1 2; public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 1 3; public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 1 4;混合模式会先预取根节点的子节点,再递归遍历,在延迟和完整性之间取得良好平衡。深度优先、广度优先模式二者互斥,同时也与混合模式互斥;组合不兼容策略会抛出 IllegalArgumentException。46.2 AccessibilityManagerServiceAccessibilityManagerService(AMS)是 Android 辅助功能子系统的核心。它是运行在 system_server 进程的系统服务。系统启动时由SystemServer.startOtherServices()启动,注册名称为Context.ACCESSIBILITY_SERVICE。源码路径:frameworks/base/services/accessibility/java/com/android/server/accessibility/ AccessibilityManagerService.java46.2.1 职责AMS 有七项主要职责:事件分发:接收来自应用的 AccessibilityEvent,并路由至已绑定的辅助功能服务。服务生命周期:绑定、管理、解绑辅助功能服务。安全强制校验:保证事件仅分发到授权服务,服务仅能访问被允许查看的内容。窗口管理:维护辅助功能窗口树,是窗口管理器窗口列表的并行数据结构。输入过滤:开启触摸探索、放大等特性时,在输入流水线安装 AccessibilityInputFilter。放大协调:管理 MagnificationController,处理全屏放大与窗口放大。用户状态管理:维护每个用户的辅助功能偏好、已启用服务、快捷方式配置。46.2.2 主要内部组件AMS 将工作委托给一系列协作类:AccessibilitySecurityPolicy(约 800 行)是守门人,负责判断:事件是否可以分发到指定服务服务是否可以获取窗口内容跨用户配置文件事件应该上报哪个包名非辅助工具类服务是否需要弹出警告安全策略维护一组事件类型位掩码,对应事件需要保留源 AccessibilityNodeInfo:// AccessibilitySecurityPolicy.java,第70行 private static final int KEEP_SOURCE_EVENT_TYPES = AccessibilityEvent.TYPE_VIEW_CLICKED | AccessibilityEvent.TYPE_VIEW_FOCUSED | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_WINDOWS_CHANGED | AccessibilityEvent.TYPE_VIEW_SELECTED | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED | AccessibilityEvent.TYPE_VIEW_SCROLLED | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY | AccessibilityEvent.TYPE_VIEW_TARGETED_BY_SCROLL;不在该位掩码中的事件,在投递至服务前会剥离源节点,防止未授权的内容爬取。AccessibilityWindowManager 维护辅助功能窗口树,跟踪:全局交互连接(跨用户窗口)单用户交互连接活跃窗口与辅助功能焦点窗口画中画窗口AccessibilityUserState 保存单用户配置:已绑定与正在绑定的服务列表(mBoundServices)已启用服务组件名各类快捷方式分配放大模式偏好软键盘显示模式46.2.3 事件分发流水线事件分发流水线是辅助功能框架对性能最敏感的路径。下面跟踪一个事件从产生到投递的完整流程。步骤 1:事件产生View 调用sendAccessibilityEvent()或者sendAccessibilityEventUnchecked()。事件沿着视图树向上传递,调用父 View 的requestSendAccessibilityEvent(),父视图可以修改或者拦截事件。步骤 2:跨进程投递事件传递到 ViewRootImpl,经由客户端 AccessibilityManager 通过 Binder 调用服务端 AMS:// AccessibilityManagerService.java,第1617行 public void sendAccessibilityEvent(AccessibilityEvent event, int userId) {步骤 3:安全校验AMS 解析调用用户,校验上报的包名,检查分发权限:// AccessibilityManagerService.java,第1647‑1653行 resolvedUserId = mSecurityPolicy .resolveCallingUserIdEnforcingPermissionsLocked(userId); event.setPackageName(mSecurityPolicy.resolveValidReportedPackageLocked( event.getPackageName(), UserHandle.getCallingAppId(), resolvedUserId, getCallingPid()));步骤 4:窗口状态更新对于影响窗口跟踪的事件(例如 TYPE_WINDOW_STATE_CHANGED),AMS 请求 WindowManager 重新计算辅助功能窗口:// AccessibilityManagerService.java,第1698行 wm.computeWindowsForAccessibility(displayId);步骤 5:分发到服务实际分发会两次调用notifyAccessibilityServicesDelayedLocked():一次给同步(交互)模式请求该事件的服务;一次给异步(观测)模式的服务。// AccessibilityManagerService.java,第1716行 private void dispatchAccessibilityEventLocked(AccessibilityEvent event) { if (mProxyManager.isProxyedDisplay(event.getDisplayId())) { mProxyManager.sendAccessibilityEventLocked(event); } else { notifyAccessibilityServicesDelayedLocked(event, false); notifyAccessibilityServicesDelayedLocked(event, true); } mUiAutomationManager.sendAccessibilityEventLocked(event); }步骤 6:通知输入过滤器如果安装了输入过滤器(用于触摸探索或放大),事件同样会转发给它:// AccessibilityManagerService.java,第1663行 if (mHasInputFilter mInputFilter != null) { mMainHandler.sendMessage(obtainMessage( AccessibilityManagerService::sendAccessibilityEventToInputFilter, this, AccessibilityEvent.obtain(event))); }流水线流程简图:46.2.4 AMS 初始化AccessibilityManagerService 的构造函数可以看到全部协作对象的创建:// AccessibilityManagerService.java,第642行 public AccessibilityManagerService(Context context) { super(PermissionEnforcer.fromContext(context)); mContext = context; mPowerManager = context.getSystemService(PowerManager.class); mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); mTraceManager = AccessibilityTraceManager.getInstance( mWindowManagerService.getAccessibilityController(), this, mLock); mMainHandler = new MainHandler(mContext.getMainLooper()); mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); mPackageManager = mContext.getPackageManager(); // 安全策略 + 窗口跟踪 mSecurityPolicy = new AccessibilitySecurityPolicy( policyWarningUIController, mContext, this, LocalServices.getService(PackageManagerInternal.class)); mA11yWindowManager = new AccessibilityWindowManager( mLock, mMainHandler, mWindowManagerService, this, mSecurityPolicy, this, mTraceManager); mA11yDisplayListener = new AccessibilityDisplayListener(...); // 放大功能 mMagnificationController = new MagnificationController( this, mLock, mContext, new MagnificationScaleProvider(mContext), Executors.newSingleThreadExecutor(), mContext.getMainLooper()); mMagnificationProcessor = new MagnificationProcessor(mMagnificationController); // 其他协作组件 mCaptioningManagerImpl = new CaptioningManagerImpl(mContext); mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext, mMainHandler, mUiAutomationManager, this); mFlashNotificationsController = new FlashNotificationsController(mContext); mUmi = LocalServices.getService(UserManagerInternal.class); mInputManager = context.getSystemService(InputManager.class); if (UserManager.isVisibleBackgroundUsersEnabled()) { mVisibleBgUserIds = new SparseBooleanArray(); mUmi.addUserVisibilityListener((u, v) - onUserVisibilityChanged(u, v)); } else { mVisibleBgUserIds = null; } // 听力设备通话路由通知控制器(由特性开关控制) if (com.android.settingslib.flags.Flags .hearingDevicesInputRoutingControl()) { mHearingDeviceNotificationController = new HearingDevicePhoneCallNotificationController(context); } else { mHearingDeviceNotificationController = null; } init(); }Android 17 的构造函数新增两个老版本不存在的协作对象:ProxyManager(用于代理虚拟显示的辅助功能,见 46.2.16)和 UserManagerInternal(mUmi),用于后台可见用户监听,以及高级保护模式用户限制校验(见 46.12)。注意 FullScreenMagnificationController 不再在这里直接创建,改为由 MagnificationController 延迟构造。init()阶段,AMS 注册广播接收器、注册辅助功能设置变更的内容观察者,注册可处理的键盘手势:// AccessibilityManagerService.java,第693行 private void init() { mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager); registerBroadcastReceivers(); mAccessibilityContentObserver = new AccessibilityContentObserver(mMainHandler); mAccessibilityContentObserver.register(mContext.getContentResolver()); ListInteger supportedGestures = new ArrayList(); if (enableColorInversionKeyGestures()) { supportedGestures.add( KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DISPLAY_COLOR_INVERSION); } if (enableSelectToSpeakKeyGestures()) { supportedGestures.add( KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK); } supportedGestures.add(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION); if (enableTalkbackKeyGestures()) { supportedGestures.add( KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SCREEN_READER); } supportedGestures.add(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS); if (enableA11yTopRowShortcut()) { supportedGestures.add( KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TOP_ROW_ACCESSIBILITY_KEY); } if (!supportedGestures.isEmpty()) { mInputManager.registerKeyGestureEventHandler( supportedGestures, mKeyGestureEventHandler); } disableAccessibilityMenuToMigrateIfNeeded(); }该初始化流程展示 AMS 在启动时如何连接输入系统、设置数据库和窗口管理器。对比 Android 16,两个关键手势:切换放大、切换 Voice Access,现在无条件注册,不再受特性开关控制,代表该键盘快捷键功能已经正式发布。早期版本控制它们的enableTalkbackAndMagnifierKeyGestures、enableVoiceAccessKeyGestures标记已经移除。剩余开关enableColorInversionKeyGestures、enableSelectToSpeakKeyGestures、enableTalkbackKeyGestures、enableA11yTopRowShortcut继续控制新增能力,包括 46.10 描述的顶行辅助功能按键。46.2.5 LocalService 接口AMS 通过 AccessibilityManagerInternal 对外暴露一套内部接口,供 system_server 内部其他系统服务调用:// AccessibilityManagerService.java,第479行 private static final class LocalServiceImpl extends AccessibilityManagerInternal { @Override public void setImeSessionEnabled( SparseArrayIAccessibilityInputMethodSession sessions, boolean enabled) { ... } @Override public void unbindInput() { ... } @Override public void bindInput() { ... } @Override public void createImeSession(ArraySetInteger ignoreSet) { ... } @Override public void startInput( IRemoteAccessibilityInputConnection connection, EditorInfo editorInfo, boolean restarting) { ... } @Override public void performSystemAction(int actionId) { ... } }该接口允许 InputMethodManagerService 和辅助功能服务协同管理输入法会话,同时允许其他系统服务通过辅助功能框架触发系统动作。46.2.6 WINDOW_STATE_CHANGED 事件延迟投递事件分发流水线一个重要细节是 TYPE_WINDOW_STATE_CHANGED 事件的延迟逻辑。当应用上报窗口状态变更,但该窗口还没有注册到辅助功能窗口列表(应用进程与 WindowManagerService 出现竞态),AMS 最多延迟该事件 500ms:// AccessibilityManagerService.java,第281行 private static final int POSTPONE_WINDOW_STATE_CHANGED_EVENT_TIMEOUT_MILLIS = 500;当收到 WINDOWS_CHANGE_ADDED 事件,AMS 检查是否存在匹配该新窗口的待延迟事件并进行分发:// AccessibilityManagerService.java,第5249行 public void sendAccessibilityEventForCurrentUserLocked(AccessibilityEvent event) { if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_ADDED) { sendPendingWindowStateChangedEventsForAvailableWindowLocked( event.getRealWindowId()); } sendAccessibilityEventLocked(event, mCurrentUserId); }注意 Android 17 使用event.getRealWindowId()做匹配,而不是逻辑窗口 ID,这点对画中画窗口(它的可见窗口 ID 会被重映射)至关重要。46.2.7 服务绑定辅助功能服务使用标准 AndroidbindService()机制绑定,有一条关键安全限制:只有声明android.permission.BIND_ACCESSIBILITY_SERVICE权限的服务才允许被绑定。绑定生命周期由 AccessibilityServiceConnection 管理:frameworks/base/services/accessibility/java/com/android/server/accessibility/ AccessibilityServiceConnection.java该类继承 AbstractAccessibilityServiceConnection,提供辅助功能服务与 UiAutomation 连接的公共行为。抽象基类实现 IAccessibilityServiceConnection.Stub,也就是服务端 Binder 端点,供服务侧调用。连接持有 AccessibilityUserState 的弱引用,避免循环引用,因为用户状态会维护已绑定服务列表:// AccessibilityServiceConnection.java,第98行 final WeakReferenceAccessibilityUserState mUserStateWeakReference;46.2.8 安全模型辅助功能服务拥有极高权限:读取屏幕内容、观测用户输入、注入操作,因此框架具备一套完备安全控制:权限要求:服务必须在清单声明android.permission.BIND_ACCESSIBILITY_SERVICE。用户显式授权:用户必须在设置中手动开启每一个服务,确认弹窗会提示该服务将获得的能力。事件过滤:AccessibilitySecurityPolicy.canDispatchAccessibilityEventLocked()判断事件是否可以下发给当前用户的服务。包名校验:校验上报包名,阻止恶意应用伪造来自其他包的事件。mSecurityPolicy.resolveValidReportedPackageLocked( event.getPackageName(), UserHandle.getCallingAppId(), resolvedUserId, getCallingPid());源信息剥离:不在 KEEP_SOURCE_EVENT_TYPES 集合中的事件,分发前移除源 AccessibilityNodeInfo,防止服务访问未授权内容。非工具类服务通知:元数据没有标记accessibilityTool="true"的服务会弹出常驻警告通知,由 PolicyWarningUIController 控制。frameworks/base/services/accessibility/java/com/android/server/accessibility/ PolicyWarningUIController.java增强确认模式 ECM:EnhancedConfirmationManager 为辅助功能服务激活增加额外校验,尤其针对侧载应用。AMS 在启用服务前会调用它(AccessibilityManagerService.java 第 5634 行)。用户隔离:每个用户拥有独立辅助功能状态,由 AccessibilityUserState 管理。配置文件父用户和托管配置文件共享辅助功能状态。高级保护模式 AAPM:Android 17 新增。设备所有者开启高级保护模式后,可以完全禁止非工具类辅助功能服务,详见 46.12。46.2.9 锁与线程模型AMS 使用一把锁(mLock)完成全部状态同步。需要在不持有锁的情况下执行的操作(例如向服务进程发起 Binder 调用)采用重同步模式:锁内复制需要的状态,释放锁之后再执行外部调用。AMS 在主 Handler 处理事件,保证顺序执行:// AccessibilityManagerService.java,第5258行 private void sendAccessibilityEventLocked(AccessibilityEvent event, int userId) { // 重同步,避免持锁向外调用 event.setEventTime(SystemClock.uptimeMillis()); mMainHandler.sendMessage(obtainMessage( AccessibilityManagerService::sendAccessibilityEvent, this, event, userId)); }保证事件分发、窗口状态更新、服务通知在主线程按确定顺序执行。46.2.10 AMS Shell 命令AMS 通过 AccessibilityShellCommand 对外提供 shell 调试命令:# 列出已启用的辅助功能服务 adb shell cmd accessibility get-enabled-services # 启用一个辅助功能服务 adb shell settings put secure enabled_accessibility_services \ com.google.android.marvin.talkback/\ com.google.android.marvin.talkback.TalkBackService # 查看触摸探索是否开启 adb shell settings get secure touch_exploration_enabled # 导出辅助功能全部状态 adb shell dumpsys accessibilitydumpsys accessibility调试价值极高。输出当前用户状态、全部绑定服务及其能力、辅助功能窗口列表、放大状态、输入过滤器状态。46.2.11 闪光灯通知FlashNotificationsController 为听障用户提供视觉提醒告警:frameworks/base/services/accessibility/java/com/android/server/accessibility/ FlashNotificationsController.java开启后,通知、闹钟或者其他提醒事件到来时,相机闪光灯或者屏幕会闪烁。控制器监听音频播放配置,将闹钟、通知声音映射为闪烁模式。闪烁原因枚举:AccessibilityManager.FLASH_REASON_ALARMAccessibilityManager.FLASH_REASON_PREVIEW配置项为Settings.System.CAMERA_FLASH_NOTIFICATION和Settings.System.SCREEN_FLASH_NOTIFICATION。46.2.12 FingerprintGestureDispatcher对于后置指纹传感器设备,辅助功能服务可以捕获传感器上的滑动手势:frameworks/base/services/accessibility/java/com/android/server/accessibility/ FingerprintGestureDispatcher.java分发器向指纹 HAL 注册,将手势事件路由给声明flagRequestFingerprintGestures的服务。// FingerprintGestureDispatcher.java,第36行 public class FingerprintGestureDispatcher extends IFingerprintClientActiveCallback.Stub implements Handler.Callback {