Android:AccessibilityService辅助功能基础使用(附微信抢红包教程)

val eventType = event?.getEventType()
Log.i(TAG, “pkgName: p k g N a m e e v e n t T y p e : {pkgName} eventType: pkgNameeventType:{eventType} className: e v e n t ? . g e t C l a s s N a m e ( ) . t o S t r i n g ( ) " + " e v e n t . t e x t : {event?.getClassName().toString()} " + "event.text: event?.getClassName().toString()"+"event.text:{listToString(event?.text)} event?.getContentChangeTypes()😒{event?.getContentChangeTypes()}\n”)
when (eventType) {
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED -> com.example.zhouzhihui.accessibilitydemo.access.packet.handleNotification(event)//64 1–>click
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> {//32 2048
val className = event.getClassName().toString()
if (className == “com.tencent.mm.ui.LauncherUI” || className == “com.tencent.mm.ui.mogic.WxViewPager” || className == “android.widget.EditText”/* || className == “android.widget.ListView”*/) {
com.example.zhouzhihui.accessibilitydemo.access.packet.searchPacket(rootInActiveWindow)
} else if (className == “com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI”) {//com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyPrepareUI
com.example.zhouzhihui.accessibilitydemo.access.packet.openPacket(rootInActiveWindow)
} else if (className == “com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI”) {
com.example.zhouzhihui.accessibilitydemo.access.packet.closePacket(rootInActiveWindow)
}
}
}
if (event?.getContentChangeTypes() == AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT) {
Withdraw().withDraw(event, rootInActiveWindow)//防消息撤回
}

rootInActiveWindow?.recycle()//避免重复创建实例通过recycle方法回收掉nodeInfo(我们自己手动去回收)
}

代码片段4事件被分发成四个分流:handleNotification(event: AccessibilityEvent?)searchPacket(rootInActiveWindow: AccessibilityNodeInfo?)openPacket(rootInActiveWindow: AccessibilityNodeInfo?)closePacket(rootInActiveWindow: AccessibilityNodeInfo?),这四个方法的处理逻辑在Packet.kt类中。

  1. handleNotification(event: AccessibilityEvent?)。当eventType == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED == 64的时候执行这个事件流,这个事件表示监听到了通知栏事件,微信处在后台的时候来了聊天消息,就会出发这个事件,我们的方法检测通知内容是否包含为本"[微信红包]",如果包含就表示收到了红包消息,就执行它附带的PendingIntent,然后就会跳到相应的聊天页面。

  2. searchPacket(rootInActiveWindow: AccessibilityNodeInfo?)。当eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED == 32或者eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED == 2048的时候执行这个事件流。32表示窗口状态发生了变化,比如微信的主页"com.tencent.mm.ui.LauncherUI"从后台调到前台就会触发这个事件,并且它附带的className就是"com.tencent.mm.ui.LauncherUI";2048表示窗口的内容发生了变化,比如你在微信的第一个tab页面,这时候来了个聊天消息,就会触发这个事件,附带的className是android.widget.ListView,嗯,没错,微信竟然还是在用ListView这个过时的组件而不是RecyclerView。我们捕捉到这个事件后调用searchPacket()方法,顾名思义,这个方法要搜索红包并点击。我们传给它的参数通过API AccessibilityService.getRootInActiveWindow()获取的,我有点搞不懂这个API和AccessibilityEvent.getSource()有什么区别,前者是辅助服务调用的,应该是窗口的根节点,后者是监听到的某个事件获取的,应该是这个事件的源节点,我用Log显示大部分时候两者是一致的。searchPacket方法通过递归查找红包,当找到某个节点内容包含“领取红包”就终止递归,然后循环查找这个节点和它的父节点的第一个能够点击的节点,执行点击事件rootInActiveWindow.performAction(AccessibilityNodeInfo.ACTION_CLICK)就能自动点击红包。

  3. openPacket(rootInActiveWindow: AccessibilityNodeInfo?)。条件同上,当eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED == 32或者eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED == 2048的时候执行这个事件流。通过上面的searchPacket我们搜索到了红包并点击了,这时会出现红包领取页面,我们这里openPacket方法是要找到领取红包的节点并执行这个节点的点击事件进行领取。关键是如何找到这个节点,一种方法是通过ViewId,API AccessibilityNodeInfo.getViewIdResourceName()可以获取这个节点的id,但是你需要事先知道这个节点的id,而且辅助的配置标记必须是android:accessibilityFlags="flagReportViewIds"才能获取节点的id,可以使用Android Device Monitor或者Layout Inspector查看id,也可以直接把节点的id打印出来进行查看对比,但是微信的程序员经常改变id,我不认为这个方法是可靠的,我的方法是如果满足条件(rootInActiveWindow?.isClickable == true && rootInActiveWindow?.className?.contains("android.widget.Button") == true)就认为这个节点是领取红包的按钮,然后执行点击事件:rootInActiveWindow?.performAction(AccessibilityNodeInfo.ACTION_CLICK)

  4. closePacket(rootInActiveWindow: AccessibilityNodeInfo?)。条件同上,当eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED == 32或者eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED == 2048的时候执行这个事件流。这个方法是为了找到左上角的返回按钮,进行点击返回聊天页面。这个也不是通过id的方式,而是如果满足(rootInActiveWindow?.className == "android.widget.LinearLayout" && rootInActiveWindow?.isClickable && TextUtils.isEmpty(rootInActiveWindow?.text))就认为是左上角的返回节点。

下面贴出代码:

// 代码片段5
fun handleNotification(event: AccessibilityEvent?) {
if (event == null) {
return
}
val texts = event.text
if (!texts.isEmpty()) {
for (text in texts) {
val content = text.toString()
if (content.contains(“[微信红包]”)) {
if (event.parcelableData != null && event.parcelableData is Notification) {
val notification = event.parcelableData as Notification
val pendingIntent = notification.contentIntent
try {
pendingIntent.send()
} catch (e: PendingIntent.CanceledException) {
e.printStackTrace()
}

}
}
}
}
}

fun searchPacket(rootInActiveWindow: AccessibilityNodeInfo?) {
Log.i(TAG, “searchPacket node: ${rootInActiveWindow} childCount: ${rootInActiveWindow?.childCount} idName: ${rootInActiveWindow?.getViewIdResourceName()}”)
if (rootInActiveWindow?.text.toString() == “领取红包”) {
if (rootInActiveWindow?.isClickable == true) {
rootInActiveWindow.performAction(AccessibilityNodeInfo.ACTION_CLICK)
} else {
var parent: AccessibilityNodeInfo? = rootInActiveWindow?.getParent()
while (parent != null) {
if (parent.isClickable) {
parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
break
}
parent = parent.parent
}
}
} else {
for (i in 0 until (rootInActiveWindow?.childCount ?: -1)) {
searchPacket(rootInActiveWindow?.getChild(i))
}
}
}

fun openPacket(rootInActiveWindow: AccessibilityNodeInfo?) {
Log.i(TAG, “openPacket node: ${rootInActiveWindow} childCount: ${rootInActiveWindow?.childCount} idName: ${rootInActiveWindow?.getViewIdResourceName()}”)
if (rootInActiveWindow?.isClickable == true && rootInActiveWindow?.className?.contains(“android.widget.Button”) == true) {
rootInActiveWindow?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
// node?.traversalAfter
for (i in 0 until (rootInActiveWindow?.childCount ?: -1)) {
openPacket(rootInActiveWindow?.getChild(i))
}
}

fun closePacket(rootInActiveWindow: AccessibilityNodeInfo?) {
Log.i(TAG, “closePacket node: ${rootInActiveWindow} childCount: ${rootInActiveWindow?.childCount} idName: ${rootInActiveWindow?.getViewIdResourceName()}”)
if (rootInActiveWindow?.className == “android.widget.LinearLayout” && rootInActiveWindow?.isClickable && TextUtils.isEmpty(rootInActiveWindow?.text)) {
//className: android.widget.LinearLayout; text: null; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: com.tencent.mm:id/ho;
rootInActiveWindow?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
return
}
for (i in 0 until (rootInActiveWindow?.childCount ?: -1)) {
closePacket(rootInActiveWindow?.getChild(i))
}
}

此外,在MainActivity里面,还有判断服务是否开启的逻辑,如果没有开启,则可以点击跳转带开启页面:

// 代码片段6 MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
hello.also {
val isOn = isAccessibilityServiceOn()
it.text = if (isOn) “服务已经开启” else “点击开启服务”
it.isEnabled = !isOn
it.setOnClickListener {
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
}
}
}
}

// 代码片段7 Tools.kt
val TAG = AccessibilityService::class.java.simpleName
fun listToString(list : List?): String {
var result = StringBuilder(“”)
list?.forEach {
result.append(“${it.toString()}\t”)
}
return result.toString()
}

fun isPrePagePacket(prePageName: String): Boolean {
return prePageName == “com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI” || prePageName == “com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI”
}

fun Context.isAccessibilityServiceOn(): Boolean {
var service = “ p a c k a g e N a m e / {packageName}/ packageName/{MyAccessibilityService::class.java.canonicalName}”
var enabled = Settings.Secure.getInt(applicationContext.contentResolver, Settings.Secure.ACCESSIBILITY_ENABLED)
var splitter = TextUtils.SimpleStringSplitter(‘:’)
if (enabled == 1) {
var settingValue = Settings.Secure.getString(applicationContext.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
if (settingValue != null) {
splitter.setString(settingValue)
while (splitter.hasNext()) {
var accessibilityService = splitter.next()
if (accessibilityService.equals(service, ignoreCase = true)) {
return true
}
}
}
}
return false
}

自动领取红包的代码写完了,运行安装到手机上,还差最后一步了,就是在手机的“设置”里面把刚刚装上的应用的服务开启,我的小米手机开启方法如图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后

好了,本文是对AccessibilityService简单的应用,如果你觉得文章写得不错就给个赞呗?如果你有更好的想法和项目欢迎留言。一定会认真查询,修正不足。谢谢!

希望读到这的您能转发分享和关注一下我,以后还会更新技术干货,谢谢您的支持!

转发+点赞+关注,第一时间获取最新知识点

Android架构师之路很漫长,一起共勉吧!


以下墙裂推荐阅读!!!

最后祝大家生活愉快~

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

  • 思维脑图
  • 性能优化学习笔记


  • 性能优化视频

    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

pF5-1712742056298)]

  • 性能优化视频
    [外链图片转存中…(img-nC9Skw1z-1712742056298)]
    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值