Android 7系统无障碍服务(二)AccessibilityManagerService 启动与初始化

系列目录 :第一篇:全景图与架构概览 | [第二篇:AccessibilityManagerService 启动与初始化](#第二篇:AccessibilityManagerService 启动与初始化) | 第三篇:无障碍服务的注册与绑定 | [第四篇:AccessibilityEvent 的产生与投递](#第四篇:AccessibilityEvent 的产生与投递) | [第五篇:AccessibilityNodeInfo 的构建与查询](#第五篇:AccessibilityNodeInfo 的构建与查询) | [第六篇:输入事件拦截与 TouchExplorer](#第六篇:输入事件拦截与 TouchExplorer) | [第七篇:手势分发与 KeyEvent 处理](#第七篇:手势分发与 KeyEvent 处理) | 第八篇:放大镜与辅助功能特性


上一篇我们从宏观上梳理了无障碍服务的五层架构、核心类关系和三条主干数据流。本篇聚焦于整个无障碍模块的"中枢"------AccessibilityManagerService(以下简称 AMS),深入分析它从 SystemServer 启动到完成初始化的全过程。


一、启动入口:SystemServer.startOtherServices()

AMS 的创建发生在 SystemServer 的 startOtherServices() 方法中,与 ActivityManagerService、WindowManagerService 等核心服务一样,属于系统启动的中后期阶段。

源码路径 :frameworks/base/services/java/com/android/server/SystemServer.java

java 复制代码
private void startOtherServices() {
    // ... 大量其他服务的初始化 ...

    // 获取 WindowManagerService,它在 AMS 之前初始化
    inputManager = new InputManagerService(context);
    wm = WindowManagerService.main(context, inputManager,
            mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
            !mFirstBoot, mOnlyCore, new PhoneWindowManager());

    // ... 其他服务 ...

    // 将已初始化的服务注册到 ServiceManager
    ServiceManager.addService(Context.WINDOW_SERVICE, wm);
    ServiceManager.addService(Context.INPUT_SERVICE, inputManager);

    // ...

    // 创建 AccessibilityManagerService
    accessibility = new AccessibilityManagerService(context);

    // 注册到 ServiceManager,使客户端可以通过 Binder 获取
    ServiceManager.addService(Context.ACCESSIBILITY_SERVICE, accessibility);
}

调用链路很简洁:

复制代码
SystemServer.startOtherServices()
    → new AccessibilityManagerService(context)
    → ServiceManager.addService(Context.ACCESSIBILITY_SERVICE, accessibility)

这里有两个值得注意的细节:

  1. AMS 的创建时机在 WMS 之后 ,但 WMS 实例并没有直接传递给 AMS 的构造函数。AMS 的构造函数仅接收 Context,后续需要 WMS 时通过 LocalServices 机制获取 WindowManagerInternal------这是一种更晚绑定、更低耦合的设计。

  2. AMS 注册的服务名是 Context.ACCESSIBILITY_SERVICE ,也就是字符串 "accessibility"。客户端通过 Context.getSystemService(Context.ACCESSIBILITY_SERVICE) 拿到的就是 AMS 的 Binder 代理------实际上是 IAccessibilityManager.Stub 的代理对象。


二、AMS 构造函数:内部状态的初始化

AMS 的构造函数完成了全部"基础设施"的搭建。

源码路径 :frameworks/base/services/core/java/com/android/server/accessibility/AccessibilityManagerService.java

java 复制代码
public AccessibilityManagerService(Context context) {
    mContext = context;

    // 创建 PackageManager 引用,用于后续查询已安装的无障碍服务
    mPackageManager = mContext.getPackageManager();

    // 创建 PowerManager 引用,放大镜功能需要用 WakeLock
    mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);

    // 初始化全局锁对象 ------ 几乎所有对内部状态的修改都需持有此锁
    mLock = new Object();

    // 当前操作用户 ID,初始化为当前活跃用户
    mCurrentUserId = ActivityManager.getCurrentUser();

    // 创建主线程 Handler,AMS 中大量异步操作依赖它
    mMainHandler = new MainHandler(mContext.getMainLooper());

    // 向 WMS 注册无障碍焦点相关的 WindowManagerInternal
    // 通过 LocalServices 获取引用,不直接持有 WMS

    // 初始化 UserState ------ 每个用户独立的状态存储
    mUserStates = new SparseArray<>();

    // 读取 Secure Settings 初始化当前用户的配置
    UserState userState = new UserState(mCurrentUserId);
    mUserStates.put(mCurrentUserId, userState);
    readConfigurationForUserStateLocked(userState);

    // 注册 Settings.Secure 内容观察者
    registerSettingsContentObservers(mCurrentUserId);

    // 注册系统广播接收器
    registerBroadcastReceivers();
}

构造函数中的初始化可以归为以下几类:

2.1 系统服务引用

变量 类型 用途
mPackageManager PackageManager 查询已安装无障碍服务列表、解析 ServiceInfo
mPowerManager PowerManager 放大镜功能需要防止屏幕超时(WakeLock)
mLock Object 全局同步锁,保护内部状态的一致性

2.2 匿名 Binder:mService

mService 是 AMS 内部最核心的服务端 Binder,一个匿名 IAccessibilityManager.Stub 实现。所有来自 AccessibilityManager(客户端)和 AccessibilityService 的跨进程调用,都通过这个 Binder 进入 AMS。

java 复制代码
// 源码路径:frameworks/base/services/core/java/com/android/server/accessibility/AccessibilityManagerService.java

private final IAccessibilityManager.Stub mService = new IAccessibilityManager.Stub() {

    @Override
    public void sendAccessibilityEvent(AccessibilityEvent event, int userId) {
        // 第四篇重点:事件的跨进程入口
    }

    @Override
    public boolean sendFingerprintGesture(int keyCode) {
        // 指纹手势的无障碍处理
    }

    @Override
    public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {
        // 查询已安装的无障碍服务列表
    }

    @Override
    public List<AccessibilityServiceInfo> getEnabledAccessibilityServices(int feedbackType) {
        // 查询已启用的无障碍服务列表
    }

    @Override
    public void interrupt(int userId) {
        // 中断所有无障碍服务的当前操作
    }

    @Override
    public int addClient(IAccessibilityManagerClient client, int userId) {
        // 客户端注册回调,用于状态变化通知
    }

    @Override
    public void registerUiTestAutomationService(
            IBinder owner, IAccessibilityServiceClient client,
            AccessibilityServiceInfo info, int flags) {
        // UiAutomation 测试框架专用的注册入口
    }

    // ... 更多接口方法,涵盖事件投递、服务管理、状态查询等
};

这个匿名 Binder 通过 onBind() 暴露给 ServiceManager:

java 复制代码
@Override
public IBinder onBind(Intent intent) {
    return mService;
}

当 ServiceManager.addService(Context.ACCESSIBILITY_SERVICE, accessibility) 执行时,系统会将 mService 注册到 ServiceManager,使所有进程都能通过 Binder 查询到它。

mService 中的每个方法几乎都遵循同一个调用模式:

复制代码
Binder 线程 → 获取 mLock → 切 mMainHandler → 执行业务逻辑

这种"Binder 线程加锁 + Handler 切主线程"的双重保护,确保了多线程环境下内部状态的一致性。

2.3 mMainHandler:主线程消息处理

AMS 内部定义了一个 MainHandler,是 Handler 的子类,承载了大量延时任务和异步回调的分发:

java 复制代码
// 源码路径:frameworks/base/services/core/java/com/android/server/accessibility/AccessibilityManagerService.java

private final class MainHandler extends Handler {
    public static final int MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER = 1;
    public static final int MSG_SEND_KEY_EVENT_TO_INPUT_FILTER = 2;
    public static final int MSG_SEND_STATE_TO_CLIENTS = 3;
    public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 4;
    public static final int MSG_ANNOUNCE_NEW_USER_IF_NEEDED = 5;
    public static final int MSG_CLEAR_ACCESSIBILITY_FOCUS = 6;

    public MainHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER:
                // 将事件发送到 InputFilter 链,供 TouchExplorer 等处理
                notifyAccessibilityServicesDelayedLocked(...);
                break;
            case MSG_SEND_KEY_EVENT_TO_INPUT_FILTER:
                // 发送按键事件到 InputFilter 链
                break;
            case MSG_SEND_STATE_TO_CLIENTS:
                // 通知所有已注册客户端无障碍状态变化
                break;
            case MSG_CLEAR_ACCESSIBILITY_CACHE:
                // 通知所有 AccessibilityService 清除内部缓存
                break;
            case MSG_ANNOUNCE_NEW_USER_IF_NEEDED:
                // 多用户切换时的语音提示
                break;
            case MSG_CLEAR_ACCESSIBILITY_FOCUS:
                // 清除无障碍焦点
                break;
        }
    }
}

这些消息类型覆盖了事件投递(第四篇)、状态广播、缓存清理(第五篇)等后续各篇涉及的核心流程。理解 MainHandler 的消息模型,是理解整个 AMS 异步机制的基石。


三、UserState:多用户状态隔离

Android 7 支持多用户(Multi-User),每个用户拥有独立的无障碍服务配置和运行时状态。AMS 通过 UserState 内部类实现用户级隔离,以 SparseArray<UserState> 存储所有用户状态,键为用户 ID。

源码路径 :frameworks/base/services/core/java/com/android/server/accessibility/AccessibilityManagerService.java

java 复制代码
private class UserState {
    // 用户 ID
    public final int mUserId;

    // ===== 持久化配置项(来自 Settings.Secure)=====

    // 已启用的无障碍服务列表(ComponentName 字符串,冒号分隔)
    public String mEnabledServicesStr;

    // 触摸探索模式是否允许
    public boolean mTouchExplorationGranted;

    // 放大镜是否开启
    public boolean mMagnificationEnabled;

    // 无障碍快捷方式是否开启
    public boolean mAccessibilityShortcutEnabled;

    // 无障碍快捷方式对应的目标服务 ComponentName
    public String mAccessibilityShortcutTarget;

    // 高对比度文字是否开启
    public boolean mHighTextContrastEnabled;

    // 字体缩放比例
    public float mFontScale;

    // ===== 运行时状态 =====

    // 已绑定并正在运行的无障碍服务列表
    public final List<Service> mBoundServices = new ArrayList<>();

    // 正在绑定中的服务(ComponentName 集合)
    public final Set<ComponentName> mBindingServices = new HashSet<>();

    // 已安装的无障碍服务信息缓存
    public List<AccessibilityServiceInfo> mInstalledServices;

    // 无障碍是否全局开启
    public boolean mEnabled;

    // 当前有无障碍焦点的窗口 ID
    public int mAccessibilityFocusWindowId = -1;

    // 当前有无障碍焦点的节点 ID
    public long mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;

    // ContentObserver 引用(切换用户时用于注销)
    public ContentObserver mContentObserver;

    // 广播接收器列表
    public final List<BroadcastReceiver> mReceivers = new ArrayList<>();
}

多用户切换时,AMS 的 switchUser() 方法会:

  1. 在当前 UserState 上调用 unbindAllServicesLocked() 停止所有服务
  2. 切换到目标 UserState
  3. 在目标 UserState 上调用 bindServicesLocked() 重新绑定该用户启用的服务

mUserStates 的 SparseArray 结构确保了不同用户间的状态完全隔离。


四、Settings.Secure 配置项读取与初始化

无障碍服务的大部分开关和配置都通过 Settings.Secure 持久化存储。AMS 在初始化 UserState 时,需要一次性读取所有相关配置并缓存到内存。

4.1 关键 Secure 配置项一览

配置项 Key 类型 含义
ACCESSIBILITY_ENABLED int (0/1) 无障碍功能全局开关
ENABLED_ACCESSIBILITY_SERVICES String 已启用的无障碍服务列表,格式 pkg/ServiceCls:pkg2/ServiceCls
TOUCH_EXPLORATION_ENABLED int (0/1) 触摸探索模式开关
ACCESSIBILITY_SHORTCUT_ENABLED int (0/1) 无障碍快捷方式开关
ACCESSIBILITY_SHORTCUT_TARGET_SERVICE String 快捷方式目标服务 ComponentName
ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED int (0/1) 放大镜开关
ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED int (0/1) 高对比度文字开关
FONT_SCALE float 字体缩放比例
ACCESSIBILITY_SCRIPT_INJECTION int (0/1) 允许服务注入网页脚本

4.2 readConfigurationForUserStateLocked()

这个方法在初始化时被调用,将所有 Secure 配置一次性读取并缓存到 UserState:

java 复制代码
private void readConfigurationForUserStateLocked(UserState userState) {
    // 全局开关
    userState.mEnabled = (Settings.Secure.getIntForUser(
            mContext.getContentResolver(),
            Settings.Secure.ACCESSIBILITY_ENABLED, 0,
            userState.mUserId) == 1);

    // 已启用的服务列表字符串
    userState.mEnabledServicesStr = Settings.Secure.getStringForUser(
            mContext.getContentResolver(),
            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
            userState.mUserId);

    // 触摸探索
    userState.mTouchExplorationGranted = (Settings.Secure.getIntForUser(
            mContext.getContentResolver(),
            Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0,
            userState.mUserId) == 1);

    // 放大镜
    userState.mMagnificationEnabled = (Settings.Secure.getIntForUser(
            mContext.getContentResolver(),
            Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0,
            userState.mUserId) == 1);

    // 高对比度文字
    userState.mHighTextContrastEnabled = (Settings.Secure.getIntForUser(
            mContext.getContentResolver(),
            Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0,
            userState.mUserId) == 1);

    // ... 其他配置项 ...
}

后续所有状态判断都直接使用内存缓存,而不是每次都查询 ContentProvider,这是一个典型的性能优化策略。


五、ContentObserver:配置变化的实时监听

仅仅读取初始值是不够的------用户在"设置 → 无障碍"中修改任何开关,AMS 都必须立即响应。这是通过 ContentObserver 实现的。

源码路径 :frameworks/base/services/core/java/com/android/server/accessibility/AccessibilityManagerService.java

java 复制代码
private void registerSettingsContentObservers(int userId) {
    UserState userState = getUserStateLocked(userId);

    ContentObserver observer = new ContentObserver(mMainHandler) {
        @Override
        public void onChange(boolean selfChange, Uri uri) {
            synchronized (mLock) {
                UserState userState = getCurrentUserStateLocked();

                // 无障碍全局开关变化 → 更新状态并重新绑定/解绑服务
                if (userState.mEnabled != isAccessibilityEnabled()) {
                    userState.mEnabled = isAccessibilityEnabled();
                    if (userState.mEnabled) {
                        bindServicesLocked(userState);  // 关→开:绑定服务
                    } else {
                        unbindAllServicesLocked(userState);  // 开→关:解绑全部
                    }
                    notifyClientsOfStateChangeLocked(userState);
                }

                // 已启用服务列表变化 → 重新绑定
                String newServicesStr = Settings.Secure.getStringForUser(
                        mContext.getContentResolver(),
                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                        userId);
                if (!newServicesStr.equals(userState.mEnabledServicesStr)) {
                    userState.mEnabledServicesStr = newServicesStr;
                    unbindAllServicesLocked(userState);
                    bindServicesLocked(userState);
                }

                // 触摸探索开关变化 → 重新配置 InputFilter
                // 放大镜开关变化 → 重新配置 MagnificationController
                // ...
            }
        }
    };

    ContentResolver resolver = mContext.getContentResolver();
    // 为每个关键 URI 注册监听
    resolver.registerContentObserver(
            Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_ENABLED),
            false, observer, userId);
    resolver.registerContentObserver(
            Settings.Secure.getUriFor(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES),
            false, observer, userId);
    resolver.registerContentObserver(
            Settings.Secure.getUriFor(Settings.Secure.TOUCH_EXPLORATION_ENABLED),
            false, observer, userId);
    // ... 更多 URI 注册

    userState.mContentObserver = observer;
}

这里有三个关键设计细节:

  1. **回调在 m
相关推荐
应用市场1 小时前
不用一张图片的像素小游戏:小人是字符串,房间是矩形,平衡靠两千个假玩家
android·kotlin·游戏开发
stevenzqzq2 小时前
android 对渠道的理解
android
QEasycloud2 小时前
BOM接口抛转失败钉钉通知策略实战教程:MySQL异常日志到钉钉消息的端到端配置
android·mysql·钉钉
AIsoft_86892 小时前
医患交流内容如何做好记录?
android·iphone
蒸鱼Yuzheng3 小时前
Android Native 崩溃证据链:Tombstone、Build ID 与 Trace 如何联合归因
android·ndk·tombstone·调试符号·崩溃分析
事圆则缓3 小时前
Kotlin 变量与函数:把页面状态和行为写成清楚的契约
android·开发语言·kotlin
hunterandroid3 小时前
MVVM 迁移到 MVI:复杂页面状态如何收敛
android
妖星3 小时前
Android 开机动画定制:从 bootanimation.zip 到源码编译
android
临期冰淇淋4 小时前
MTK平台 安卓16 GMS报错修改
android·java·前端
mmsx4 小时前
Android 模块化三件套:轻量组件化、宿主插件双形态、AIDL 隔离
android·网络·组件化·aidl