You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android无障碍应用无法执行定时上滑手势,请求排查解决

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-------&gt;".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
    }
}

问题排查与修复建议

  1. 修正无障碍服务meta-data配置
    Manifest中服务的meta-data标签android:name属性错误,必须设置为android.accessibilityservice.AccessibilityService,否则系统无法关联配置文件,导致服务无法正常初始化。修改后代码:

    <meta-data
        android:name="android.accessibilityservice.AccessibilityService"
        android:resource="@xml/accessibility_service_config"/>
    
  2. 移除无效权限请求
    BIND_ACCESSIBILITY_SERVICE是系统权限,不需要通过运行时权限请求,直接从权限列表中移除。Android 14+的FOREGROUND_SERVICE_SPECIAL_USE需要在Manifest中声明,若要让服务长期运行不被系统杀死,需将服务改为前台服务并申请该权限。

  3. 优化手势坐标计算
    直接使用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逻辑不变
    }
    
  4. 验证服务启动状态
    检查onServiceConnected的日志是否输出,确认服务已成功连接。若未输出,说明服务未被系统正确初始化,需检查配置和权限。

  5. 检查手势调度结果
    查看performSwipeUp中STATUS $result的日志,若返回false,说明手势调度失败,需确认无障碍服务已在系统设置中开启,且应用拥有足够权限。

内容的提问来源于stack exchange,提问作者Greyfrog

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 08:34:50