pen abstract class EventHandler(val service: AccessibilityService) {
//任务队列,重复事件较多,按顺序处理并发事件,找到目标node后清空队列
var eventQueue = LinkedList()
/**
* 将event加到队尾
* 队列中有正在处理的任务则等待处理完成
* 没有则开启一个新的任务
* */
fun pushEvent(event: AccessibilityEvent) {
if (eventQueue.size == 0) {
eventQueue.add(event)
while (eventQueue.size > 0) {
//找到目标node,直接终止任务,清空当前队列,找不到则继续下一个event
if (handleEvent(eventQueue.first)) {
eventQueue.clear()
break
} else {
eventQueue.removeFirst()
}
}
} else {
eventQueue.add(event)
}
}
abstract fun handleEvent(event: AccessibilityEvent): Boolean
}
--------------------------------父子分割线-------------------------------------
class WeChatEventHandler(service: AccessibilityService) : EventHandler(service) {
val TAG = "WeChatEventHandlerLog"
/**
* @return 在一个任务队列中是否找到了目标node,true为找到
* */
override fun handleEvent(event: AccessibilityEvent): Boolean {
Log.i(TAG, event.eventType.toString())
when (event.eventType) {
//效果不好,已废弃,改为使用 NotificationListenerService
// AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED -> {
// checkNotification(it)
// }
//根据调试后,确定微信在出现红包是会多次受到这个event,排队处理event,找到关键词并点击即可
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> {
findNodeByText("微信红包")?.let {
clickNode(it)
return true
}
}
//微信红包弹出来以后,只收到这一个event,但界面加载又需要时间,所以这里间隔100ms,重试10次找到开红包的按钮,找到则终止
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> {
//找到开红包按钮,重试10次
GlobalScope.launch {
var retryTimes = 0
while (retryTimes < 10) {
delay(100)
val node = findNodeByDes("开")
if (node != null) {
clickNode(node)
break
} else {
retryTimes++
}
}
}
return false
}
else -> {
}
}
return false
}
private fun findNodeByText(matchStr: String): AccessibilityNodeInfo? {
return findNode(matchStr) {
it.text?.toString() ?: ""
}
}
private fun findNodeByDes(matchStr: String): AccessibilityNodeInfo? {
return findNode(matchStr) {
it.contentDescription?.toString() ?: ""
}
}
/**
* 用Stack 模拟递归,避免栈溢出
* 递归遍历屏幕上的所有节点,通过文案来匹配需要找到的目标
* */
private fun findNode(
matchStr: String,
matchRule: (AccessibilityNodeInfo) -> String
): AccessibilityNodeInfo? {
try {
val nodeGroupStack = Stack()
var node = service.rootInActiveWindow
while (true) {
val nodeList = mutableListOf()
if (node.childCount > 0) {
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child.childCount > 0) {
nodeGroupStack.push(child)
} else {
val text = child.text?.toString() ?: ""
//确认是没用的红包,就可以放弃这个模块了
if (text.contains("已领取") || text.contains("已被领完")) {
break
}
nodeList.add(child)
}
}
} else {
val text = node.text?.toString() ?: ""
//在列表中发现没用的红包,跳出循环,继续找下一个红包
if (text.contains("已领取") || text.contains("已被领完")) {
continue
}
nodeList.add(node)
}
nodeList.forEach { child ->
var content = matchRule(child)
//各种没抢到红包的情况,直接返回或者回主页,不要遮挡屏幕,以免收不到后续红包
val text = child.text?.toString() ?: ""
if (text.contains("已存入零钱") || text.contains("个红包,") || text.contains("个红包共") || text.contains("手慢了,")) {
if (AppConfig.mode == 0) {
service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
} else {
service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME)
}
service.rootInActiveWindow
}
if (content == matchStr) {
return child
}
}
//用循环模拟递归,避免栈溢出
if (nodeGroupStack.size > 0) {
node = nodeGroupStack.pop()
} else {
break
}
}
} catch (e: Exception) {
Log.e(TAG, e.message.toString())
}
return null
}
/**
* 点击节点
* 有click事件则调用click
* 没有就获取坐标,模拟点击
* */
private fun clickNode(node: AccessibilityNodeInfo) {
if (node.isClickable) {
Log.i(TAG, "clickBtn: ${node.text}")
node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
} else {
//点击屏幕坐标
Rect().let { rect ->
node.getBoundsInScreen(rect)
val x = (rect.left + rect.right) / 2
val y = (rect.top + rect.bottom) / 2
dispatchGestureClick(x, y)
Log.i(TAG, "clickScreen: ${node.text} , x: $x , y: $y")
}
}
}
/**
* 点击指定位置
* api > 24
*/
private fun dispatchGestureClick(x: Int, y: Int) {
val path = Path()
path.moveTo(x.toFloat(), y.toFloat())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
service.dispatchGesture(
GestureDescription.Builder().addStroke(
GestureDescription.StrokeDescription(
path,
0,
200
)
).build(),
null,
null
)
}
}
}