android無障礙服務

1.無障礙服務在后臺運行,并在系統(tǒng)觸發(fā)回調時接收這些回調事件,如焦點改變、按鈕被點擊等。

服務可以對特定包程序進行監(jiān)控,也可以進行所有應用包的監(jiān)控,需要設置對應的權限

服務類需要繼承 AccessibilityService 類

class ForegroundDetectorService : AccessibilityService() {}

2.生命周期

無障礙服務的生命周期由系統(tǒng)進行管理,并遵循已經建立的服務的生命周期.服務的啟動需要用戶在設置中手動打開來實現.

當打開無障礙服務后會調用 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 是服務的配置設置,可以設置接收特定類型的無障礙事件,只監(jiān)聽特定應用包,指定時間內只獲取一次類型的事件,檢查窗口內容等

如下:

<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 兩個相同事件類型的最短發(fā)送周期,單位是毫秒

android:accessibilityFlags 附加標志(反饋類型)

android:canRetrieveWindowContent 是否需要獲取活動窗口內容

android:canRequestTouchExplorationMode 是否支持觸摸模式,在此模式下,觸摸的項目會被朗讀出來,用戶界面可以通過手勢進行探索

android:canRequestEnhancedWebAccessibility 是否支持網頁無障礙增強功能

android:canRequestFilterKeyEvents 是否需要請求過濾關鍵事件

android:canControlMagnification 是否可以控制顯示放大

android:canPerformGestures 是否可以執(zhí)行手勢

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

視圖文本內容發(fā)生變化(如輸入框文字修改)

AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED

窗口狀態(tài)變化(如彈出對話框或菜單)

AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED

通知欄狀態(tài)變化

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

視圖發(fā)生滾動

AccessibilityEvent.TYPE_VIEW_SCROLLED

文本選中范圍變化(如編輯框選中文字)

AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED

窗口內容或布局結構變化

AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED

應用主動觸發(fā)的無障礙通知(如語音提示)

AccessibilityEvent.TYPE_ANNOUNCEMENT

系統(tǒng)手勢監(jiān)測開始/結束

AccessibilityEvent.TYPE_GESTURE_DETECTION_START

系統(tǒng)手勢監(jiān)測開始/結束

AccessibilityEvent.TYPE_GESTURE_DETECTION_END

觸摸交互開始/結束(全局觸摸事件)

AccessibilityEvent.TYPE_TOUCH_INTERACTION_START

觸摸交互開始/結束(全局觸摸事件)

AccessibilityEvent.TYPE_TOUCH_INTERACTION_END

視圖獲得/失去無障礙焦點(區(qū)別于普通焦點)

AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED

屏幕窗口層級變化(如多任務切換)

AccessibilityEvent.TYPE_WINDOWS_CHANGED

視圖獲得/失去無障礙焦點(區(qū)別于普通焦點)

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) {

? ? ? ? }

? ? ? ? //窗口狀態(tài)變化(如彈出對話框或菜單)

? ? ? ? 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

? ? ? ? ? ? // 發(fā)送通知

? ? ? ? ? ? showNotification("前臺應用已更改: $currentApp", this)

? ? ? ? ? ? //網絡請求

? ? ? ? ? ? val prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE)

? ? ? ? ? ? val serverUrl = prefs.getString("SERVER_URL", "https://footprint.codevtool.com/updata.php")

? ? ? ? ? ? //使用協程進行異步網絡請求,防止 ANR(應用無響應)錯誤

? ? ? ? ? ? scope.launch {

? ? ? ? ? ? // 執(zhí)行網絡請求

? ? ? ? ? ? ? ? // 執(zhí)行網絡請求

? ? ? ? ? ? ? ? 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)

? ? ? ? }

? ? ? ? // 創(chuàng)建新視圖

? ? ? ? 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)

? ? }

}

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容