android无障碍服务

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)

    }

}

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容