KotlinAutomation,使用Kotlin实现root后多点触控
KotlinAutomation
本仓库实现了 TouchDown(按下)、TouchMove(滑动)、TouchUp(抬起) 等功能,适用于 已Root的安卓设备。它支持 多点触控,可同时模拟多个手指的触摸操作。
相关技术可参考:Auto.js、按键精灵、懒人精灵。
关键词
Kotlin 自动化、多指触控、多点触摸、Root权限、Instrumentation、InputManager
核心原理
在 Android 自动化 中,模拟真实触摸 是关键部分之一。通常可以使用 无障碍服务(Accessibility Service) 实现触摸操作,但它需要 预设完整的手势轨迹,然后一次性派发。
那么,如果在触摸过程中轨迹是动态变化的,该怎么办?
在尝试直接 向系统注入手势事件 时,即便获得了Root权限,仍会遇到 SecurityException(安全异常),提示 缺少INJECT_EVENTS权限。如果直接使用sendevent,会遇到兼容性问题。因为云手机无法使用。本项目开源网址:点击这里。视频讲解:点击这里
核心解决方案如下:
1. 预留可调用的类
代码示例:
package com.coc.zkq
import android.os.SystemClock
import android.view.InputDevice
import android.view.InputEvent
import android.view.MotionEvent
import android.view.MotionEvent.PointerCoords
import android.view.MotionEvent.PointerProperties
import java.lang.Thread.sleep
import java.lang.reflect.Method
class MultiSwipeSimulator {
companion object {
@JvmStatic
fun simulateDualTouchSwipe(
x1Start: Int, y1Start: Int, x1End: Int, y1End: Int,
x2Start: Int, y2Start: Int, x2End: Int, y2End: Int,
duration: Long
) {
try {
val inputManagerClass = Class.forName("android.hardware.input.InputManager")
val getInstanceMethod = inputManagerClass.getMethod("getInstance")
val inputManager = getInstanceMethod.invoke(null)
val injectInputEventMethod = inputManagerClass.getMethod(
"injectInputEvent", InputEvent::class.java, Int::class.javaPrimitiveType
)
val downTime = SystemClock.uptimeMillis()
var eventTime = downTime
// 第一个触点按下
val prop0 = PointerProperties().apply {
id = 0
toolType = MotionEvent.TOOL_TYPE_FINGER
}
val initialCoord0 = PointerCoords().apply {
x = x1Start.toFloat()
y = y1Start.toFloat()
}
val downEvent = MotionEvent.obtain(
downTime, eventTime,
MotionEvent.ACTION_DOWN, 1,
arrayOf(prop0), arrayOf(initialCoord0),
0, 0, 1f, 1f, 0, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0
)
injectInputEventMethod.invoke(inputManager, downEvent, 0)
downEvent.recycle()
// 添加第二个触点
eventTime += 20
val prop1 = PointerProperties().apply {
id = 1
toolType = MotionEvent.TOOL_TYPE_FINGER
}
val initialCoord1 = PointerCoords().apply {
x = x2Start.toFloat()
y = y2Start.toFloat()
}
val pointerDownAction = MotionEvent.ACTION_POINTER_DOWN or (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT)
val pointerDownEvent = MotionEvent.obtain(
downTime, eventTime, pointerDownAction, 2,
arrayOf(prop0, prop1), arrayOf(initialCoord0, initialCoord1),
0, 0, 1f, 1f, 0, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0
)
injectInputEventMethod.invoke(inputManager, pointerDownEvent, 0)
pointerDownEvent.recycle()
// 计算滑动参数
val steps = (duration / 20).coerceAtLeast(1)
val stepDuration = duration / steps
val deltaX1 = (x1End - x1Start).toFloat()
val deltaY1 = (y1End - y1Start).toFloat()
val deltaX2 = (x2End - x2Start).toFloat()
val deltaY2 = (y2End - y2Start).toFloat()
// 发送连续移动事件
repeat(steps.toInt()) { step ->
eventTime += stepDuration
val progress = (step + 1f) / steps
// 计算当前坐标
val currentCoord0 = PointerCoords().apply {
x = x1Start + deltaX1 * progress
y = y1Start + deltaY1 * progress
}
val currentCoord1 = PointerCoords().apply {
x = x2Start + deltaX2 * progress
y = y2Start + deltaY2 * progress
}
// 发送移动事件
val moveEvent = MotionEvent.obtain(
downTime, eventTime,
MotionEvent.ACTION_MOVE, 2,
arrayOf(prop0, prop1), arrayOf(currentCoord0, currentCoord1),
0, 0, 1f, 1f, 0, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0
)
injectInputEventMethod.invoke(inputManager, moveEvent, 0)
moveEvent.recycle()
SystemClock.sleep(stepDuration)
}
// 抬起第二个触点
eventTime += stepDuration
val pointerUpAction = MotionEvent.ACTION_POINTER_UP or (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT)
val pointerUpEvent = MotionEvent.obtain(
downTime, eventTime, pointerUpAction, 2,
arrayOf(prop0, prop1), arrayOf(
PointerCoords().apply { x = x1End.toFloat(); y = y1End.toFloat() },
PointerCoords().apply { x = x2End.toFloat(); y = y2End.toFloat() }
),
0, 0, 1f, 1f, 0, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0
)
injectInputEventMethod.invoke(inputManager, pointerUpEvent, 0)
pointerUpEvent.recycle()
// 抬起第一个触点
eventTime += 20
val upEvent = MotionEvent.obtain(
downTime, eventTime,
MotionEvent.ACTION_UP, 1,
arrayOf(prop0), arrayOf(
PointerCoords().apply { x = x1End.toFloat(); y = y1End.toFloat() }
),
0, 0, 1f, 1f, 0, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0
)
injectInputEventMethod.invoke(inputManager, upEvent, 0)
upEvent.recycle()
} catch (e: Exception) {
e.printStackTrace()
}
}
@JvmStatic
fun main(args: Array<String>) {
// 检查参数数量是否正确
if (args.size != 9) {
println("Usage: MultiSwipeSimulator startX1 startY1 endX1 endY1 startX2 startY2 endX2 endY2 duration")
return
}
try {
// 解析参数
val startX1 = args[0].toInt()
val startY1 = args[1].toInt()
val endX1 = args[2].toInt()
val endY1 = args[3].toInt()
val startX2 = args[4].toInt()
val startY2 = args[5].toInt()
val endX2 = args[6].toInt()
val endY2 = args[7].toInt()
val duration = args[8].toLong()
// 调用 simulateMultiSwipe 方法
simulateDualTouchSwipe(
startX1, startY1, endX1, endY1,
startX2, startY2, endX2, endY2,
duration
)
} catch (e: NumberFormatException) {
println("Invalid argument format. All arguments must be integers except duration (long).")
e.printStackTrace()
} catch (e: Exception) {
println("An error occurred during execution.")
e.printStackTrace()
}
}
}
}
2. 通过Root权限调用该类
为了使用这个类,需要先 把这个代码编译成APK 并将其部署到设备上,然后通过 app_process 命令调用它。
本示例使用 libsu 进行Root操作,若未安装libsu,也可直接使用 su -c 运行命令。
示例命令:
/sdcard/10.apk是APK的存放路径,需自行修改。com.coc.zkq是包名,若有变动,也需修改。MultiSwipeSimulator是类名。- 后续的数字参数代表触摸坐标及滑动时间。
代码示例:
fun runAppProcessAsRoot() {
try {
// Modify the APK path accordingly!
val command =
"app_process -Djava.class.path=/sdcard/10.apk /data/local/tmp com.coc.zkq.MultiSwipeSimulator 200 500 500 500 200 100 500 600 1000"
val result = Shell.su(command).exec()
if (result.isSuccess) {
Log.d("test", "Command executed successfully.")
result.out.forEach { println(it) }
} else {
Log.d("test", "Command failed with exit code ${result.code}.")
result.err.forEach { println(it) }
}
} catch (e: Exception) {
e.printStackTrace()
}
}
结语
本项目通过 Kotlin+Root权限,实现了 多点触控模拟,可用于自动化测试、交互研究等场景。若有疑问或建议,欢迎讨论!

5052

被折叠的 条评论
为什么被折叠?



