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

Android(minSDK30)跨应用模拟屏幕点击输入的实现方案咨询

问题:Android高版本实现跨应用虚拟屏幕点击的可行方案

背景

我正在开发一款类似AnyDesk的远程访问应用,支持通过浏览器在局域网内访问并操控移动设备。目前已实现通过WebSocket将设备画面实时传输至PC浏览器,但无法模拟跨应用的屏幕点击输入——PC端通过WebSocket发送的坐标能在Android代码中接收,但无法触发点击事件。

开发环境

  • Android Studio Hedgehog
  • Java Gradle项目
  • minSDK版本:30

已尝试的无效方案

1. 使用Instrumentation

代码实现:

public class InputSimulator {
    public static void simulateTap (int x, int y) {
        Instrumentation inst = new Instrumentation();
        long timeDown = SystemClock.uptimeMillis();
        long eventTime = timeDown + 100;
        MotionEvent motionEventDown = MotionEvent.obtain(timeDown,eventTime,MotionEvent.ACTION_DOWN,x,y,0);
        inst.sendPointerSync(motionEventDown);
        MotionEvent motionEventUP = MotionEvent.obtain(timeDown,eventTime,MotionEvent.ACTION_UP,x,y,0);
        inst.sendPointerSync(motionEventUp);
        motionEventDown.recycle();
        motionEventUp.recycle();
    }
}

问题:因Android安全限制(Android 8.0以上禁止)抛出异常:

java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission

在Manifest中添加该权限时,提示:

Permissions with the protection level signature, privileged or signatureOrSystem are only granted to system apps

该权限属于系统级权限,普通应用无法获取。

2. 使用Runtime执行input命令

代码实现:

public class InputSimulator {
    public static void simulateTap (int x, int y) {
        try {
            Process process = Runtime.getRuntime().exec("input " + x + " " + y);
            int exitCode = process.waitFor();
            Log.d("INPUT", "COMMAND CODE: " + exitCode); // exitCode is 0 but no tap
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

问题:命令执行返回码为0,但未触发任何点击操作。(注:原代码缺少tap指令,这是可能的原因之一,但即使补充后,非Root设备下普通应用也无法执行该命令)

3. 尝试AccessibilityService但不知如何实现

已创建AccessibilityService的基础框架,但不清楚如何通过它实现坐标点击功能。

核心疑问

在Android 11+(minSDK30)的环境下,实现跨应用的虚拟屏幕点击,是否有无需Root、无需安装为系统应用的可行方案?如果必须Root或系统应用,有没有其他替代思路?


解决方案

方案一:正确实现AccessibilityService(非Root/非系统应用可行)

这是当前高版本Android中,普通应用实现跨应用模拟输入的唯一合法途径,步骤如下:

  1. 创建AccessibilityService类
public class RemoteControlAccessibilityService extends AccessibilityService {
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        // 无需处理事件,仅需保持服务运行
    }

    @Override
    public void onInterrupt() {
        // 服务中断处理
    }

    // 对外提供模拟点击的方法
    public void simulateTap(int x, int y) {
        // 创建GestureDescription.Builder
        GestureDescription.Builder builder = new GestureDescription.Builder();
        // 构建点击路径:按下->抬起,持续100ms
        Path path = new Path();
        path.moveTo(x, y);
        // 添加按下动作(0ms时)
        builder.addStroke(new GestureDescription.StrokeDescription(
                path,
                0,
                100
        ));
        // 执行手势
        dispatchGesture(builder.build(), new GestureResultCallback() {
            @Override
            public void onCompleted(GestureDescription gestureDescription) {
                super.onCompleted(gestureDescription);
                Log.d("ACCESSIBILITY", "点击执行完成");
            }

            @Override
            public void onCancelled(GestureDescription gestureDescription) {
                super.onCancelled(gestureDescription);
                Log.e("ACCESSIBILITY", "点击被取消");
            }
        }, null);
    }
}
  1. 在Manifest中注册服务
<service
    android:name=".RemoteControlAccessibilityService"
    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>
  1. 创建AccessibilityService配置文件
    在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:description="@string/accessibility_service_description" />

在strings.xml中添加描述:

<string name="accessibility_service_description">远程控制需要开启无障碍权限,用于模拟屏幕点击操作</string>
  1. 引导用户开启无障碍权限
    普通应用无法自动获取无障碍权限,必须引导用户手动开启:
// 检查无障碍权限是否开启
private boolean isAccessibilityServiceEnabled() {
    int accessibilityEnabled = 0;
    final String serviceName = getPackageName() + "/" + RemoteControlAccessibilityService.class.getName();
    try {
        accessibilityEnabled = Settings.Secure.getInt(
                getContentResolver(),
                Settings.Secure.ACCESSIBILITY_ENABLED
        );
    } catch (Settings.SettingNotFoundException e) {
        e.printStackTrace();
    }
    TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(':');
    if (accessibilityEnabled == 1) {
        String settingValue = Settings.Secure.getString(
                getContentResolver(),
                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
        );
        if (settingValue != null) {
            splitter.setString(settingValue);
            while (splitter.hasNext()) {
                String accessibilityService = splitter.next();
                if (accessibilityService.equalsIgnoreCase(serviceName)) {
                    return true;
                }
            }
        }
    }
    return false;
}

// 引导用户开启权限
private void requestAccessibilityPermission() {
    Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
    startActivity(intent);
}

方案二:Root设备下使用input命令(补充)

如果你的应用目标用户包含Root设备用户,可以修正input命令的执行方式:

public static void simulateTapRoot(int x, int y) {
    try {
        // 需要以root身份执行
        Process process = Runtime.getRuntime().exec("su");
        OutputStream outputStream = process.getOutputStream();
        outputStream.write(("input tap " + x + " " + y + "\n").getBytes());
        outputStream.flush();
        outputStream.close();
        process.waitFor();
    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
    }
}

注意:原代码缺少tap指令,这是之前执行无效果的原因之一,但非Root设备下普通应用无法执行input命令。

方案三:系统应用(仅适合定制ROM场景)

如果你的应用可以作为系统应用安装(通过签名或放入/system/app目录),可以使用Instrumentation或直接调用InputManager,但这仅适用于定制ROM或企业级场景,普通开发者无法实现。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 03:57:46