1.无障碍服务在后台运行,并在系统触发回调时接收这些回调事件,如焦点改变、按钮被点击等。
服务可以对特定包程序进行监控,也可以进行所有应用包的监控,需要设置对应的权限
服务类需要继承 AccessibilityService 类
class ForegroundDetectorService : AccessibilityService() {}
2.生命周期
无障碍服务的生命周期由系统进行管理,并遵循已经建立的服务的生命周期.服务的启动需要用户在设置中手动打开来实现.
当打开无障碍服务后会调用 override fun onServiceConnected() {} 方法,可以进行一些操作,如设置无障碍反馈类型的详细配置,如:
override fun onServiceConnected() {
serviceInfo.apply {
// 设置组合反馈类型(语音+震动)
feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN or
AccessibilityServiceInfo.FEEDBACK_HAPTIC
// 配置语音反馈的详细参数
speechRate = 1.2f // 语速
isSpeechShutDown = false // 保持语音开启
}
this.serviceInfo = serviceInfo
}
当关闭无障碍服务后会调用 override fun disableSelf() {} 方法
3.声明权限
无障碍服务需要在 AndroidManifest.xml 中进行权限声明
需要进行权限指定 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" 和无障碍服务指定 android:name="android.accessibilityservice.AccessibilityService" 如下:
<service
android:name=".ForegroundDetectorService"
android:exported="false"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/empty_config"/>
</service>
ForegroundDetectorService 是我们的无障碍服务, empty_config 是服务的配置设置,可以设置接收特定类型的无障碍事件,只监听特定应用包,指定时间内只获取一次类型的事件,检查窗口内容等
如下:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeWindowStateChanged"
android:canRetrieveWindowContent="true"
android:accessibilityFlags="flagDefault"
android:description="@string/accessibility_service_desc"/>
可配置的项如下:
android:description 相关数据的描述文本
android:summary 摘要
android:settingsActivity 允许用户修改此服务设置的活动的完全限定类名。
android:accessibilityEventTypes 此服务希望接收的事件类型
android:packageNames 希望接收的包,逗号分隔,如果不设置则接收所有包
android:accessibilityFeedbackType 服务提供的反馈类型
android:notificationTimeout 两个相同事件类型的最短发送周期,单位是毫秒
android:accessibilityFlags 附加标志(反馈类型)
android:canRetrieveWindowContent 是否需要获取活动窗口内容
android:canRequestTouchExplorationMode 是否支持触摸模式,在此模式下,触摸的项目会被朗读出来,用户界面可以通过手势进行探索
android:canRequestEnhancedWebAccessibility 是否支持网页无障碍增强功能
android:canRequestFilterKeyEvents 是否需要请求过滤关键事件
android:canControlMagnification 是否可以控制显示放大
android:canPerformGestures 是否可以执行手势
android:canRequestFingerprintGestures 是否获取从之吻传感器获取手势
android:nonInteractiveUiTimeout 推荐的超时毫秒
android:interactiveUiTimeout
android:animatedImageDrawable 是否展示无障碍服务的使用动画,用于帮助用户如何使用它
android:htmlDescription Html 描述,展示无障碍服务的快捷方式使用,可用性和限制
android:canTakeScreenshot 是否需要能够截取屏幕图片
android:isAccessibilityTool 是否使用辅助功能来帮助残障用户
android:tileService 完全限定类名,与无障碍快捷方式 android.service.quicksettings.TileService 一对一映射
android:intro 无障碍快捷方式目标的详细介绍,包括目的或行为
4. 无障碍服务的事件类型
视图被点击(如按钮、复选框)
AccessibilityEvent.TYPE_VIEW_CLICKED
视图被长按
AccessibilityEvent.TYPE_VIEW_LONG_CLICKED
视图获得焦点(如输入框被选中)
AccessibilityEvent.TYPE_VIEW_FOCUSED
列表项或下拉选项被选中
AccessibilityEvent.TYPE_VIEW_SELECTED
视图文本内容发生变化(如输入框文字修改)
AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
窗口状态变化(如弹出对话框或菜单)
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
通知栏状态变化
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED
触摸浏览手势开始/结束(视障用户手势操作)
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START
触摸浏览手势开始/结束(视障用户手势操作)
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END
鼠标悬停进入/离开视图(支持鼠标设备)
AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
鼠标悬停进入/离开视图(支持鼠标设备)
AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
视图发生滚动
AccessibilityEvent.TYPE_VIEW_SCROLLED
文本选中范围变化(如编辑框选中文字)
AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
窗口内容或布局结构变化
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
应用主动触发的无障碍通知(如语音提示)
AccessibilityEvent.TYPE_ANNOUNCEMENT
系统手势监测开始/结束
AccessibilityEvent.TYPE_GESTURE_DETECTION_START
系统手势监测开始/结束
AccessibilityEvent.TYPE_GESTURE_DETECTION_END
触摸交互开始/结束(全局触摸事件)
AccessibilityEvent.TYPE_TOUCH_INTERACTION_START
触摸交互开始/结束(全局触摸事件)
AccessibilityEvent.TYPE_TOUCH_INTERACTION_END
视图获得/失去无障碍焦点(区别于普通焦点)
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
屏幕窗口层级变化(如多任务切换)
AccessibilityEvent.TYPE_WINDOWS_CHANGED
视图获得/失去无障碍焦点(区别于普通焦点)
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
5.无障碍的反馈类型
声音反馈 通过非语音的提示音(如蜂鸣声、音效)提供反馈
AccessibilityServiceInfo.FEEDBACK_AUDIBLE
震动反馈 通过设备振动提供触觉反馈
AccessibilityServiceInfo.FEEDBACK_HAPTIC
语音反馈 通过语音合成(TTS)朗读文字内容(最常用的无障碍反馈方式)
AccessibilityServiceInfo.FEEDBACK_SPOKEN
视觉反馈 通过界面元素变化(如高亮、弹窗)提供视觉提示
AccessibilityServiceInfo.FEEDBACK_VISUAL
通用反馈 未指定具体类型的默认反馈机制
AccessibilityServiceInfo.FEEDBACK_GENERIC
盲文反馈 通过连接盲文点字显示器输出内容(针对视障用户)
AccessibilityServiceInfo.FEEDBACK_BRAILLE
例子:
package com.example.footprint
//通知
//协程基础包
//协程挂起函数支持
import android.accessibilityservice.AccessibilityService
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.PixelFormat
import android.os.Build
import android.util.Log
import android.view.Gravity
import android.view.WindowManager
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
import android.widget.TextView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
import android.graphics.Color
//无障碍服务
class ForegroundDetectorService : AccessibilityService() {
// 定义协程作用域
//private val scope = CoroutineScope(Dispatchers.IO)
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.IO + job)
//悬浮窗相关
private lateinit var windowManager: WindowManager
private var overlayText: TextView? = null
//无障碍服务中的事件
override fun onAccessibilityEvent(event: AccessibilityEvent) {
Log.d("ForegroundDetector", "${event.eventType}")
if (event.eventType == TYPE_WINDOW_STATE_CHANGED) {
}
//窗口状态变化(如弹出对话框或菜单)
if (event.eventType == TYPE_WINDOW_STATE_CHANGED) {
val currentApp = event.packageName?.toString()
val appName = currentApp?.let { packageName ->
try {
// 通过PackageManager获取应用名
val pm = this@ForegroundDetectorService.packageManager
pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)).toString()
} catch (e: Exception) {
packageName // 失败时返回包名
}
} ?: "null"
val deviceModel = Build.MANUFACTURER + " " + Build.MODEL
// 发送通知
showNotification("前台应用已更改: $currentApp", this)
//网络请求
val prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE)
val serverUrl = prefs.getString("SERVER_URL", "https://footprint.codevtool.com/updata.php")
//使用协程进行异步网络请求,防止 ANR(应用无响应)错误
scope.launch {
// 执行网络请求
// 执行网络请求
try {
val url = URL(serverUrl)
(url.openConnection() as HttpURLConnection).apply {
requestMethod = "POST"
doOutput = true
OutputStreamWriter(outputStream).use {
it.write("手机,${currentApp},${appName},${deviceModel}")
}
inputStream.bufferedReader().use {
Log.d("Network", "响应: ${it.readText()}")
}
}
} catch (e: Exception) {
Log.e("Network", "请求失败", e)
}
}
}
}
override fun onInterrupt() {}
override fun onServiceConnected() {
// 可选:配置无障碍服务参数
}
override fun onCreate() {
super.onCreate()
// 初始化窗口管理器
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
}
override fun onDestroy() {
super.onDestroy()
// 取消所有协程以避免内存泄漏
// 正确地取消所有子协程
job.cancel()
//销毁弹窗视图
overlayText?.takeIf { it.parent != null }?.let {
windowManager.removeView(it)
}
}
//展示无障碍格式的悬浮视图
private fun showOverlay(text: String) {
// 移除旧视图
overlayText?.takeIf { it.parent != null }?.let {
windowManager.removeView(it)
}
// 创建新视图
TextView(this).apply {
setText(text)
setBackgroundColor(0x66000000)
setTextColor(Color.WHITE)
setPadding(20, 10, 20, 10)
WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
).also { params ->
params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
params.y = 100
windowManager.addView(this, params)
overlayText = this
}
// 3秒后自动消失
postDelayed({
if (parent != null) windowManager.removeView(this)
}, 3000)
}
}
// 显示通知的辅助方法
private fun showNotification(message: String, context: Context) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channelId = "foreground_detector_channel"
//因为 模块级build.gradle 中配置的 minSdkVersion 已设置为26(Android 8.0/Oreo)或更高版本 因此条件判断Build.VERSION.SDK_INT >= Build.VERSION_CODES.O始终为真,属于冗余代码
//VERSION_CODES.O对应API级别26
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelId, "Foreground Detector Channel", NotificationManager.IMPORTANCE_DEFAULT).apply {
setSound(null, null) // 禁用声音
enableVibration(false) // 禁用震动
lockscreenVisibility = Notification.VISIBILITY_SECRET // 锁屏不显示
}
notificationManager.createNotificationChannel(channel)
}
val notification = Notification.Builder(context, channelId)
.setContentTitle("前台应用更改通知")
.setContentText(message)
.setSmallIcon(android.R.drawable.ic_dialog_info) // 替换为你的图标资源
.build()
notificationManager.notify(1, notification)
}
}