Android无障碍应用无法执行定时上滑手势,请求排查解决
我正在开发一款Android应用,目标是每5秒自动执行一次上滑手势,实现对任意打开的应用(比如Instagram Reels、Facebook时间线、Pinterest)的滚动操作。我已经配置了所有必要权限、Android Manifest和无障碍服务,也写了GestureAccessibilityService类,但应用没按预期工作,求排查问题。
accessibility_service_config.xml
<?xml version="1.0" encoding="utf-8"?> <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_service_description" android:canRequestTouchExplorationMode="true" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault" android:canPerformGestures="true" android:accessibilityEventTypes="typeAllMask" android:canRetrieveWindowContent="true" android:notificationTimeout="100"/>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.ThumbD" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".GestureAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" android:exported="false"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService"/> </intent-filter> <meta-data android:name=".GestureAccessibilityService" android:resource="@xml/accessibility_service_config"/> </service> </application> </manifest>
GestureAccessibilityService.kt
class GestureAccessibilityService:AccessibilityService() { private val TAG = "GESTURE" private val handler = Handler(Looper.getMainLooper()) private val gestureRunnable = object : Runnable { override fun run() { performSwipeUp() //dispatch(0f,0f,0f,200f, applicationContext) handler.postDelayed(this, 5000) // Repeat every 5 seconds (5000 milliseconds) } } override fun onAccessibilityEvent(event: AccessibilityEvent?) { } override fun onInterrupt() { // NO-OP } override fun onServiceConnected() { super.onServiceConnected() "onServiceConnected".logd() handler.postDelayed(gestureRunnable, 0) } override fun onDestroy() { super.onDestroy() // Stop the repeating gesture when the service is destroyed handler.removeCallbacks(gestureRunnable) } private fun performSwipeUp() { val displayMetrics = resources.displayMetrics val screenHeight = displayMetrics.heightPixels val gestureDescription = GestureDescription.Builder() .addStroke( GestureDescription.StrokeDescription( Path().apply { moveTo(displayMetrics.widthPixels / 2f, screenHeight * 0.8f) lineTo(displayMetrics.widthPixels / 2f, screenHeight * 0.2f) }, 0, 500 ) ) .build() val result = dispatchGesture(gestureDescription, object : GestureResultCallback() { override fun onCompleted(gestureDescription: GestureDescription?) { super.onCompleted(gestureDescription) "Gesture completed".logd() } override fun onCancelled(gestureDescription: GestureDescription?) { super.onCancelled(gestureDescription) "Gesture cancelled".logd() } }, null) "STATUS $result".logd() } fun dispatch(x: Float, y: Float,xx: Float, yy: Float, context:Context): Boolean { try { val accessibilityManager = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager val isEnabled = accessibilityManager.isEnabled if (isEnabled) { val result = dispatchGesture( buildSwipe(x, y, xx, yy), object : GestureResultCallback() { override fun onCompleted(gestureDescription: GestureDescription?) { super.onCompleted(gestureDescription) "Gesture completed".logd() } override fun onCancelled(gestureDescription: GestureDescription?) { super.onCancelled(gestureDescription) "Gesture cancelled".logd() } }, null ) "STATUS $result".logd() return result } else { // Accessibility service is not enabled return false } }catch (e: Exception) { "Error dispatching gesture: ${e.message}".logd() e.printStackTrace() return false } } private fun buildSwipe( startX: Float, startY: Float, endX: Float, endY: Float ): GestureDescription { val swipePath = Path() swipePath.apply { moveTo(startX, startY) lineTo(endX, endY) } val swipeBuilder = GestureDescription.Builder() swipeBuilder.addStroke(GestureDescription.StrokeDescription(swipePath, 0, 100L)) return swipeBuilder.build() } }
MainActivity.kt
class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) /*Permissions*/ val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { listOf( Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE, Manifest.permission.BIND_ACCESSIBILITY_SERVICE ) } else { listOf(Manifest.permission.BIND_ACCESSIBILITY_SERVICE) } checkPermissions(permissions) if(isAccessibilityServiceEnabled(this)){ "started sevices------->".logd() } } fun permissionAccessServices(view: View){ openAccessibilitySettings(this) } private fun checkPermissions(permissions: List<String>){ PermissionX.init(this) .permissions(permissions) .request { allGranted, grantedList, deniedList -> if (allGranted) { Log.d("Permission","SuccessFull"); } else { Log.d("Permission","Failed $deniedList"); } } } private fun isAccessibilityServiceEnabled(mContext: Context): Boolean { var accessibilityEnabled = 0 val service: String = mContext.packageName + "/" + GestureAccessibilityService::class.java.canonicalName try { accessibilityEnabled = Settings.Secure.getInt( mContext.applicationContext.contentResolver, Settings.Secure.ACCESSIBILITY_ENABLED ) "accessibilityEnabled = $accessibilityEnabled".logd() } catch (e: Settings.SettingNotFoundException) { "Error finding setting, default accessibility to not found: ${e.message}".logd() } val mStringColonSplitter = TextUtils.SimpleStringSplitter(':') if (accessibilityEnabled == 1) { "Accessibility Is Enabled".logd() val settingValue: String = Settings.Secure.getString( mContext.applicationContext.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES ) if (settingValue != null) { mStringColonSplitter.setString(settingValue) while (mStringColonSplitter.hasNext()) { val accessibilityService = mStringColonSplitter.next() "AccessibilityService :: $accessibilityService $service".logd() if (accessibilityService.equals(service, ignoreCase = true)) { "accessibility is switched on!".logd() return true } } } } else { "accessibility is disabled".logd() } return false } }
问题排查与修复建议
修正无障碍服务meta-data配置
Manifest中服务的meta-data标签android:name属性错误,必须设置为android.accessibilityservice.AccessibilityService,否则系统无法关联配置文件,导致服务无法正常初始化。修改后代码:<meta-data android:name="android.accessibilityservice.AccessibilityService" android:resource="@xml/accessibility_service_config"/>移除无效权限请求
BIND_ACCESSIBILITY_SERVICE是系统权限,不需要通过运行时权限请求,直接从权限列表中移除。Android 14+的FOREGROUND_SERVICE_SPECIAL_USE需要在Manifest中声明,若要让服务长期运行不被系统杀死,需将服务改为前台服务并申请该权限。优化手势坐标计算
直接使用displayMetrics获取的高度包含状态栏和导航栏,可能导致手势落在不可交互区域。改用WindowManager获取可用窗口高度:private fun performSwipeUp() { val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager val windowMetrics = windowManager.currentWindowMetrics val insets = windowMetrics.windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()) val usableWidth = windowMetrics.bounds.width() val usableHeight = windowMetrics.bounds.height() - insets.top - insets.bottom val startX = usableWidth / 2f val startY = insets.top + usableHeight * 0.8f val endX = usableWidth / 2f val endY = insets.top + usableHeight * 0.2f val gestureDescription = GestureDescription.Builder() .addStroke( GestureDescription.StrokeDescription( Path().apply { moveTo(startX, startY) lineTo(endX, endY) }, 0, 200 // 缩短手势持续时间,提升兼容性 ) ) .build() // 后续dispatchGesture逻辑不变 }验证服务启动状态
检查onServiceConnected的日志是否输出,确认服务已成功连接。若未输出,说明服务未被系统正确初始化,需检查配置和权限。检查手势调度结果
查看performSwipeUp中STATUS $result的日志,若返回false,说明手势调度失败,需确认无障碍服务已在系统设置中开启,且应用拥有足够权限。
内容的提问来源于stack exchange,提问作者Greyfrog

