android跨进程事件注入(程序模拟用户输入)

本文详细介绍了如何通过模拟用户输入来实现自动化操作,包括按键和触摸屏事件的模拟。重点讲解了通过向Linux底层写入事件来模拟点击和触摸,探讨了不同设备间的协议差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载请注明出处

早想写这篇,一直没空,现在总结下。
需求:
需要在程序内模拟用户输入,比如点击屏幕,或者输入键盘。模拟用户的滑动等。具体的需求,比如测试的时候,测试打开浏览器1000次。或者通过网络发送命令给手机,在手机上执行点击或者输入。再或者,平板和蓝牙鼠标通过蓝牙通信,通过鼠标让平板上的鼠标能移动和点击。这些都需要用到事件注入。
分析:
模拟用户输入的方式有几种,一是monkeyrunner,这个的原理是在PC上,通过python调用android的一些包,然后通过机器的调试端口和机器通信,机器接收到相应的命令后再往硬件写入相应的事件。这个常用语测试。并且,不是所有的机器都开了调试端口,并且需要连接PC。二是IwindowManager的injectInputEventNoWait,这个调用方便,也很简单,但是从1.5(1.6?)后android系统做了限制,不允许跨进程注入,这个方法只能在自己这个程序内用,home出去就不行了。三是直接往linux底层/dev/input/event*写事件,这个实现起来复杂,需要root权限,但是却能实现跨进程,比如蓝牙鼠标的需求,也只能用这种方法实现。讲这个具体实现的不多,本文详细介绍下。对linux了解些的人应该一看就懂知道怎么回事。android上实现只不过有些地方比较绕而已。
1.android界面点击事件流程。
有必要先说下android界面捕获事件的流程。用户在屏幕上点击一下后,程序里面的OnClickListener是怎样收到这个事件的。大致流程如下
用户点击-(硬件驱动部分)硬件产生一个中断,往/dev/input/event*写入一个相应的信号->jni部分,android循环读取/dev/input/event*的事件,再分发给WindowManagerServer,最后再发到相应的ViewGroup和View。这里可以通过往/dev/input/event*写信号的方式,来达到模拟事件的目的,接下来关心的就是信号的协议了。
2.按键协议分析
连接手机,adb shell,输入getevent,关掉手机的自动旋转屏幕,按一下手机的menu键,会看到类似如下输出。


linux上的硬件会分别对应/dev/input/event*,这里的*一般是0-9的数字,getevent开头那部分已经显示,event2是keypad,event1是touchscreen等。
最下面的0001 008b 00000001分别叫做type,code,value。
参考linux input,type 对应 【#define EV_KEY 0x01】,code 对应【#define KEY_MENU 139】(8b == 139),value 1表示按下,0表示松开。那么按键的协议就很清楚了,试着在adb shell里面输入“sendevent /dev/input/event2 0 139 1”和”sendevent /dev/input/event2 0 139 2“后发现menu弹出来了,和按键的效果一样。
3.触摸协议分析
ok,来点复杂的。触摸协议稍微麻烦点,分单点触摸和多点触摸。
先说单点触摸,打开模拟器。同样关闭自动旋屏,进入adb shell。鼠标点击一下屏幕,要足够快,不然数据太多。得到输出和下面类似。

 
可以看到模拟器上的设备数少了很多,单点触摸的协议每次点击会写6条信号。参考linux_input对应的值以及分析分别如下
/dev/input/event0: 0003 0000 00000117   EV_ABS  ABS_X  0x117
触摸点的x坐标
/dev/input/event0: 0003 0001 0000020f    EV_ABS  ABS_Y  0x20f
触摸点的y坐标
/dev/input/event0: 0001 014a 00000001   EV_KEY  BTN_TOUCH 1
touch down
/dev/input/event0: 0000 0000 00000000   EV_SYN 0 0
同步信号量
/dev/input/event0: 0001 014a 00000000   EV_KEY  BTN_TOUCH 0
touch up
/dev/input/event0: 0000 0000 00000000   EV_SYN 0 0

同步信号量
使用4.0的模拟器,settings-developer options-show touches 和pointer locations勾上后,可以看到点击的轨迹,adb shell后分别用sendevent输入以上消息,可以看到屏幕上出现点击效果。
再看多点触摸协议,使用adb shell 进入手机,关掉旋屏,getevent后快速点一下屏幕,可以看到类似如下输出。(每个厂商的协议可能不同,以下数据为小米1的)
第一个坐标
/dev/input/event1: 0003 0039 00000000 EV_ABS ABS_MT_TRACKING_ID 0
/dev/input/event1: 0003 0035 000001b0 EV_ABS ABS_MT_POSITION_X 0x1b0
/dev/input/event1: 0003 0036 000000d7 EV_ABS ABS_MT_POSITION_Y 0xd7
/dev/input/event1: 0003 003a 00000001 EV_ABS  ABS_MT_PRESSURE 0x1
/dev/input/event1: 0003 0032 00000001 EV_ABS ABS_MT_WIDTH_MAJOR 0x1
/dev/input/event1: 0000 0002 00000000 EV_SYN SYN_MT_REPORT 0
/dev/input/event1: 0000 0000 00000000 EV_SYN SYN_REPORT 0
第二个坐标
/dev/input/event1: 0003 0039 00000000 EV_ABS ABS_MT_TRACKING_ID 0
/dev/input/event1: 0003 0035 000001b0 EV_ABS ABS_MT_POSITION_X 0x1b0
/dev/input/event1: 0003 0036 000000d7 EV_ABS ABS_MT_POSITION_Y 0xd7
/dev/input/event1: 0003 003a 00000001 EV_ABS  ABS_MT_PRESSURE 0x1
/dev/input/event1: 0003 0032 00000001 EV_ABS ABS_MT_WIDTH_MAJOR 0x1
/dev/input/event1: 0000 0002 00000000 EV_SYN SYN_MT_REPORT 0
/dev/input/event1: 0000 0000 00000000 EV_SYN SYN_REPORT 0
第三个坐标
/dev/input/event1: 0003 0039 00000000 EV_ABS ABS_MT_TRACKING_ID 0
/dev/input/event1: 0003 0035 00000191 EV_ABS ABS_MT_POSITION_X 0x191
/dev/input/event1: 0003 0036 00000098 EV_ABS ABS_MT_POSITION_Y 0x98
/dev/input/event1: 0003 003a 00000001 EV_ABS  ABS_MT_PRESSURE 0x1
/dev/input/event1: 0003 0032 00000001 EV_ABS ABS_MT_WIDTH_MAJOR 0x1
/dev/input/event1: 0000 0002 00000000 EV_SYN SYN_MT_REPORT 0
/dev/input/event1: 0000 0000 00000000 EV_SYN SYN_REPORT 0
松开
/dev/input/event1: 0000 0002 00000000 EV_SYN SYN_MT_REPORT 0
/dev/input/event1: 0000 0000 00000000 EV_SYN SYN_REPORT 0
这里是一次点击,注意到ABS_MT_TRACKING_ID都是一样的。系统检测到三个点,每次会发送点的x,y,以及收到的压力,触摸的范围。最后两条表示松开这个点。如果两个手指同时点击,可以发现ABS_MT_TRACKING_ID会有两个不同的值,分别是两个点。据说最多支持5点。
每个厂商实现协议不一样。htc g3如下
/dev/input/event1: 0003 003a 002a0002
/dev/input/event1: 0003 0039 8b8c0ddc
/dev/input/event1: 0003 003a 00000002
/dev/input/event1: 0003 0039 8bac0dde
/dev/input/event1: 0003 003a 00000000
/dev/input/event1: 0003 0039 802814b1

4.可能遇到的问题

实际实现的时候,还可能遇到问题

一是root,getevent和sendevent需要/dev/input/event*的权限。一般应用是没有这个权限的,需要在程序里面获取su后,执行chmod 666 /dev/input/event*。

二是设备名称。因为你不知道触摸屏或者按键到底对应的event*是多少。需要有一个初始化的过程,大致思路是往event0-event9分别写入按键和触摸信号,同时监听activity里的onkeydown和view的onclick,这样来侦测设备。

三是厂商的实现不一样,这个没办法,只能一个一个适配了,一般来说都还是标准的,有些厂商会有单独的实现。
参考
http://lxr.free-electrons.com/source/include/uapi/linux/input.h#L803
http://source.android.com/tech/input/touch-devices.html
http://cjix.info/blog/misc/internal-input-event-handling-in-the-linux-kernel-and-the-android-userspace/

 

### Android 事件注入机制详解 #### 背景介绍 在 Android 平台中,由于进程之间的隔离特性,通常情况下无法直接访问或操控另一个应用的内部状态。然而,在某些特殊需求下(如自动化测试工具、辅助功能服务),可能需要向目标应用程序发送输入事件(例如触摸屏点击)。这种行为被称为 **事件注入**。 为了实现这一目的,Android 提供了一些特定 API 和权限支持,同时也存在一些第三方技术手段来完成更复杂的操作。以下是关于 Android 事件注入的具体实现方式及其工作原理: --- #### 方法一:使用 `Instrumentation` 类进行事件注入 `Instrumentation` 是 Android SDK 中的一个核心类,用于监控和控制应用程序的行为。它允许开发模拟用户交互并执行各种调试任务。通过该类中的方法可以轻松地将按键或触控事件注入到当前运行的应用程序中。 具体代码示例如下: ```java // 获取 Instrumentation 实例对象 Instrumentation instrumentation = new Instrumentation(); // 发送单击屏幕坐标 (x=100, y=200)事件 instrumentation.sendPointerSync(MotionEvent.obtain( SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 100f, 200f, 0)); instrumentation.sendPointerSync(MotionEvent.obtain( SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 100f, 200f, 0)); ``` 上述代码片段展示了如何利用 `sendPointerSync()` 函数创建一个虚拟的手指按下与抬起动作序列[^1]。 需要注意的是,这种方式仅适用于同一进程中触发事件;如果尝试越不同包名的应用,则会抛出安全异常。 --- #### 方法二:借助 AccessibilityService 完成高级别的事件分发 对于应用间的全局范围内的手势捕捉以及重放等功能来说,推荐采用基于无障碍模式的服务架构设计思路。此类方案无需额外申请 root 权限即可生效,并且兼容性强于其他侵入型做法。 下面是一个简单的配置文件声明例子: ```xml <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_service_description" android:packageNames="com.example.targetapp" android:accessibilityEventTypes="typeAllMask" android:accessibilityFeedbackType="feedbackGeneric" android:notificationTimeout="100"/> ``` 接着定义对应的 Java 文件逻辑处理部分: ```java public class MyAccessibilityService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event){ // 处理接收到的各种类型的 accessibility events... performGlobalAction(GLOBAL_ACTION_BACK); } @Override public void onInterrupt(){} } ``` 这里我们演示了当检测到来自指定 app (`targetapp`) 的任何 UI 变化时自动返回主页的功能[^3]。 不过值得注意的一点在于,启用此选项前必须引导最终用户手动开启对应开关页面设置项才能正常运作起来。 --- #### 方法三:Root 设备上的低级系统调用 假如目标设备已经被越狱解锁过的话,那么还可以考虑运用更加底层的技术途径去达成同样的效果——即加载动态链接库至远程进程内存空间之中再加以操纵其函数入口点位置从而间接影响原生窗口消息循环队列里的待处理项目列表内容结构形式等等复杂过程... 举个实际应用场景下的伪代码框架示意比如这样子写出来可能会比较直观一点吧? ```c++ #include <dlfcn.h> #include <sys/types.h> void inject_event(pid_t target_pid, int type, float x, float y){ const char *lib_name="/data/local/tmp/libevent.so"; if(auto handle=dlopen(lib_name,Rtld_LAZY)){ typedef bool (*inject_func)(pid_t,int,float,float); auto func=reinterpret_cast<inject_func>(dlsym(handle,"do_inject")); if(func && !func(target_pid,type,x,y)) printf("Failed!\n"); dlclose(handle); } } int main(){ pid_t victim=get_app_process_id("com.somegame"); inject_event(victim,EVENT_TYPE_TOUCHDOWN,50.0F,80.0F); sleep(1); inject_event(victim,EVENT_TYPE_TOUCHUP,50.0F,80.0F); return 0; } ``` 以上 C++ 版本脚本实现了针对某款手游 APP 进程 ID 查找定位之后连续两次分别下达按压释放指令的过程描述。 当然啦,这类手法往往伴随着较高的风险系数,稍不注意就可能导致整个操作系统崩溃重启甚至永久损坏硬件资源等问题发生哦! --- ### 总结说明 综上所述,根据不同的开发环境和技术背景可以选择适合自己的解决方案来进行安卓平台上面的事件注入实践探索之旅咯~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值