Android 16系统源码_无障碍辅助(一)认识无障碍服务

news2025/7/21 10:09:46

前言

Android 的无障碍辅助功能(Accessibility)是一套专为残障用户或特殊场景设计的核心技术框架,旨在让所有用户都能便捷地操作设备。其功能覆盖视觉、听觉、运动能力和认知障碍支持,同时为开发者提供标准化 API 以实现应用适配。

核心功能组件

Android 提供了多种内置无障碍工具和服务,涵盖不同场景需求:

功能名称主要用途适用人群
TalkBack屏幕阅读器,通过语音和振动反馈界面内容视障用户
Voice Access语音控制界面元素(可见即可说)运动障碍/免提操作
Switch Access通过物理开关或摄像头动作控制设备重度运动障碍用户
Select to Speak点按或圈选文本后朗读内容阅读障碍/学习困难用户
字幕和音频增强实时字幕、声音放大器、左右声道平衡听障用户
高对比度模式调整界面颜色和字体大小以提升可读性低视力用户

技术架构与原理

Android 无障碍功能的实现依赖多层系统服务协作。

事件传递机制

  • 事件源:用户操作(如点击)或系统事件(如通知)触发 AccessibilityEvent。
  • 收集与分发:系统级服务 AccessibilityManagerService 统一管理事件,按类型分发给注册的无障碍服务。
  • 处理响应:开发者实现的 AccessibilityService 通过 onAccessibilityEvent() 接收事件并执行逻辑(如点击按钮、读取文本)。

界面元素访问

  • 控件树解析:通过 AccessibilityNodeInfo 获取界面元素的层级结构、属性(如文本、坐标)。
  • 窗口快照:AccessibilityWindowManager 维护窗口状态,提供跨应用的界面内容访问(需用户授权)。

语音与交互

  • 语音合成(TTS):TalkBack 使用 TextToSpeech 引擎朗读文本。
  • 手势映射:自定义手势(如双指滑动)触发操作,由 AccessibilityGestureDetector 解析。

简单案例

下面我们将会编写一个简单的案例,具体认识一下 Android 无障碍服务 (AccessibilityService) 。此案例主要用于获取当前屏幕上的所有文本信息。

步骤 1:添加无障碍服务声明(AndroidManifest.xml )

<service
    android:name=".MyAccessibilityService"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
    android:exported="true">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
    </intent-filter>
    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/accessibility_service_config" />
</service>

步骤 2:创建无障碍服务配置文件(res/xml/accessibility_service_config.xml)

<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/accessibility_service_desc"
    android:accessibilityEventTypes="typeWindowContentChanged|typeWindowStateChanged"
    android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows"
    android:canRetrieveWindowContent="true"
    android:settingsActivity="com.example.MainActivity"/>

步骤 3:实现无障碍服务类

public class MyAccessibilityService extends AccessibilityService {

    @Override
    public void onServiceConnected() {
        Log.d("Accessibility", "无障碍服务已启动");
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        if (eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED ||
            eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            
            AccessibilityNodeInfo rootNode = getRootInActiveWindow();
            if (rootNode == null) return;
            
            List<String> texts = getAllTexts(rootNode);
            Log.d("ScreenTexts", "捕获文本: " + TextUtils.join(", ", texts)); 
            sendTextToActivity(texts);
            rootNode.recycle(); // 释放资源
        }
    }

    private List<String> getAllTexts(AccessibilityNodeInfo node) {
        List<String> texts = new ArrayList<>();
        traverseNode(node, texts);
        return filterUniqueTexts(texts);
    }

    private void traverseNode(AccessibilityNodeInfo node, List<String> texts) {
        if (node == null) return;

        CharSequence text = node.getText();
        if (text != null && !TextUtils.isEmpty(text)) {
            texts.add(text.toString().trim());
        }

        for (int i = 0; i < node.getChildCount(); i++) {
            AccessibilityNodeInfo child = node.getChild(i);
            if (child != null) {
                traverseNode(child, texts);
                child.recycle(); // 必须回收子节点
            }
        }
    }

    private List<String> filterUniqueTexts(List<String> list) {
        Set<String> uniqueSet = new HashSet<>(list);
        return new ArrayList<>(uniqueSet);
    }

    private void sendTextToActivity(List<String> texts) {
        Intent intent = new Intent("ACTION_UPDATE_TEXT");
        intent.putStringArrayListExtra("texts", new ArrayList<>(texts));
        sendBroadcast(intent);
    }

    @Override
    public void onInterrupt() {}
}

启用无障碍服务

需要在系统设置中启用应用的无障碍服务。
请添加图片描述

运行效果

当界面内容变化时(如打开新页面),服务会自动捕获所有可见文本,并打印出来。

在我们在设置页面开启无障碍服务的时候
开启无障碍服务

TextAccessibilityService com.example.myapplication            I  无障碍服务已启动
TextAccessibilityService com.example.myapplication            I  捕获文本:TextAccessibility_tip”快捷方式, 关闭, 设置, 选项, 使用“TextAccessibility_tip, TextAccessibilityDesc, TextAccessibility_tip

打开首页
打开首页

TextAccessibilityService  com.example.myapplication            I  捕获文本: 设置, 文件, 时钟, 拨打电话, MyApplication, WebView Browser Tester, 相机, 日历, TMoble, 通讯录, Search, 搜索应用, 短信, 图库
TextAccessibilityService  com.example.myapplication            I  捕获文本: 拨打电话, 短信, WebView Browser Tester, 相机, 图库

打开MainActivity
打开MainActivity

TextAccessibilityService  com.example.myapplication            I  捕获文本: Hello World!

注意事项

权限限制

  • Android 11+ 需要 android.permission.QUERY_ALL_PACKAGES 权限才能获取其他应用的内容。

  • 敏感场景(如密码输入)可能无法捕获文本。

性能优化

  • 避免频繁遍历控件树(可增加事件过滤)。

  • 使用 postDelayed 防抖处理高频事件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2391992.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式数据库备份实践

在分布式备份中可以采取两种方式进行备份&#xff0c;一种是采用手动编写backup.yml文件进行备份&#xff0c;另外一种是吧备份过程交给备份工具自动执行。如果需要个性化进行备份&#xff0c;建议采用手动编写备份文件方式进行备份。 以下是针对两种备份方式的实践&#xff1a…

如何发布npm包?

如何发布npm包&#xff1f; 1. 注册账号[npm官网](https://www.npmjs.com/)2. 检查 npm 源是否在官方 npm 仓库&#xff0c;如果不在&#xff0c;进行切换3. 检查4. 打包配置5. 发布6. 使用错误&#xff1a;版本更新命令 1. 注册账号npm官网 2. 检查 npm 源是否在官方 npm 仓库…

实验设计与分析(第6版,Montgomery)第5章析因设计引导5.7节思考题5.6 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第5章析因设计引导5.7节思考题5.6 R语言解题。主要涉及方差分析&#xff0c;正态假设检验&#xff0c;残差分析&#xff0c;交互作用图&#xff0c;等值线图。 dataframe <-data.frame…

OpenCV计算机视觉实战(8)——图像滤波详解

OpenCV计算机视觉实战&#xff08;8&#xff09;——图像滤波详解 0. 前言1. 线性滤波1.1 均值滤波1.2 高斯滤波1.3 拉普拉斯滤波1.4 Sobel 滤波 2. 非线性滤波3. 自定义卷积核小结系列链接 0. 前言 在本文中&#xff0c;我们将深入探索线性与非线性滤波的算法原理、性能优化及…

自动化安全脚本学习

1.目录扫描器 目标&#xff1a;使用python编写一个自动化目录扫描工具&#xff0c;实现简单信息收集&#xff0c;判断目标网站是否存在常见路径。 import requests #用于发HTTP请求 from concurrent.futures import ThreadPoolExecutor #实现多线程扫描# 扫描目标 target h…

传输层协议TCP(上)

上一篇https://blog.csdn.net/Small_entreprene/article/details/148143494?fromshareblogdetail&sharetypeblogdetail&sharerId148143494&sharereferPC&sharesourceSmall_entreprene&sharefromfrom_link 上文学习了传输层的协议之一UDP&#xff0c;接下来…

Windows下安装并使用kubectl查看K8S日志

【1】安装kubectl 官网文档&#xff1a;https://kubernetes.io/zh-cn/docs/tasks/tools/install-kubectl-windows/ 下载后得到 kubectl.exe&#xff0c;放到一个目录下&#xff0c;然后配置环境变量。 此时CMD 进入DOS命令窗口 kubectl version【2】配置config文件 其实就是…

Android studio进阶开发(六)--如何用真机通过okhttp连接服务器

我们学过了如何通过okhttp查询网络上已经发布的网页&#xff0c;但我们还需要在做全栈时保证前后端能够交互。 前要课程 okhttp的使用 真机端口连接 安全认证 由于http的安全性较差&#xff0c;在没有安全协议的情况下&#xff0c;使用自己的后端连接会报错&#xff0c;所以…

WeakAuras Lua Script [ICC BOSS 11 - Sindragosa]

WeakAuras Lua Script [ICC BOSS 11 - Sindragosa] 冰冠堡垒Icecrown Citadel 冰龙 辛达苟萨&#xff08;寒冰信标插件&#xff09; 左 &#xff08;绿&#xff0c;黄&#xff09; 中(蓝&#xff0c;紫&#xff09; 右&#xff08;白&#xff0c;橙&#xff09; lua script&…

电脑开机后出现bootmgr is conmpressed原因及解决方法

最近有网友问我为什么我电脑开机后出现BOOTMGR is compressed&#xff0c;这个提示意思是:意思是启动管理器被压缩了&#xff0c;即使重启也无法正常进入系统。原因有很多&#xff0c;大部分是引导出现问题&#xff0c;或选错了启动硬盘所导致的&#xff0c;下面我们来详细分析…

vite配置一个css插件

vite.config.js的plugins执行函数 该例子只是替换一些css,具体内容不重要,主要看形参的运用 // vite-plugin-css.js export default function cssPlugin() {return {name: vite-plugin-css-post, // 插件的名字&#xff0c;Vite 插件必须有名字enforce: post, // 设定插件执…

React+Taro 微信小程序做一个页面,背景图需贴手机屏幕最上边覆盖展示

话不多说 直接上图 第一步 import { getSystemInfoSync } from tarojs/taro;第二步 render() {const cardBanner getImageUrlByGlobal(member-merge-bg.png);const { safeArea, statusBarHeight } getSystemInfoSync();const NAV_BAR_HEIGHT 44;const navBarHeight NAV…

Spring框架学习day4--Spring集成Mybatis(IOC)

Spring集成Mybatis1.添加jar包&#xff08;pom.xml&#xff09;2.配置sqlSessionFactiory&#xff08;spring.xml)3.再service类中注入Dao代理接口4.测试类5文件结构 Spring集成Mybatis Spring集成Mybatis其核心是将SqlSessionFactory交由Spring管理&#xff0c;并由 Spring管理…

【C++ Qt】容器类(GroupBox、TabWidget)内附思维导图 通俗易懂

每日激励&#xff1a;“不设限和自我肯定的心态&#xff1a;I can do all things。 — Stephen Curry” ✍️绪论​&#xff1a; 本章主要介绍了 Qt 中 QGroupBox 与 QTabWidget 控件。QGroupBox 是带标题的分组框&#xff0c;能容纳其他控件&#xff0c;有标题、对齐方式、是否…

SOC-ESP32S3部分:18-串口

飞书文档https://x509p6c8to.feishu.cn/wiki/NqrMw6X8Si6sSqkyPbxcFRxGnid UART全称是通用异步接收器/发送器&#xff0c;ESP32-S3 芯片有 3 个 UART 控制器。每个 UART 控制器可以独立配置波特率、数据位长度、位顺序、停止位位数、奇偶校验位等参数。 串口文档参考&#xf…

https下git拉取gitlab仓库源码

git init 创建仓库 参考下面创建公私秘钥对 注意不要以root用户身份创建公私钥&#xff0c;确保保存在/home/username GitLab配置ssh key - 阿豪聊干货 - 博客园 Your identification has been saved in /home/xxx/.ssh/id_ed25519 Your public key has been saved in /ho…

距离计算范围查找距离排序

一 使用场景 目前基于某个位置查附近的人&#xff0c;附近的商家等等&#xff0c;查出来的结果添加距离&#xff0c;或者查附近多大范围内的人或者商家&#xff0c;然后按距离排序已经是IT界一个很通用的功能了。 二 距离计算搜索(百万点集以下) 2.1 球的定义 2.2 两点之…

PS linux 基础篇1-AXI_DMA

系列文章目录 文章目录 系列文章目录前言一、AXI DMA ip核二、BD工程三、PS linux工程1.使用开源的xilinx_axidma-master工程验证驱动2.按照其他的开源进行就行&#xff0c;没什么写的了 前言 PL与PS之间快速的接口&#xff0c;本文为LOOP回环测试 一、AXI DMA ip核 MM2S mem…

AI大模型学习三十、ubuntu安装comfyui,安装插件,修改返回405 bug,值得一看喔

一、说明 ComfyUI是一个开源的、基于节点的Web应用。它允许用户根据一系列文本提示&#xff08;Prompt&#xff09;生成图像。 ComfyUI使用扩散模型作为基础模型&#xff0c;并结合 ControlNet、Lora和LCM低阶自适应等模型&#xff0c;每个工具都由程序中的一个节点表示 二、开…

Collection集合遍历的三种方法

1.foreach循环遍历 格式&#xff1a;for&#xff08;元素的数据类型 变量名&#xff1a;数组或集合&#xff09;{ } 2.使用迭代器遍历 方法名称&#xff1a;Iterator<E> iterator&#xff08;&#xff09; 说明&#xff1a;返回集合中的迭代器对象&#xff0c;该迭代…