系列目录:第一篇:全景图与架构概览 | 第二篇:AccessibilityManagerService 启动与初始化 | 第三篇:无障碍服务的注册与绑定 | 第四篇:AccessibilityEvent 的产生与投递 | 第五篇:AccessibilityNodeInfo 的构建与查询 | 第六篇:输入事件拦截与 TouchExplorer | 第七篇:手势分发与 KeyEvent 处理 | 第八篇:放大镜与辅助功能特性
上一篇我们分析了 AMS 的启动与初始化过程,初始化完成时mBoundServices还是空的,没有任何无障碍服务被绑定。本篇将完整梳理一个无障碍服务从"在 Manifest 中声明"到"被 AMS 绑定并能够接收回调"的全过程,包括 AccessibilityServiceInfo 的解析、服务启用的触发链路、以及bindServicesLocked()的核心绑定逻辑。
一、Manifest 声明:一个无障碍服务的"入场券"
一个 App 要成为系统级的无障碍服务,必须在AndroidManifest.xml中满足三个条件:
源码路径:开发者工程的AndroidManifest.xml
<manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.example.accessibility"><application><!-- 1. 声明 Service --><serviceandroid:name=".MyAccessibilityService"android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"android:exported="true"><!-- 2. intent-filter 指定 action --><intent-filter><actionandroid:name="android.accessibilityservice.AccessibilityService"/></intent-filter><!-- 3. meta-data 指向服务配置文件 --><meta-dataandroid:name="android.accessibilityservice"android:resource="@xml/accessibility_service_config"/></service></application></manifest>这三项缺一不可,每一项都有明确的系统级校验:
| 条件 | 作用 | 校验位置 |
|---|---|---|
android.permission.BIND_ACCESSIBILITY_SERVICE | 限定只有系统签名的 App 才能绑定该 Service。普通 App 无法声明此权限,绑定时 AMS 会校验调用方是否持有此权限 | AMSbindServiceLocked() |
android.accessibilityservice.AccessibilityServiceaction | 系统用此 action 筛选哪些 Service 是无障碍服务。AMS 调用PackageManager.queryIntentServices()时使用此 Intent | AMSgetInstalledAccessibilityServiceList() |
meta-data指向配置 XML | 提供AccessibilityServiceInfo的配置来源(capabilities、feedbackType、flags 等) | AMS 解析 XML 构建AccessibilityServiceInfo |
其中BIND_ACCESSIBILITY_SERVICE是一个 signature-level 权限,定义在 framework-res 中:
<!-- frameworks/base/core/res/AndroidManifest.xml --><permissionandroid:name="android.permission.BIND_ACCESSIBILITY_SERVICE"android:protectionLevel="signature"/>这意味着只有与系统使用相同签名的 App 才能声明此权限。普通第三方 App 的签名与系统签名不同,系统在 bindService 时会校验这一权限,不匹配则拒绝绑定。这个安全机制确保无障碍服务无法被恶意 App 伪装。
二、AccessibilityServiceInfo 配置解析
meta-data指向的 XML 文件定义了无障碍服务的能力声明,系统在绑定前需要解析这些配置。
2.1 配置 XML 示例
源码路径:开发者工程的res/xml/accessibility_service_config.xml
<accessibility-servicexmlns:android="http://schemas.android.com/apk/res/android"android:accessibilityEventTypes="typeAllMask"android:accessibilityFeedbackType="feedbackGeneric"android:accessibilityFlags="flagDefault| flagRequestTouchExplorationMode| flagRequestFilterKeyEvents"android:canRequestEnhancedWebAccessibility="true"android:canRetrieveWindowContent="true"android:notificationTimeout="100"android:description="@string/accessibility_service_description"/>2.2 配置项与 AccessibilityServiceInfo 字段映射
解析发生在 AMS 调用PackageManager.getServiceInfo()获取ServiceInfo之后,通过meta-data读取 XML 属性并填充到AccessibilityServiceInfo对象中。
源码路径:frameworks/base/services/core/java/com/android/server/accessibility/AccessibilityManagerService.java
privateAccessibilityServiceInfoparseServiceInfoLocked(ResolveInforesolveInfo,UserStateuserState){ServiceInfoserviceInfo=resolveInfo.serviceInfo;StringpackageName=serviceInfo.packageName;StringclassName=serviceInfo.name;AccessibilityServiceInfoinfo;try{info=newAccessibilityServiceInfo(resolveInfo,mContext);// 构造函数内部会解析 meta-data XML 并填充字段}catch(XmlPullParserException|IOExceptione){returnnull;}returninfo;}AccessibilityServiceInfo的构造函数读取 XML 中各属性,核心字段映射如下:
| XML 属性 | AccessibilityServiceInfo 字段 | 含义 |
|---|---|---|
accessibilityEventTypes | eventTypes | 要监听的事件类型位掩码(如TYPE_VIEW_CLICKED、TYPE_WINDOW_STATE_CHANGED等) |
accessibilityFeedbackType | feedbackType | 反馈类型(语音、触觉、通用等) |
accessibilityFlags | flags | 功能标志,见下文详述 |
notificationTimeout | notificationTimeout | 事件通知的最小间隔(毫秒),避免高频事件冲击 |
canRetrieveWindowContent | 对应CAN_RETRIEVE_WINDOW_CONTENTcapability flag | 是否可查询窗口内容节点树 |
canRequestEnhancedWebAccessibility | 对应CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITYflag | 是否可请求增强的 WebView 无障碍支持 |
description | description | 服务描述,展示给用户(在 Settings 中) |
2.3 accessibilityFlags 详解
flags字段决定了服务的行为模式,在无障碍框架中是最关键的配置之一:
// 源码路径:frameworks/base/core/java/android/accessibilityservice/AccessibilityServiceInfo.java// 默认标志:服务仅接收 AccessibilityEvent,不请求任何特殊能力publicstaticfinalintFLAG_DEFAULT=0;// 请求过滤按键事件 → AMS 会将 KeyEvent 先交给此服务处理publicstaticfinalintFLAG_REQUEST_FILTER_KEY_EVENTS=0x00000004;// 请求触摸探索模式 → AMS 会注册 InputFilter 并启用 TouchExplorerpublicstaticfinalintFLAG_REQUEST_TOUCH_EXPLORATION_MODE=0x00000020;// 请求增强 Web 无障碍 → 允许服务向 WebView 注入脚本publicstaticfinalintFLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY=0x00000010;// 请求过滤指纹手势publicstaticfinalintFLAG_REQUEST_FINGERPRINT_GESTURES=0x00000100;// 服务开启后,请求显示无障碍按钮publicstaticfinalintFLAG_REQUEST_ACCESSIBILITY_BUTTON=0x00000200;notificationTimeout也是值得关注的字段。它控制两个连续事件之间的最小发送间隔。例如设置为 100ms 时,如果 View 层在 100ms 内连续发送了多个事件,AMS 会将它们合并,在延时到期时一次性分发给服务。这避免了快速滑动列表时产生大量无意义的中间事件。
三、服务启用的触发路径
一个无障碍服务并不会因为 Manifest 声明了就会自动运行。它需要用户在"设置 → 无障碍"中手动开启。这个开启动作如何传递到 AMS 并触发服务绑定,是本节的核心内容。
3.1 Settings App 写入配置
当用户在 Settings 中开启某个无障碍服务时,Settings App(通常是Settings.apk或厂商定制版)会执行以下操作:
用户点击开关 → Settings App 将目标服务的 ComponentName 追加到 ENABLED_ACCESSIBILITY_SERVICES → Settings.Secure.putString(ENABLED_ACCESSIBILITY_SERVICES, "com.a/.SvcA:com.b/.SvcB") → Settings.Secure.putInt(ACCESSIBILITY_ENABLED, 1) // 如果之前是关闭状态ENABLED_ACCESSIBILITY_SERVICES的值是一个以冒号(:)分隔的 ComponentName 字符串,例如:
"com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService"当同时启用多个服务时:
"com.a/.SvcA:com.b/.SvcB:com.c/.SvcC"3.2 ContentObserver 感知变化
AMS 在初始化时(第二篇分析过)已经为ENABLED_ACCESSIBILITY_SERVICES和ACCESSIBILITY_ENABLED这两个 URI 注册了 ContentObserver。当 Settings.Secure 的值发生变化时,Observer 的onChange()被触发:
源码路径:frameworks/base/services/core/java/com/android/server/accessibility/AccessibilityManagerService.java
// ContentObserver 中的核心处理逻辑(简化)@OverridepublicvoidonChange(booleanselfChange,Uriuri){synchronized(mLock){UserStateuserState=getCurrentUserStateLocked();// 步骤 1:重新读取已启用服务列表字符串StringnewServicesStr=Settings.Secure.getStringForUser(mContext.getContentResolver(),Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,mCurrentUserId);// 步骤 2:对比新旧值if(!Objects.equals(newServicesStr,userState.mEnabledServicesStr)){userState.mEnabledServicesStr=newServicesStr;// 步骤 3:解绑旧服务,绑定新服务unbindAllServicesLocked(userState);if(userState.mEnabled){bindServicesLocked(userState);}}// 步骤 4:如果全局开关从关→开,触发 bindServicesLockedbooleannewEnabled=(Settings.Secure.getIntForUser(mContext.getContentResolver(),Settings.Secure.ACCESSIBILITY_ENABLED,0,mCurrentUserId)==1);if(userState.mEnabled!=newEnabled){userState.mEnabled=newEnabled;if(newEnabled){bindServicesLocked(userState);}else{unbindAllServicesLocked(userState);}notifyClientsOfStateChangeLocked(userState);}}}完整的配置变化 → 服务绑定触发链路:
用户在 Settings 中开启无障碍服务 ↓ Settings.Secure.putString(ENABLED_ACCESSIBILITY_SERVICES, ...) ↓ ContentProvider 数据变化 ↓ ContentObserver.onChange() → (mMainHandler) ↓ 读取新配置值, 与 UserState 中的旧值对比 ↓ (有变化) unbindAllServicesLocked() → bindServicesLocked()四、bindServiceLocked():服务绑定的核心流程
当 AMS 决定要绑定无障碍服务时,bindServicesLocked()是入口,而真正的绑定逻辑在bindServiceLocked()中完成。
源码路径:frameworks/base/services/core/java/com/android/server/accessibility/AccessibilityManagerService.java
4.1 bindServicesLocked() 入口
privatevoidbindServicesLocked(UserStateuserState){// 通过 PackageManager 查询所有声明了无障碍 intent-filter 的 ServiceList<AccessibilityServiceInfo>installedServices=getInstalledAccessibilityServiceList(userState.mUserId);// 解析已启用服务列表字符串,得到 ComponentName 集合Set<ComponentName>enabledServices=parseEnabledServicesFromString(userState.mEnabledServicesStr);// 遍历已安装的服务for(AccessibilityServiceInfoinfo:installedServices){ComponentNamecomponentName=info.getComponentName();// 如果该服务在启用列表中,尝试绑定if(enabledServices.contains(componentName)){bindServiceLocked(componentName,info,userState);}}}getInstalledAccessibilityServiceList()是查询所有已安装无障碍服务的关键方法:
privateList<AccessibilityServiceInfo>getInstalledAccessibilityServiceList(intuserId){// 使用 Intent(action=android.accessibilityservice.AccessibilityService) 查询Intentintent=newIntent(AccessibilityService.SERVICE_INTERFACE);List<ResolveInfo>resolveInfoList=mPackageManager.queryIntentServicesAsUser(intent,PackageManager.GET_META_DATA,userId);List<AccessibilityServiceInfo>result=newArrayList<>();for(ResolveInforesolveInfo:resolveInfoList){AccessibilityServiceInfoinfo=parseServiceInfoLocked(resolveInfo,userState);if(info!=null){result.add(info);}}returnresult;}4.2 bindServiceLocked() 详细分析
privatevoidbindServiceLocked(ComponentNamecomponentName,AccessibilityServiceInfoserviceInfo,UserStateuserState){// ===== 步骤 1:检查是否已绑定或正在绑定 =====if(isServiceBoundLocked(userState,componentName)){return;// 已绑定,跳过}if(isServiceBindingLocked(userState,componentName)){return;// 正在绑定中,跳过(防止重复绑定)}// ===== 步骤 2:创建 Service 对象(封装服务连接信息) =====Serviceservice=newService(userState.mUserId,componentName,serviceInfo);// ===== 步骤 3:构造绑定 Intent =====Intentintent=newIntent(AccessibilityService.SERVICE_INTERFACE);intent.setComponent(componentName);// ===== 步骤 4:调用 Context.bindServiceAsUser() =====// BIND_AUTO_CREATE: 自动创建 Service 实例// BIND_SERVICE: 以服务身份绑定(对调用方透明)intflags=Context.BIND_AUTO_CREATE|Context.BIND_SERVICE|Context.BIND_INCLUDE_CAPABILITIES;booleanwasBound=mContext.bindServiceAsUser(intent,service,// ServiceConnection 实现flags,newUserHandle(userState.mUserId));}这里的Service类(注意不要与 Android 的android.app.Service混淆,它是 AMS 的内部类)实现了ServiceConnection接口,是 AMS 与无障碍服务进程之间连接的"桥梁"。
4.3 AccessibilityServiceConnection(Service 内部类)
AMS 内部的Service类(在 Android 较新版本中也叫AccessibilityServiceConnection)是一个核心的内部类,承担了以下职责:
源码路径:frameworks/base/services/core/java/com/android/server/accessibility/AccessibilityManagerService.java
privatefinalclassServiceextendsIAccessibilityServiceConnection.StubimplementsServiceConnection,IBinder.DeathRecipient{// 用户 IDfinalintmUserId;// 无障碍服务 ID(自增整数,唯一标识)finalintmId;// 服务组件名finalComponentNamemComponentName;// 服务配置信息AccessibilityServiceInfomAccessibilityServiceInfo;// 指向服务进程的回调接口代理(通过 onServiceConnected 获取)IAccessibilityServiceClientmServiceInterface;// ServiceConnection 回调状态booleanmServiceConnected;// 事件分发相关// ...}这个Service类同时扮演了三个角色:
IAccessibilityServiceConnection.Stub:作为 Binder 服务端,响应来自 AccessibilityService 的请求(如
getWindow()、findAccessibilityNodeInfoByAccessibilityId()等)。它是一个双向通信通道——服务端向客户端推送事件,同时客户端也可以通过这个 Binder 向服务端查询信息。ServiceConnection 实现:接收 Android 系统的服务绑定回调
onServiceConnected()和onServiceDisconnected()。DeathRecipient 实现:监听 AccessibilityService 所在进程的死亡事件。当服务进程崩溃或被杀死时,
binderDied()会被回调,AMS 可以触发重新绑定或清理逻辑。
4.4 ServiceInfo 验证:disableSelf() 能力
在bindServiceLocked()之前,AMS 还会校验AccessibilityServiceInfo中声明的 capability。一个被普遍关注的机制是disableSelf()——无障碍服务可以主动调用此方法要求系统关闭自己(在用户授权过期或条件不满足时使用)。此能力的启用要求服务在配置中声明了canRetrieveWindowContent或特定的 flags。
五、服务绑定的完整时序
当mContext.bindServiceAsUser()调用成功后,Android 的 ActivityManagerService 会启动目标 App 进程(如果尚未运行),并触发标准的 Service 绑定流程。以下是完整的时序:
AMS (system_server) AccessibilityService (App 进程) │ │ │ bindServiceAsUser(intent, service, flags) │ │─────────────────────────────────────────────────→│ │ │ AMS 启动目标进程 │ 进程启动 │ (如果尚未运行