博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
微信自动抢红包的实现(Demo已增加查看TopActivity功能)
阅读量:6602 次
发布时间:2019-06-24

本文共 7689 字,大约阅读时间需要 25 分钟。

hot3.png

 

先说说遇到的一些问题

去年写过微信抢红包插件的实现,但是今年春节的时候发现微信更新之后自己写的插件竟然会停在开红包的页面无法继续向下执行,debug之后发现问题是微信团队把开红包按钮的文本内容现在改成了一张图片,导致我使用findAccessibilityNodeInfosByText()找不到有效的子节点,也就无法实现模拟点击去打开红包。

于是乎我开始尝试通过获取指定控件的ID去实现,迅速打开IDE,使用Android Device Monitor查看开红包按钮的控件id,后面会附上使用方法,然后使用findAccessibilityNodeInfosByViewId()来获取开红包按钮的节点,结果当然是可以的。
但是这就带来了另一个问题:如果微信每次发版都修改这个按钮的控件id,那我的插件也就只能每次都跟随着修改代码才能正常使用,事实也证明微信的确是每次发版都会修改此控件的id值。
针对这个问题我目前的做法是开一个ArrayList记录微信开红包button所使用过的id值,然后去遍历id值通过findAccessibilityNodeInfosByViewId()获取节点,当然用map存储id值及其对应微信版本号用来做版本兼容会更好些,谁让我懒呢,懒得去获取微信版本号。当然,还有种暴力的方法,就是遍历开红包页面的节点树并模拟点击其下的每一个能点击的button,因为其实界面里能点击的就只有关闭按钮和开红包按钮,但关闭按钮其实是个imageView而不是button。
坑总是一个接一个,在最近的微信版本更新后,我发现不仅仅是控件id会发生改变,就连某些activity的名称都被修改了,以及聊天页面对消息推送的处理方式也变了,适配的代码我已经更新到github。
以后微信红包如果还有其他修改,我会把适配后的代码直接更新到github的demo,所以下方的代码片不一定是最新的,感兴趣的同学可以上github去star一下,。当然适配过程中遇到的问题我还是会记录在这里。

核心代码片如下:

抢红包:

if ("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI".equals(event.getClassName())) {    //当前在红包待开页面,去拆红包    getLuckyMoney();} else if ("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI".equals(event.getClassName())) {    //拆完红包后看详细纪录的界面    openNext("查看我的红包记录");} else if ("com.tencent.mm.ui.LauncherUI".equals(event.getClassName())) {    //在聊天界面,去点中红包    openLuckyEnvelope();}

自动加人:

if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && event.getClassName().equals("com.tencent.mm.ui.LauncherUI")) {    //记录打招呼人数置零    i = 0;    //当前在微信聊天页就点开发现    openNext("发现");    //然后跳转到附近的人    openDelay(1000, "附近的人");} else if (event.getClassName().equals("com.tencent.mm.plugin.nearby.ui.NearbyFriendsUI") && eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {    prepos = 0;    //当前在附近的人界面就点选人打招呼    AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();    List
list = nodeInfo.findAccessibilityNodeInfosByText("米以内"); Log.d("name", "附近的人列表人数: " + list.size()); if (i < (list.size() * page)) { list.get(i % list.size()).performAction(AccessibilityNodeInfo.ACTION_CLICK); list.get(i % list.size()).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK); } else if (i == list.size() * page) { //本页已全部打招呼,所以下滑列表加载下一页,每次下滑的距离是一屏 for (int i = 0; i < nodeInfo.getChild(0).getChildCount(); i++) { if (nodeInfo.getChild(0).getChild(i).getClassName().equals("android.widget.ListView")) { AccessibilityNodeInfo node_lsv = nodeInfo.getChild(0).getChild(i); node_lsv.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); page++; new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException mE) { mE.printStackTrace(); } AccessibilityNodeInfo nodeInfo_ = getRootInActiveWindow(); List
list_ = nodeInfo_.findAccessibilityNodeInfosByText("米以内"); Log.d("name", "列表人数: " + list_.size()); //滑动之后,上一页的最后一个item为当前的第一个item,所以从第二个开始打招呼 list_.get(1).performAction(AccessibilityNodeInfo.ACTION_CLICK); list_.get(1).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK); } }).start(); } } }} else if (event.getClassName().equals("com.tencent.mm.plugin.profile.ui.ContactInfoUI") && eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { if (prepos == 1) { //从打招呼界面跳转来的,则点击返回到附近的人页面 performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); i++; } else if (prepos == 0) { //从附近的人跳转来的,则点击打招呼按钮 AccessibilityNodeInfo nodeInfo = getRootInActiveWindow(); if (nodeInfo == null) { Log.w(TAG, "rootWindow为空"); return; } List
list = nodeInfo.findAccessibilityNodeInfosByText("打招呼"); if (list.size() > 0) { list.get(list.size() - 1).performAction(AccessibilityNodeInfo.ACTION_CLICK); list.get(list.size() - 1).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK); } else { //如果遇到已加为好友的则界面的“打招呼”变为“发消息",所以直接返回上一个界面并记录打招呼人数+1 performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); i++; } }} else if (event.getClassName().equals("com.tencent.mm.ui.contact.SayHiEditUI") && eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { //当前在打招呼页面 prepos = 1; //输入打招呼的内容并发送 inputHello(hello); openNext("发送");}

打开通知栏消息:

private void openNotification(AccessibilityEvent event) {    if (event.getParcelableData() == null || !(event.getParcelableData() instanceof Notification)) {        return;    }    //将通知栏消息打开    Notification notification = (Notification) event.getParcelableData();    PendingIntent pendingIntent = notification.contentIntent;    try {        pendingIntent.send();    } catch (PendingIntent.CanceledException e) {        e.printStackTrace();    }}

点击匹配的nodeInfo str text关键字:

private void openNext(String str) {    AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();    if (nodeInfo == null) {        Log.w(TAG, "rootWindow为空");        Toast.makeText(this, "rootWindow为空", Toast.LENGTH_SHORT).show();        return;    }    List
list = nodeInfo.findAccessibilityNodeInfosByText(str); Log.d("name", "匹配个数: " + list.size()); if (list.size() > 0) { list.get(list.size() - 1).performAction(AccessibilityNodeInfo.ACTION_CLICK); list.get(list.size() - 1).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK); } else { Toast.makeText(this, "找不到有效的节点", Toast.LENGTH_SHORT).show(); }}

自动输入打招呼内容:

private void inputHello(String hello) {    AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();    //找到当前获取焦点的view    AccessibilityNodeInfo target = nodeInfo.findFocus(AccessibilityNodeInfo.FOCUS_INPUT);    if (target == null) {        Log.d(TAG, "inputHello: null");        return;    }    ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);    ClipData clip = ClipData.newPlainText("label", hello);    clipboard.setPrimaryClip(clip);    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {        target.performAction(AccessibilityNodeInfo.ACTION_PASTE);    }}

关于如何获取app页面中控件的id:

在Android Studio中开启Android Device Monitor,选择设备后点击Dump View Hierarchy for UI Automator即可查看

 

1

 

配置使用AccessibilityService:

在manifest中的配置:

meta-data中的xml资源文件:

其中packageName用于配置你想要监测的包名,如果多个则用逗号隔开

accessibilityEventTypes表示该服务可监测界面中哪些事件类型,如窗口打开,滑动等,具体值可查看api
accessibilityFeedbackType:表示反馈方式,比如是语音播放,还是震动
canRetrieveWindowContent:表示该服务能否访问活动窗口中的内容
notificationTimeout:接受事件的时间间隔

当然,除了以meta-data的方式静态配置,也可通过在服务启动时的onServiceConnected()方法中调用setServiceInfo(AccessibilityServiceInfo)进行动态配置。

补充:

几种常用accessibilityEventType事件类型:

TYPE_WINDOW_STATE_CHANGED: 窗口状态改变事件类型,打开PopupWindow、dialog、menu等
TYPE_NOTIFICATION_STATE_CHANGED: 通知栏事件
TYPE_WINDOW_CONTENT_CHANGED: 窗口中内容改变
TYPE_VIEW_SCROLLED: 控件滑动事件
TYPE_WINDOWS_CHANGED: 显示窗口改变
TYPE_VIEW_TEXT_CHANGED : editText控件的内容发生改变
TYPE_TOUCH_INTERACTION_START: 用户开始触摸屏幕
TYPE_TOUCH_INTERACTION_END: 用户停止触摸屏幕

其中TYPE_WINDOW_CONTENT_CHANGED 又可以细分为4个二级类型:

1.CONTENT_CHANGE_TYPE_SUBTREE: 节点发生增减
2.CONTENT_CHANGE_TYPE_TEXT: 节点文本发生改变
3.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION: 节点的内容描述发生改变,即控件的contentDescription属性发生改变
4.CONTENT_CHANGE_TYPE_UNDEFINED: 未定义类型,即除上面三种之外的类型

接下来,或许你可以自己尝试下使用AccessibilityService实现app的自动安装/批量安装,去学习吧,骚年!

转载于:https://my.oschina.net/u/3729361/blog/1584214

你可能感兴趣的文章
9.02-Spring IOC 容器中Bean的生命周期
查看>>
6.6 tar打包
查看>>
微信自动抢红包的实现(Demo已增加查看TopActivity功能)
查看>>
Spring MVC核心技术
查看>>
TCP协议如何保证传输的可靠性
查看>>
Spring Cloud云架构 - SSO单点登录之OAuth2.0 登出流程(3)
查看>>
建站心得之discuz门户程序相比ZBLOG具有哪些优势[图]
查看>>
编程之美 测试赛 石头剪刀布
查看>>
签名问题
查看>>
软件开发各阶段交付物列表
查看>>
2018-05-24 Linux学习
查看>>
ntp服务器的搭建
查看>>
我的友情链接
查看>>
sysstat 安装
查看>>
《你必须知道的.NET》 - 书摘精要
查看>>
六、nginx搭建织梦DedeCms网站
查看>>
Tair学习小记
查看>>
网卡绑定(服务器&&交换机),缓存服务器Squid架构配置
查看>>
web网站加速之CDN(Content Delivery Network)技术原理
查看>>
打算写一款框架来提高自己 写个结构吧
查看>>