ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android 7系统无障碍服务(三)无障碍服务的注册与绑定

Android 7系统无障碍服务(三)无障碍服务的注册与绑定 系列目录第一篇全景图与架构概览 | 第二篇AccessibilityManagerService 启动与初始化 | 第三篇无障碍服务的注册与绑定 | 第四篇AccessibilityEvent 的产生与投递 | 第五篇AccessibilityNodeInfo 的构建与查询 | 第六篇输入事件拦截与 TouchExplorer | 第七篇手势分发与 KeyEvent 处理 | 第八篇放大镜与辅助功能特性上一篇我们分析了 AMS 的启动与初始化过程初始化完成时mBoundServices还是空的没有任何无障碍服务被绑定。本篇将完整梳理一个无障碍服务从在 Manifest 中声明到被 AMS 绑定并能够接收回调的全过程包括 AccessibilityServiceInfo 的解析、服务启用的触发链路、以及bindServicesLocked()的核心绑定逻辑。一、Manifest 声明一个无障碍服务的入场券一个 App 要成为系统级的无障碍服务必须在AndroidManifest.xml中满足三个条件源码路径开发者工程的AndroidManifest.xmlmanifestxmlns:androidhttp://schemas.android.com/apk/res/androidpackagecom.example.accessibilityapplication!-- 1. 声明 Service --serviceandroid:name.MyAccessibilityServiceandroid:permissionandroid.permission.BIND_ACCESSIBILITY_SERVICEandroid:exportedtrue!-- 2. intent-filter 指定 action --intent-filteractionandroid:nameandroid.accessibilityservice.AccessibilityService//intent-filter!-- 3. meta-data 指向服务配置文件 --meta-dataandroid:nameandroid.accessibilityserviceandroid:resourcexml/accessibility_service_config//service/application/manifest这三项缺一不可每一项都有明确的系统级校验条件作用校验位置android.permission.BIND_ACCESSIBILITY_SERVICE限定只有系统签名的 App 才能绑定该 Service。普通 App 无法声明此权限绑定时 AMS 会校验调用方是否持有此权限AMSbindServiceLocked()android.accessibilityservice.AccessibilityServiceaction系统用此 action 筛选哪些 Service 是无障碍服务。AMS 调用PackageManager.queryIntentServices()时使用此 IntentAMSgetInstalledAccessibilityServiceList()meta-data指向配置 XML提供AccessibilityServiceInfo的配置来源capabilities、feedbackType、flags 等AMS 解析 XML 构建AccessibilityServiceInfo其中BIND_ACCESSIBILITY_SERVICE是一个 signature-level 权限定义在 framework-res 中!-- frameworks/base/core/res/AndroidManifest.xml --permissionandroid:nameandroid.permission.BIND_ACCESSIBILITY_SERVICEandroid:protectionLevelsignature/这意味着只有与系统使用相同签名的 App 才能声明此权限。普通第三方 App 的签名与系统签名不同系统在 bindService 时会校验这一权限不匹配则拒绝绑定。这个安全机制确保无障碍服务无法被恶意 App 伪装。二、AccessibilityServiceInfo 配置解析meta-data指向的 XML 文件定义了无障碍服务的能力声明系统在绑定前需要解析这些配置。2.1 配置 XML 示例源码路径开发者工程的res/xml/accessibility_service_config.xmlaccessibility-servicexmlns:androidhttp://schemas.android.com/apk/res/androidandroid:accessibilityEventTypestypeAllMaskandroid:accessibilityFeedbackTypefeedbackGenericandroid:accessibilityFlagsflagDefault| flagRequestTouchExplorationMode| flagRequestFilterKeyEventsandroid:canRequestEnhancedWebAccessibilitytrueandroid:canRetrieveWindowContenttrueandroid:notificationTimeout100android:descriptionstring/accessibility_service_description/2.2 配置项与 AccessibilityServiceInfo 字段映射解析发生在 AMS 调用PackageManager.getServiceInfo()获取ServiceInfo之后通过meta-data读取 XML 属性并填充到AccessibilityServiceInfo对象中。源码路径frameworks/base/services/core/java/com/android/server/accessibility/AccessibilityManagerService.javaprivateAccessibilityServiceInfoparseServiceInfoLocked(ResolveInforesolveInfo,UserStateuserState){ServiceInfoserviceInforesolveInfo.serviceInfo;StringpackageNameserviceInfo.packageName;StringclassNameserviceInfo.name;AccessibilityServiceInfoinfo;try{infonewAccessibilityServiceInfo(resolveInfo,mContext);// 构造函数内部会解析 meta-data XML 并填充字段}catch(XmlPullParserException|IOExceptione){returnnull;}returninfo;}AccessibilityServiceInfo的构造函数读取 XML 中各属性核心字段映射如下XML 属性AccessibilityServiceInfo 字段含义accessibilityEventTypeseventTypes要监听的事件类型位掩码如TYPE_VIEW_CLICKED、TYPE_WINDOW_STATE_CHANGED等accessibilityFeedbackTypefeedbackType反馈类型语音、触觉、通用等accessibilityFlagsflags功能标志见下文详述notificationTimeoutnotificationTimeout事件通知的最小间隔毫秒避免高频事件冲击canRetrieveWindowContent对应CAN_RETRIEVE_WINDOW_CONTENTcapability flag是否可查询窗口内容节点树canRequestEnhancedWebAccessibility对应CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITYflag是否可请求增强的 WebView 无障碍支持descriptiondescription服务描述展示给用户在 Settings 中2.3 accessibilityFlags 详解flags字段决定了服务的行为模式在无障碍框架中是最关键的配置之一// 源码路径frameworks/base/core/java/android/accessibilityservice/AccessibilityServiceInfo.java// 默认标志服务仅接收 AccessibilityEvent不请求任何特殊能力publicstaticfinalintFLAG_DEFAULT0;// 请求过滤按键事件 → AMS 会将 KeyEvent 先交给此服务处理publicstaticfinalintFLAG_REQUEST_FILTER_KEY_EVENTS0x00000004;// 请求触摸探索模式 → AMS 会注册 InputFilter 并启用 TouchExplorerpublicstaticfinalintFLAG_REQUEST_TOUCH_EXPLORATION_MODE0x00000020;// 请求增强 Web 无障碍 → 允许服务向 WebView 注入脚本publicstaticfinalintFLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY0x00000010;// 请求过滤指纹手势publicstaticfinalintFLAG_REQUEST_FINGERPRINT_GESTURES0x00000100;// 服务开启后请求显示无障碍按钮publicstaticfinalintFLAG_REQUEST_ACCESSIBILITY_BUTTON0x00000200;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/.SvcC3.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){UserStateuserStategetCurrentUserStateLocked();// 步骤 1重新读取已启用服务列表字符串StringnewServicesStrSettings.Secure.getStringForUser(mContext.getContentResolver(),Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,mCurrentUserId);// 步骤 2对比新旧值if(!Objects.equals(newServicesStr,userState.mEnabledServicesStr)){userState.mEnabledServicesStrnewServicesStr;// 步骤 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.mEnablednewEnabled;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.java4.1 bindServicesLocked() 入口privatevoidbindServicesLocked(UserStateuserState){// 通过 PackageManager 查询所有声明了无障碍 intent-filter 的 ServiceListAccessibilityServiceInfoinstalledServicesgetInstalledAccessibilityServiceList(userState.mUserId);// 解析已启用服务列表字符串得到 ComponentName 集合SetComponentNameenabledServicesparseEnabledServicesFromString(userState.mEnabledServicesStr);// 遍历已安装的服务for(AccessibilityServiceInfoinfo:installedServices){ComponentNamecomponentNameinfo.getComponentName();// 如果该服务在启用列表中尝试绑定if(enabledServices.contains(componentName)){bindServiceLocked(componentName,info,userState);}}}getInstalledAccessibilityServiceList()是查询所有已安装无障碍服务的关键方法privateListAccessibilityServiceInfogetInstalledAccessibilityServiceList(intuserId){// 使用 Intent(actionandroid.accessibilityservice.AccessibilityService) 查询IntentintentnewIntent(AccessibilityService.SERVICE_INTERFACE);ListResolveInforesolveInfoListmPackageManager.queryIntentServicesAsUser(intent,PackageManager.GET_META_DATA,userId);ListAccessibilityServiceInforesultnewArrayList();for(ResolveInforesolveInfo:resolveInfoList){AccessibilityServiceInfoinfoparseServiceInfoLocked(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 对象封装服务连接信息 ServiceservicenewService(userState.mUserId,componentName,serviceInfo);// 步骤 3构造绑定 Intent IntentintentnewIntent(AccessibilityService.SERVICE_INTERFACE);intent.setComponent(componentName);// 步骤 4调用 Context.bindServiceAsUser() // BIND_AUTO_CREATE: 自动创建 Service 实例// BIND_SERVICE: 以服务身份绑定对调用方透明intflagsContext.BIND_AUTO_CREATE|Context.BIND_SERVICE|Context.BIND_INCLUDE_CAPABILITIES;booleanwasBoundmContext.bindServiceAsUser(intent,service,// ServiceConnection 实现flags,newUserHandle(userState.mUserId));}这里的Service类注意不要与 Android 的android.app.Service混淆它是 AMS 的内部类实现了ServiceConnection接口是 AMS 与无障碍服务进程之间连接的桥梁。4.3 AccessibilityServiceConnectionService 内部类AMS 内部的Service类在 Android 较新版本中也叫AccessibilityServiceConnection是一个核心的内部类承担了以下职责源码路径frameworks/base/services/core/java/com/android/server/accessibility/AccessibilityManagerService.javaprivatefinalclassServiceextendsIAccessibilityServiceConnection.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 启动目标进程 │ 进程启动 │ (如果尚未运行
返回列表