如何用Flutter实现跨应用自动点击?求基于uiautomatorviewer的示例代码
实现Flutter跨App自动点击指定ID按钮的方案
一、核心原理
Flutter本身没有直接操作其他应用UI的API,必须依赖Android原生的AccessibilityService(无障碍服务)——这是Android系统提供的系统级辅助服务,用户授权后可监听并操作其他应用的UI元素。你通过uiautomatorviewer获取的控件ID,就是定位目标按钮的关键标识。
二、分步实现
1. 配置Android无障碍服务
在Android项目的src/main/res/xml目录下创建accessibility_service_config.xml文件,配置服务权限:
<?xml version="1.0" encoding="utf-8"?> <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeAllMask" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews|flagRequestTouchExplorationMode" android:canPerformGestures="true" android:canRetrieveWindowContent="true" android:description="@string/accessibility_service_description" />
在src/main/res/values/strings.xml添加服务描述:
<string name="accessibility_service_description">用于自动点击其他应用的指定按钮</string>
2. 编写Android原生AccessibilityService类
在src/main/kotlin/[你的包名]/路径下创建AutoClickService.kt:
package com.yourpackage.name import android.accessibilityservice.AccessibilityService import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo class AutoClickService : AccessibilityService() { // 替换为你从uiautomatorviewer获取的目标控件完整ID(格式:包名:id/控件ID) private val TARGET_BUTTON_ID = "com.target.app:id/btn_submit" // 替换为目标应用的包名 private val TARGET_APP_PACKAGE = "com.target.app" override fun onAccessibilityEvent(event: AccessibilityEvent) { // 监听前台窗口切换,仅处理目标应用的界面 if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { val currentPackage = event.packageName?.toString() if (currentPackage == TARGET_APP_PACKAGE) { findAndClickButton(rootInActiveWindow) } } } private fun findAndClickButton(node: AccessibilityNodeInfo?) { node ?: return // 遍历所有子节点匹配目标ID for (i in 0 until node.childCount) { val childNode = node.getChild(i) childNode ?: continue if (TARGET_BUTTON_ID == childNode.viewIdResourceName) { // 执行点击操作 childNode.performAction(AccessibilityNodeInfo.ACTION_CLICK) return } // 递归查找深层子节点 findAndClickButton(childNode) childNode.recycle() // 回收节点避免内存泄漏 } } override fun onInterrupt() { // 服务被中断时的空实现,可按需添加逻辑 } }
3. 在AndroidManifest中注册服务
在AndroidManifest.xml的<application>标签内添加服务注册代码:
<service android:name=".AutoClickService" 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/accessibility_service_config" /> </service>
4. Flutter端实现权限引导与控制
Flutter代码中添加权限引导按钮,并通过MethodChannel与原生交互:
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('自动点击工具')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () async { // 跳转至系统无障碍设置页面 await const MethodChannel('com.yourpackage.name/accessibility') .invokeMethod('openAccessibilitySettings'); }, child: const Text('开启无障碍权限'), ), const SizedBox(height: 20), ElevatedButton( onPressed: () { // 可扩展:通过MethodChannel向原生传递动态目标ID或启停指令 }, child: const Text('启动自动点击'), ), ], ), ), ), ); } }
修改Android原生MainActivity.kt,添加MethodChannel处理逻辑:
package com.yourpackage.name import android.content.Intent import android.provider.Settings import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class MainActivity : FlutterActivity() { private val CHANNEL = "com.yourpackage.name/accessibility" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> when (call.method) { "openAccessibilitySettings" -> { val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS) startActivity(intent) result.success(null) } else -> result.notImplemented() } } } }
三、关键注意事项
- 权限要求:必须由用户手动开启无障碍权限,应用无法自动获取
- 控件ID格式:必须使用uiautomatorviewer获取的完整ID(包含包名前缀),不能仅使用控件ID后缀
- 包名准确性:目标应用的包名必须准确,否则服务无法识别前台应用
- 内存管理:遍历
AccessibilityNodeInfo时必须调用recycle()回收节点,避免内存泄漏
内容的提问来源于stack exchange,提问作者MINH-HAI
相关产品推荐
相关产品推荐

