背景:
前面公众号文章已经分享过如何实现这种大厂里面一键连招,触摸宏的功能,原理本身是对dev/input下面的节点进行读取保存文件,然后在读取文件进行写入dev/input下面的节点,从而实现了触摸事件的读取和写入,不过这个一定要root的手机版本。
上面文章一发布后,就有好些学员联系我,他们在学习了投屏专题后,也对scrcpy有了深入的研究和理解了。很多同学就提出是否可以考虑参考scrcpy的事件注入,即已经实现了触摸注入了,只需要再额外补充一个事件的录制保存文件既可以。
方案设计
如果使用scrcpy方案则设计模块图如下:
可以看的出这里重点的方案就是scrcpy如何获取用户的触摸事件,即要监听到全局的触摸事件。
这个监听全局事件,学过framework专题课程的都应该知道可以考虑使用InputManager下面的monitor全局事件监听。
这样搞定了触摸事件监听后,整体上就没啥技术难点了,下面来开始进行用scrcpy实战实现该功能。
主程序部分代码
主程序需要实现2个核心逻辑:
1.实现对触摸事件的录制操作
2.实现对触摸事件的注入操作
代码设计如下:
public static void main(String... args) {
System.out.println("Hello World OneKeyLink demo!!!! ");
if (args.length > 0) {
HandlerThread handlerThread = new HandlerThread("test motion");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
if (args[0].equals("record")) {//识别如果当前是录制情况
handler.post(new Runnable() {
@Override
public void run() {
InputManager inputManager = InputManager.create();
inputManager.monitorAllTouch();
}
});
}else if (args[0].equals("play")) {//识别如果当前是播放情况
File file = new File(InputManager.SAVE_MOTION_PATH);
if (file.exists() && file.length() > 0) {
try {
InputManager inputManager = InputManager.create();
inputManager.injectEventFromFile(InputManager.SAVE_MOTION_PATH,handler);
}catch (Exception e) {
e.printStackTrace();
}
}
}
如果要进行录制,传递record
adb shell CLASSPATH=/data/local/tmp/onekeylink.jar app_process / com.genymobile.scrcpy.Server record
如果要进行播放,传递play
adb shell CLASSPATH=/data/local/tmp/onekeylink.jar app_process / com.genymobile.scrcpy.Server play
scrcpy触摸事件全局监听
使用InputManager相关接口如下:
frameworks/base/core/java/android/hardware/input/InputManager.java
/**
* Monitor input on the specified display for gestures.
*
* @hide
*/
public InputMonitor monitorGestureInput(String name, int displayId) {
return mGlobal.monitorGestureInput(name, displayId);
}
现在InputManager就只有这一个monitorGestureInput接口来监听全局触摸事件了,而且还是个hide,不过这个对于scrcpy来说反射都不是事。
private Method getMonitorGestureInput() {
try {
return manager.getClass().getMethod("monitorGestureInput", String.class,int.class);
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void monitorAllTouch() {
new File(SAVE_MOTION_PATH).delete();
try {
InputMonitor mInputMonitor = (InputMonitor) getMonitorGestureInput().invoke(manager, "clipboard overlay", 0);//采用反射方法进行监听获取mInputMonitor
new InputEventReceiver(
mInputMonitor.getInputChannel(), Looper.myLooper()) {
//采用反射方法进行监听获取后使用mInputMonitorgetInputChannel
@Override
public void onInputEvent(InputEvent event) {
System.out.println(" onInputEvent "+event);
if (event instanceof MotionEvent ) {
//识别触摸事件然后,进行事件转换成text
String motionText = covertMotionEventToSmall((MotionEvent) event).toSaveString();
try {
saveTouchFile = new FileWriter(SAVE_MOTION_PATH,true);
saveTouchFile.write(motionText + "\n");//保存写入触摸事件文本
saveTouchFile.close();
}catch (Exception e){
e.printStackTrace();
}
}
finishInputEvent(event, true /* handled */);
}
};
}catch (Exception e) {
e.printStackTrace();
}
}
上面就有2个核心代码部分:
1、反射调用InputManager的monitorGestureInput获取InputMonitor
2、根据InputMonitor的getInputChannel创建对应InputEventReceiver
3、接受触摸事件转换成文本,保存到文件
通过上面几步骤就完成了监听触摸事件保存到文件的操作。
读取文件进行注入
这部分其实不要怎么讲解,就是读取文件然后进行注入
if (file.exists() && file.length() > 0) {
try {
InputManager inputManager = InputManager.create();
inputManager.injectEventFromFile(InputManager.SAVE_MOTION_PATH,handler);//传递文件路径
}catch (Exception e) {
e.printStackTrace();
}
}
public void injectEventFromFile(String path, Handler handler) {
if (!TextUtils.isEmpty(path)) {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {//读取解析文件
String line;
while ((line = br.readLine()) != null) {
// 处理每行数据
// System.out.println(line);
SmallMotionEvent smallMotionEvent = constructFromStrLine(line);
System.out.println("smallMotionEvent : " + smallMotionEvent.toSaveString());
MotionEvent event = convertSmallEventToNormal(smallMotionEvent);//文件转换成MotionEvent
handler.postDelayed(new Runnable() {
@Override
public void run() {
injectInputEvent(event,0);//进行事件注入
System.out.println("injectInputEvent event " +event);
}
},event.getDownTime() - initEventTime);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
测试结果如下
操作:
先使用./test_server.sh record 进行一个录制操作
录制完成后再使用 ./test_server.sh play
test_server.sh 内容
adb push /home/test/demos/scrcpy2.4/onekeylink/server/build/outputs/apk/debug/server-debug.apk /data/local/tmp/onekeylink.jar
adb shell CLASSPATH=/data/local/tmp/onekeylink.jar app_process / com.genymobile.scrcpy.Server $@
该方案因为需要MONITOR_INPUT权限,但是shell不自带该权限,所以也需要root权限,否则报错如下:
Caused by: java.lang.SecurityException: Requires MONITOR_INPUT permission
留给各位学员思考的问题:
是否可以实现一个完全user版本上也可以方案?
(完整代码需要联系马哥获取,限vip学员)
更多framework详细代码和资料参考如下链接
投屏专题部分:
https://mp.weixin.qq.com/s/IGm6VHMiAOPejC_H3N_SNg
hal+perfetto+surfaceflinger
https://mp.weixin.qq.com/s/LbVLnu1udqExHVKxd74ILg
其他课程七件套专题:
点击这里
https://mp.weixin.qq.com/s/Qv8zjgQ0CkalKmvi8tMGaw
视频试看:
https://www.bilibili.com/video/BV1wc41117L4/
参考相关链接:
https://blog.csdn.net/zhimokf/article/details/137958615
更多framework假威风耗:androidframework007