文章目录
- 第一部分:无障碍服务基础
- 1.1 无障碍服务概述
- 核心功能:
- 1.2 基本原理与架构
- 1.3 开发环境配置
- 所需工具:
- 关键依赖:
- 第二部分:创建基础无障碍服务
- 2.1 服务声明配置
- 2.2 服务配置文件
- 关键属性说明:
- 2.3 实现服务类
- 第三部分:高级功能实现
- 3.1 节点查找与操作
- 常用查找方法:
- 节点操作示例:
- 3.2 手势模拟
- 3.3 全局事件监听
- 第四部分:实战案例开发
- 4.1 自动填写表单
- 4.2 消息自动回复
- 4.3 游戏自动化辅助
- 第五部分:调试与优化
- 5.1 调试技巧
- ADB调试命令:
- 日志记录最佳实践:
- 5.2 性能优化
- 优化建议:
- 优化示例:
- 第六部分:发布与安全
- 6.1 权限与隐私
- 必要权限声明:
- 隐私注意事项:
- 6.2 发布流程
- 第七部分:高级主题
- 7.1 与其他技术的结合
- 与Tasker集成:
- 使用机器学习:
- 7.2 跨版本兼容性处理
- 版本差异处理表:
- 兼容性代码示例:

第一部分:无障碍服务基础
1.1 无障碍服务概述
安卓无障碍服务(Accessibility Service)是一种特殊类型的服务,旨在帮助残障用户或需要辅助功能的用户更好地使用设备。但它的功能远不止于此,开发者可以利用它实现自动化操作、界面监控和交互等功能。
核心功能:
- 界面内容访问:获取屏幕上的UI元素信息
- 自动化操作:模拟点击、滑动等用户操作
- 事件监控:监听窗口变化、通知、焦点改变等系统事件
- 增强交互:为特定应用提供定制化辅助功能
1.2 基本原理与架构
1.3 开发环境配置
所需工具:
- Android Studio最新版
- 安卓设备或模拟器(API 16+)
- ADB调试工具
关键依赖:
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
}
第二部分:创建基础无障碍服务
2.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/service_config" />
</service>
2.2 服务配置文件
创建res/xml/service_config.xml
:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_service_description"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows"
android:canRetrieveWindowContent="true"
android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"
android:canRequestFilterKeyEvents="true"
android:canPerformGestures="true"
android:notificationTimeout="100"
android:packageNames="com.example.targetapp" />
关键属性说明:
accessibilityEventTypes
:监听的事件类型packageNames
:指定监控的应用包名(可选)canPerformGestures
:允许执行手势操作(API 24+)
2.3 实现服务类
创建基础服务类MyAccessibilityService.kt
:
class MyAccessibilityService : AccessibilityService() {
override fun onServiceConnected() {
Log.d("A11yService", "无障碍服务已连接")
// 可以在此处进行服务配置更新
val info = AccessibilityServiceInfo().apply {
eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC
notificationTimeout = 100
flags = AccessibilityServiceInfo.DEFAULT or
AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS
}
this.serviceInfo = info
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
event ?: return
when (event.eventType) {
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> {
handleWindowChange(event)
}
AccessibilityEvent.TYPE_VIEW_CLICKED -> {
handleViewClick(event)
}
}
}
override fun onInterrupt() {
Log.w("A11yService", "无障碍服务被中断")
}
private fun handleWindowChange(event: AccessibilityEvent) {
val rootNode = rootInActiveWindow ?: return
Log.d("A11yService", "窗口变化: ${event.packageName}")
// 遍历视图树
traverseNode(rootNode)
}
private fun traverseNode(node: AccessibilityNodeInfo, depth: Int = 0) {
if (node.childCount == 0) {
Log.d("A11yTree", "${" ".repeat(depth)}${node.viewIdResourceName}")
return
}
for (i in 0 until node.childCount) {
node.getChild(i)?.let { child ->
traverseNode(child, depth + 1)
child.recycle()
}
}
}
}
第三部分:高级功能实现
3.1 节点查找与操作
常用查找方法:
fun findNodes(root: AccessibilityNodeInfo) {
// 通过文本查找
val byText = root.findAccessibilityNodeInfosByText("搜索")
// 通过View ID查找(全限定ID)
val byId = root.findAccessibilityNodeInfosByViewId("com.example.app:id/btnSubmit")
// 通过类名查找
val editTexts = mutableListOf<AccessibilityNodeInfo>()
val queue: Queue<AccessibilityNodeInfo> = LinkedList()
queue.add(root)
while (queue.isNotEmpty()) {
val current = queue.poll()
if (current.className == "android.widget.EditText") {
editTexts.add(current)
}
for (i in 0 until current.childCount) {
current.getChild(i)?.let { queue.add(it) }
}
}
}
节点操作示例:
fun performActions(node: AccessibilityNodeInfo) {
// 点击操作
if (node.isClickable) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
// 文本输入
val arguments = Bundle().apply {
putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "Hello")
}
node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)
// 焦点控制
node.performAction(AccessibilityNodeInfo.ACTION_FOCUS)
// 滚动操作
node.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
}
3.2 手势模拟
Android支持通过无障碍服务模拟复杂手势:
fun performGesture(service: AccessibilityService) {
val path = Path().apply {
moveTo(100f, 100f) // 起点
lineTo(500f, 100f) // 移动到右侧
lineTo(500f, 500f) // 向下移动
lineTo(100f, 500f) // 向左移动
close() // 闭合路径
}
val gestureBuilder = GestureDescription.Builder()
.addStroke(GestureDescription.StrokeDescription(
path,
0L, // 开始时间
1000L, // 持续时间(毫秒)
false // 是否持续
))
service.dispatchGesture(gestureBuilder.build(), object : AccessibilityService.GestureResultCallback() {
override fun onCompleted(gestureDescription: GestureDescription?) {
Log.d("Gesture", "手势完成")
}
override onCancelled(gestureDescription: GestureDescription?) {
Log.w("Gesture", "手势取消")
}
}, null)
}
3.3 全局事件监听
监听系统级事件:
override fun onAccessibilityEvent(event: AccessibilityEvent) {
when (event.eventType) {
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED -> {
val notificationText = event.text.joinToString()
Log.d("Notification", "新通知: $notificationText")
}
AccessibilityEvent.TYPE_ANNOUNCEMENT -> {
Log.d("Announcement", "系统公告: ${event.text}")
}
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START -> {
Log.d("Touch", "触摸探索开始")
}
}
}
第四部分:实战案例开发
4.1 自动填写表单
class FormFillerService : AccessibilityService() {
private val formData = mapOf(
"username" to "testuser",
"password" to "secure123",
"email" to "test@example.com"
)
override fun onAccessibilityEvent(event: AccessibilityEvent) {
if (event.eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) return
val rootNode = rootInActiveWindow ?: return
formData.forEach { (fieldName, value) ->
val nodes = rootNode.findAccessibilityNodeInfosByViewId("com.example.app:id/$fieldName")
nodes.firstOrNull()?.let { field ->
if (field.className == "android.widget.EditText") {
val args = Bundle().apply {
putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, value)
}
field.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args)
}
}
}
// 自动提交表单
rootNode.findAccessibilityNodeInfosByViewId("com.example.app:id/submit")
.firstOrNull()
?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
4.2 消息自动回复
class AutoReplyService : AccessibilityService() {
private val replyMessages = listOf(
"我正在开会,稍后回复您",
"好的,收到",
"谢谢通知"
)
override fun onAccessibilityEvent(event: AccessibilityEvent) {
if (event.packageName != "com.whatsapp") return
when (event.eventType) {
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED -> {
// 处理通知事件
val messages = event.text.filter { it.contains("发来消息") }
if (messages.isNotEmpty()) {
replyToLatestMessage()
}
}
AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> {
// 处理界面文本变化
if (isChatOpen()) {
autoReplyInChat()
}
}
}
}
private fun replyToLatestMessage() {
// 实现打开聊天界面并回复的逻辑
}
private fun isChatOpen(): Boolean {
// 检测当前是否在聊天界面
}
private fun autoReplyInChat() {
val root = rootInActiveWindow ?: return
val messageNodes = root.findAccessibilityNodeInfosByViewId("com.whatsapp:id/message_text")
// 获取最后一条消息
val lastMessage = messageNodes.lastOrNull()?.text ?: return
// 随机选择回复内容
val randomReply = replyMessages.random()
// 找到输入框并发送
root.findAccessibilityNodeInfosByViewId("com.whatsapp:id/entry")
.firstOrNull()
?.let { input ->
val args = Bundle().apply {
putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, randomReply)
}
input.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args)
// 发送消息
root.findAccessibilityNodeInfosByViewId("com.whatsapp:id/send")
.firstOrNull()
?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
}
4.3 游戏自动化辅助
class GameHelperService : AccessibilityService() {
private var isRunning = false
private val handler = Handler(Looper.getMainLooper())
private val clickRunnable = object : Runnable {
override fun run() {
performAutoClick()
if (isRunning) {
handler.postDelayed(this, 1000) // 每秒点击一次
}
}
}
override fun onServiceConnected() {
val info = AccessibilityServiceInfo().apply {
eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC
flags = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
}
serviceInfo = info
}
override fun onAccessibilityEvent(event: AccessibilityEvent) {
if (event.packageName != "com.game.package") return
when (event.eventType) {
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> {
checkGameState()
}
}
}
private fun checkGameState() {
val root = rootInActiveWindow ?: return
val battleNode = root.findAccessibilityNodeInfosByViewId("com.game.package:id/battle_indicator")
if (battleNode.isNotEmpty()) {
startAutoClicking()
} else {
stopAutoClicking()
}
}
private fun startAutoClicking() {
if (!isRunning) {
isRunning = true
handler.post(clickRunnable)
}
}
private fun stopAutoClicking() {
isRunning = false
handler.removeCallbacks(clickRunnable)
}
private fun performAutoClick() {
val root = rootInActiveWindow ?: return
val attackBtn = root.findAccessibilityNodeInfosByViewId("com.game.package:id/attack_button")
.firstOrNull()
attackBtn?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
// 随机位置点击,避免被检测为机器人
if (Math.random() < 0.3) {
val randomX = (100..500).random()
val randomY = (200..800).random()
dispatchGesture(createClickGesture(randomX, randomY), null, null)
}
}
private fun createClickGesture(x: Int, y: Int): GestureDescription {
val clickPath = Path().apply {
moveTo(x.toFloat(), y.toFloat())
}
return GestureDescription.Builder()
.addStroke(GestureDescription.StrokeDescription(
clickPath, 0, 50))
.build()
}
}
第五部分:调试与优化
5.1 调试技巧
ADB调试命令:
# 查看已启用的无障碍服务
adb shell settings get secure enabled_accessibility_services
# 启用服务
adb shell settings put secure enabled_accessibility_services com.example.pkg/.MyAccessibilityService
# 查看无障碍事件日志
adb shell logcat -s AccessibilityEvent
日志记录最佳实践:
fun logNodeInfo(node: AccessibilityNodeInfo) {
val sb = StringBuilder().apply {
append("View ID: ${node.viewIdResourceName}\n")
append("Text: ${node.text}\n")
append("Class: ${node.className}\n")
append("Bounds: ${node.boundsInScreen}\n")
append("Actions: ${node.actionList.joinToString()}\n")
append("ChildCount: ${node.childCount}\n")
}
Log.d("NodeInfo", sb.toString())
}
5.2 性能优化
优化建议:
- 减少遍历深度:只查找必要的节点层级
- 及时回收节点:调用
recycle()
释放资源 - 事件过滤:只监听必要的事件类型
- 延迟处理:对频繁事件使用防抖
- 后台处理:将耗时操作移到工作线程
优化示例:
class OptimizedService : AccessibilityService() {
private val eventQueue = LinkedBlockingQueue<AccessibilityEvent>()
private val workerThread = HandlerThread("EventProcessor").apply { start() }
private val workerHandler = Handler(workerThread.looper)
private val eventProcessor = object : Runnable {
override fun run() {
while (true) {
val event = eventQueue.take()
processEvent(event)
}
}
}
override fun onCreate() {
super.onCreate()
workerHandler.post(eventProcessor)
}
override fun onAccessibilityEvent(event: AccessibilityEvent) {
// 快速将事件加入队列,避免阻塞主线程
eventQueue.put(event)
}
private fun processEvent(event: AccessibilityEvent) {
// 实际处理逻辑
}
override fun onDestroy() {
workerThread.quitSafely()
super.onDestroy()
}
}
第六部分:发布与安全
6.1 权限与隐私
必要权限声明:
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
隐私注意事项:
- 明确告知用户:在隐私政策中说明数据收集范围
- 最小权限原则:只请求必要的权限
- 敏感数据处理:避免收集密码等敏感信息
- 数据加密:对存储的日志和数据进行加密
6.2 发布流程
-
测试阶段:
- 在不同安卓版本上测试
- 在各种品牌设备上测试(特别是国产ROM)
- 测试电池消耗情况
-
应用商店要求:
- 明确说明是无障碍辅助工具
- 提供详细的使用说明视频
- 如果是自动化工具,需遵守各商店政策
-
持续更新:
- 定期适配新安卓版本
- 针对流行应用的特殊适配
- 根据用户反馈优化功能
第七部分:高级主题
7.1 与其他技术的结合
与Tasker集成:
// 接收Tasker的广播意图
private val taskerReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == "net.dinglisch.android.tasker.ACTION_TRIGGER") {
val task = intent.getStringExtra("task")
when (task) {
"start_automation" -> startAutomation()
"stop_automation" -> stopAutomation()
}
}
}
}
override fun onCreate() {
super.onCreate()
registerReceiver(taskerReceiver, IntentFilter("net.dinglisch.android.tasker.ACTION_TRIGGER"))
}
使用机器学习:
// 使用ML Kit识别屏幕内容
fun detectTextFromScreen(bitmap: Bitmap): String {
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
val image = InputImage.fromBitmap(bitmap, 0)
return try {
val result = recognizer.process(image).await()
result.text
} catch (e: Exception) {
Log.e("ML", "识别失败", e)
""
}
}
// 截图并处理
fun captureAndAnalyze() {
val projection = MediaProjectionManager.createScreenCaptureIntent()
// 需要先获取用户授权...
val imageReader = ImageReader.newInstance(
screenWidth, screenHeight,
PixelFormat.RGBA_8888, 2
)
imageReader.setOnImageAvailableListener({ reader ->
val image = reader.acquireLatestImage()
// 转换为Bitmap并传递给识别器
val text = detectTextFromScreen(convertImageToBitmap(image))
Log.d("ScreenText", "识别结果: $text")
image.close()
}, handler)
}
7.2 跨版本兼容性处理
版本差异处理表:
功能 | API 16-22 | API 23-28 | API 29+ |
---|---|---|---|
节点信息获取 | 基本支持 | 增强支持 | 受限 |
手势模拟 | 不支持 | 部分支持 | 完全支持 |
隐私限制 | 无 | 部分 | 严格 |
后台服务 | 允许 | 限制 | 严格限制 |
兼容性代码示例:
fun performActionCompat(node: AccessibilityNodeInfo, action: Int, args: Bundle? = null): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
node.performAction(action, args)
} else {
node.performAction(action)
}
}
fun getNodeTextCompat(node: AccessibilityNodeInfo): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
node.text?.toString() ?: ""
} else {
node.text ?: ""
}
}