Android辅助功能实现自动抢红包(附源码)
一、描述
最近看到同事有用抢红包的软件,就想看看抢红包的具体实现是如何的,所以了解了一下,有用辅助功能实现的,所以在下面的示例中会展示一个抢红包的小demo,附带源码抢红包源码。
二、效果图
在桌面收到红包进行抢
在聊天页面收到口令红包
三、accessibilityservice使用
创建辅助服务类,继承accessibilityservice,实现两个接口,接收系统的事件
public class myservice extends accessibilityservice { @override public void onaccessibilityevent(accessibilityevent event) { } @override public void oninterrupt() { } }
辅助服务的配置文件,配置事件,在 res/xml下创建accessibility_service_info.xml
//具体属性的说明在第5点有说明 <?xml version="1.0" encoding="utf-8"?> <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_service_description" android:accessibilityeventtypes="typeallmask" android:accessibilityfeedbacktype="feedbackgeneric" android:notificationtimeout="100" android:accessibilityflags="flagdefault" android:canretrievewindowcontent="true" android:packagenames="top.cokernut.sample" android:settingsactivity="com.example.android.accessibility.servicesettingsactivity" />
注册service辅助服务,并且为service附加上第二步创建的xml,看清除下面的一些属性,必须要加,如果有的没加的话是没效果的
<service android:name=".myservice" android:label="辅助功能" android:permission="android.permission.bind_accessibility_service"> <intent-filter> <action android:name="android.accessibilityservice.accessibilityservice" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_info" /> </service>
4 清单文件中添加权限
<uses-permission android:name="android.permission.bind_accessibility_service" />
辅助服务配置文件xml属性说明:
//是否可以检索整个层级下的内容 android:canretrievewindowcontent="true"级下的信息 //事件通知触发点,比如窗口打开,滑动,焦点变化,长按等。 android:accessibilityeventtypes="typeallmask" #types_all_mask:所有类型 #type_view_clicked :单击 #type_view_long_clicked :长按 #type_view_selected :选中 #type_view_focused :获取焦点 #type_view_text_changed :文字改变 #type_window_state_changed :窗口状态改变 //表示反馈方式,比如是语音播放,还是震动 android:accessibilityfeedbacktype="feedbackgeneric" //接受事件的时间间隔,通常将其设置为100即可. android:notificationtimeout="100" //表示该服务是用来单独监听哪个应用的产生的事件,其他的都会过滤,如果不填就是对所有的应用进行监听,填入包名即可。 android:packagenames="top.cokernut.sample" //在代码中我们就可以通过node节点来getviewidresourcename()获取对应的节点的id android:accessibilityflags="flagdefault"
提供一个accessibilityservice的基类,集成了一些常用方法:
public class baseaccessibilityservice extends accessibilityservice { private accessibilitymanager maccessibilitymanager; private context mcontext; private static baseaccessibilityservice minstance; public void init(context context) { mcontext = context.getapplicationcontext(); maccessibilitymanager = (accessibilitymanager) mcontext.getsystemservice(context.accessibility_service); } public static baseaccessibilityservice getinstance() { if (minstance == null) { minstance = new baseaccessibilityservice(); } return minstance; } /** * check当前辅助服务是否启用 * * @param servicename servicename * @return 是否启用 */ private boolean checkaccessibilityenabled(string servicename) { list<accessibilityserviceinfo> accessibilityservices = maccessibilitymanager.getenabledaccessibilityservicelist(accessibilityserviceinfo.feedback_generic); for (accessibilityserviceinfo info : accessibilityservices) { if (info.getid().equals(servicename)) { return true; } } return false; } /** * 前往开启辅助服务界面 */ public void goaccess() { intent intent = new intent(settings.action_accessibility_settings); intent.setflags(intent.flag_activity_new_task); mcontext.startactivity(intent); } /** * 模拟点击事件 * * @param nodeinfo nodeinfo */ public void performviewclick(accessibilitynodeinfo nodeinfo) { if (nodeinfo == null) { return; } while (nodeinfo != null) { if (nodeinfo.isclickable()) { nodeinfo.performaction(accessibilitynodeinfo.action_click); break; } nodeinfo = nodeinfo.getparent(); } } /** * 模拟返回操作 */ public void performbackclick() { try { thread.sleep(500); } catch (interruptedexception e) { e.printstacktrace(); } performglobalaction(global_action_back); } /** * 模拟下滑操作 */ public void performscrollbackward() { try { thread.sleep(500); } catch (interruptedexception e) { e.printstacktrace(); } performglobalaction(accessibilitynodeinfo.action_scroll_backward); } /** * 模拟上滑操作 */ public void performscrollforward() { try { thread.sleep(500); } catch (interruptedexception e) { e.printstacktrace(); } performglobalaction(accessibilitynodeinfo.action_scroll_forward); } /** * 查找对应文本的view * * @param text text * @return view */ public accessibilitynodeinfo findviewbytext(string text) { return findviewbytext(text, false); } /** * 查找对应文本的view * * @param text text * @param clickable 该view是否可以点击 * @return view */ public accessibilitynodeinfo findviewbytext(string text, boolean clickable) { accessibilitynodeinfo accessibilitynodeinfo = getrootinactivewindow(); if (accessibilitynodeinfo == null) { return null; } list<accessibilitynodeinfo> nodeinfolist = accessibilitynodeinfo.findaccessibilitynodeinfosbytext(text); if (nodeinfolist != null && !nodeinfolist.isempty()) { for (accessibilitynodeinfo nodeinfo : nodeinfolist) { if (nodeinfo != null && (nodeinfo.isclickable() == clickable)) { return nodeinfo; } } } return null; } /** * 查找对应id的view * * @param id id * @return view */ @targetapi(build.version_codes.jelly_bean_mr2) public accessibilitynodeinfo findviewbyid(string id) { accessibilitynodeinfo accessibilitynodeinfo = getrootinactivewindow(); if (accessibilitynodeinfo == null) { return null; } list<accessibilitynodeinfo> nodeinfolist = accessibilitynodeinfo.findaccessibilitynodeinfosbyviewid(id); if (nodeinfolist != null && !nodeinfolist.isempty()) { for (accessibilitynodeinfo nodeinfo : nodeinfolist) { if (nodeinfo != null) { return nodeinfo; } } } return null; } public void clicktextviewbytext(string text) { accessibilitynodeinfo accessibilitynodeinfo = getrootinactivewindow(); if (accessibilitynodeinfo == null) { return; } list<accessibilitynodeinfo> nodeinfolist = accessibilitynodeinfo.findaccessibilitynodeinfosbytext(text); if (nodeinfolist != null && !nodeinfolist.isempty()) { for (accessibilitynodeinfo nodeinfo : nodeinfolist) { if (nodeinfo != null) { performviewclick(nodeinfo); break; } } } } @targetapi(build.version_codes.jelly_bean_mr2) public void clicktextviewbyid(string id) { accessibilitynodeinfo accessibilitynodeinfo = getrootinactivewindow(); if (accessibilitynodeinfo == null) { return; } list<accessibilitynodeinfo> nodeinfolist = accessibilitynodeinfo.findaccessibilitynodeinfosbyviewid(id); if (nodeinfolist != null && !nodeinfolist.isempty()) { for (accessibilitynodeinfo nodeinfo : nodeinfolist) { if (nodeinfo != null) { performviewclick(nodeinfo); break; } } } } /** * 模拟输入 * * @param nodeinfo nodeinfo * @param text text */ public void inputtext(accessibilitynodeinfo nodeinfo, string text) { if (build.version.sdk_int >= build.version_codes.lollipop) { bundle arguments = new bundle(); arguments.putcharsequence(accessibilitynodeinfo.action_argument_set_text_charsequence, text); nodeinfo.performaction(accessibilitynodeinfo.action_set_text, arguments); } else if (build.version.sdk_int >= build.version_codes.jelly_bean_mr2) { clipboardmanager clipboard = (clipboardmanager) getsystemservice(context.clipboard_service); clipdata clip = clipdata.newplaintext("label", text); clipboard.setprimaryclip(clip); nodeinfo.performaction(accessibilitynodeinfo.action_focus); nodeinfo.performaction(accessibilitynodeinfo.action_paste); } } @override public void onaccessibilityevent(accessibilityevent event) { } @override public void oninterrupt() { } }
四、qq抢红包
(一)抢红包流程:
- 通知栏收到qq的消息,发现是qq红包,模拟点击消息进入聊天页面
- 检索页面上的所有元素,发现有包含“点击拆开”的字眼,就模拟点击打开红包窗口
- 一两秒后执行back操作,关闭红包窗口。
- 继续等待消息来到。
(二)实现功能:
- 锁屏抢红包(不可以有密码或者图案之类的锁屏)
- 口令红包,自动输入口令并且发送
- 抢完红包后,自动回复感谢语,可在红包设置里自行设置内容
- 其他的功能就没继续往下做了,知道方法,其他都可能慢慢研究出来。
(三)抢红包辅助功能类,注释都写好了,很好理解,类中有用到qqconstant类,在第四点贴出了代码
/** * 描述:qq抢红包服务 * 作者:卜俊文 * 邮箱:344176791@qq.com * 日期:2017/11/6 上午9:25 */ public class envelopeservice extends baseaccessibilityservice { //锁屏、解锁相关 private keyguardmanager.keyguardlock kl; //唤醒屏幕相关 private powermanager.wakelock wl = null; private long delaytime = 0;//延迟抢的时间 /** * 描述:所有事件响应的时候会回调 * 作者:卜俊文 * 邮箱:344176791@qq.com * 日期:2017/11/6 上午9:26 */ @override public void onaccessibilityevent(accessibilityevent event) { //验证抢红包的开关 if (!invalidenable()) { return; } //事件类型 int eventtype = event.geteventtype(); //获取包名 charsequence packagename = event.getpackagename(); if (textutils.isempty(packagename)) { return; } switch (eventtype) { //状态栏变化 case accessibilityevent.type_notification_state_changed: if (qqconstant.qq_package_name.equals(packagename)) { //处理状态栏上qq的消息,如果是红包就跳转过去 progressqqstatusbar(event); } break; //窗口切换的时候回调 case accessibilityevent.type_window_state_changed: case accessibilityevent.type_window_content_changed: if (qqconstant.qq_package_name.equals(packagename)) { //处理正在qq聊天窗口页面,有其他群或者人有新的红包提醒,跳转过去。 progressnewmessage(event); //处理聊天页面的红包 progressqqchat(event); } break; } } /** * 描述:处理新消息 * 作者:卜俊文 * 邮箱:344176791@qq.com * 日期:2017/11/3 下午11:21 */ private void progressnewmessage(accessibilityevent event) { if (event == null) { return; } accessibilitynodeinfo source = event.getsource(); if (source == null) { return; } //根据event的source里的text,来判断这个消息是否包含[qq红包]的字眼,有的话就跳转过去 charsequence text = source.gettext(); if (!textutils.isempty(text) && text.tostring().contains(qqconstant.qq_envelope_keyword)) { performviewclick(source); } } /** * 描述:验证抢红包是否开启 * 作者:卜俊文 * 邮箱:344176791@qq.com * 日期:2017/11/3 下午4:57 */ private boolean invalidenable() { return settingconfig.getinstance().getreenable(); } /** * 描述:处理qq状态栏 * 作者:卜俊文 * 邮箱:344176791@qq.com * 日期:2017/11/1 下午1:49 */ public void progressqqstatusbar(accessibilityevent event) { list<charsequence> text = event.gettext(); //开始检索界面上是否有qq红包的文本,并且他是通知栏的信息 if (text != null && text.size() > 0) { for (charsequence charsequence : text) { if (charsequence.tostring().contains(qqconstant.qq_envelope_keyword)) { //说明存在红包弹窗,马上进去 if (event.getparcelabledata() != null && event.getparcelabledata() instanceof notification) { notification notification = (notification) event.getparcelabledata(); if (notification == null) { return; } pendingintent pendingintent = notification.contentintent; if (pendingintent == null) { return; } try { //要跳转之前,先进行解锁屏幕,然后再跳转,有可能你现在屏幕是锁屏状态,先进行解锁,然后打开页面,有密码的可能就不行了 wakeupandunlock(myapp.context); //跳转 pendingintent.send(); } catch (pendingintent.canceledexception e) { e.printstacktrace(); } } } } } } /** * 描述:处理qq聊天红包 * 作者:卜俊文 * 邮箱:344176791@qq.com * 日期:2017/11/1 下午1:56 */ public void progressqqchat(accessibilityevent event) { if (textutils.isempty(event.getclassname())) { return; //如果当前页面是聊天页面或者当前的描述信息是"返回消息界面",就肯定是对话页面 } //验证当前事件是否符合查询页面上的红包 if (!invalidenvelopeui(event)) { return; } //延迟点击红包,防止被检测到开了抢红包,不过感觉还是感觉会被检测到,应该有的效果吧... try { thread.sleep(delaytime); } catch (interruptedexception e) { e.printstacktrace(); } //普通红包,检索点击拆开的字眼。 list<accessibilitynodeinfo> envelope = findviewlistbytext(qqconstant.qq_click_take_apart, false); //处理普通红包 progressnormal(envelope); //口令红包,检索口令红包的字眼。 list<accessibilitynodeinfo> passwordlist = findviewlistbytext(qqconstant.qq_click_password_dialog, false); //处理口令红包 progresspassword(passwordlist); } /** * 描述:验证是否现在是在聊天页面,可以进行抢红包处理 * 作者:卜俊文 * 邮箱:344176791@qq.com * 日期:2017/11/3 上午11:52 * * @param event */ public boolean invalidenvelopeui(accessibilityevent event) { //判断类名是否是聊天页面 if (!qqconstant.qq_im_chat_activity.equals(event.getclassname().tostring())) { return true; } //判断页面中的元素是否有点击拆开的文本,有就返回可以进行查询了 int recordcount = event.getrecordcount(); if (recordcount > 0) { for (int i = 0; i < recordcount; i++) { accessibilityrecord record = event.getrecord(i); if (record == null) { break; } list<charsequence> text = record.gettext(); if (text != null && text.size() > 0 && text.contains(qqconstant.qq_click_take_apart)) { //如果文本中有点击拆开的字眼,就返回可以进行查询了 return true; } } } return false; } /** * 回到系统桌面 */ private void back2home(int time) { try { thread.sleep(time); } catch (interruptedexception e) { e.printstacktrace(); } intent home = new intent(intent.action_main); home.setflags(intent.flag_activity_new_task); home.addcategory(intent.category_home); startactivity(home); } /** * 描述:处理普通红包 * 作者:卜俊文 * 邮箱:344176791@qq.com * 日期:2017/11/1 下午5:02 */ public void progressnormal(list<accessibilitynodeinfo> passwordlist) { if (passwordlist != null && passwordlist.size() > 0) { for (accessibilitynodeinfo accessibilitynodeinfo : passwordlist) { if (accessibilitynodeinfo != null && !textutils.isempty(accessibilitynodeinfo.gettext()) && qqconstant.qq_click_take_apart.equals(accessibilitynodeinfo.gettext().tostring())) { //点击拆开红包 performviewclick(accessibilitynodeinfo); //回复感谢信息,根据配置文件中配置的回复信息回复 string rereplymessage = settingconfig.getinstance().getrereplymessage(); if (!textutils.isempty(rereplymessage)) { replymessage(rereplymessage); } } } //最后延迟事件触发返回事件,关闭红包页面 performbackclick(1200); } } /** * 描述:处理口令红包 * 作者:卜俊文 * 邮箱:344176791@qq.com * 日期:2017/11/1 下午4:58 * * @param passwordlist */ public void progresspassword(list<accessibilitynodeinfo> passwordlist) { if (passwordlist != null && passwordlist.size() > 0) { for (accessibilitynodeinfo accessibilitynodeinfo : passwordlist) { if (accessibilitynodeinfo != null && !textutils.isempty(accessibilitynodeinfo.gettext()) && qqconstant.qq_click_password_dialog.equals(accessibilitynodeinfo.gettext().tostring())) { //如果口令红包存在,就在输入框中进行输入,然后发送 accessibilitynodeinfo parent = accessibilitynodeinfo.getparent(); if (parent != null) { charsequence contentdescription = parent.getcontentdescription(); if (!textutils.isempty(contentdescription)) { //1. 获取口令 string key = (string) contentdescription; if (key.contains(",") && key.contains("口令:")) { key = key.substring(key.indexof("口令:") + 3, key.lastindexof(",")); } log.e("口令", key); //2. 填写口令到编辑框上然后进行发送 replymessage(key); //返回,关闭红包页面 performbackclick(1200); } } } } } } /** * 唤醒屏幕并解锁权限 * <uses-permission android:name="android.permission.wake_lock" /> */ @suppresslint("wakelock") @suppresswarnings("deprecation") public void wakeupandunlock(context context) { // 点亮屏幕 wl.acquire(); // 释放 wl.release(); // 解锁 kl.disablekeyguard(); } /** * 描述:回复消息,无延迟 * 作者:卜俊文 * 邮箱:344176791@qq.com * 日期:2017/11/3 下午5:10 */ public void replymessage(string key) { replymessage(key, 0); } /** * 描述:回复消息 * 作者:卜俊文 * 邮箱:344176791@qq.com * 日期:2017/11/3 下午5:10 */ public void replymessage(string key, int time) { //延迟 if (time > 0) { try { thread.sleep(time); } catch (interruptedexception e) { e.printstacktrace(); } } //获取qq聊天页面输入框 accessibilitynodeinfo chat_edit = findviewbyid(qqconstant.qq_chat_message_input); if (chat_edit != null) { //把口令粘贴到输入框中 pastatext(chat_edit, myapp.context, key); //获取qq聊天页面发送消息按钮 accessibilitynodeinfo sendmessage = findviewbyid(qqconstant.qq_chat_message_send); //然后就按下发送按钮 if (sendmessage != null && button.class.getname().equals(sendmessage.getclassname())) { performviewclick(sendmessage); } } } @override public void oninterrupt() { } @override protected void onserviceconnected() { super.onserviceconnected(); // 获取电源管理器对象 powermanager pm = (powermanager) myapp.context .getsystemservice(context.power_service); // 获取powermanager.wakelock对象,后面的参数|表示同时传入两个值,最后的是调试用的tag wl = pm.newwakelock( powermanager.acquire_causes_wakeup | powermanager.screen_bright_wake_lock, "bright"); keyguardmanager km = (keyguardmanager) myapp.context.getsystemservice(context.keyguard_service); kl = km.newkeyguardlock("unlock"); //初始化屏幕的监听 screenlistener screenlistener = new screenlistener(myapp.context); screenlistener.begin(new screenlistener.screenstatelistener() { @override public void onscreenon() { log.e("screenlistener", "屏幕打开了"); } @override public void onscreenoff() { //在屏幕关闭的时候,进行锁屏,不执行的话,锁屏就失效了,因为要实现锁屏状态下也可以进行抢红包。 log.e("screenlistener", "屏幕关闭了"); if (kl != null) { kl.disablekeyguard(); kl.reenablekeyguard(); } } @override public void onuserpresent() { log.e("screenlistener", "解锁了"); } }); } @override public void ondestroy() { super.ondestroy(); } }
(四)qq辅助服务里有用到的常量
public class qqconstant { //qq的应用包名 public static final string qq_package_name = "com.tencent.mobileqq"; //状态栏红包关键字 public static final string qq_envelope_keyword = "[qq红包]"; //qq聊天页面 public static final string qq_im_chat_activity = "com.tencent.mobileqq.activity.splashactivity"; //点击拆开 public static final string qq_click_take_apart = "点击拆开"; //口令红包 public static final string qq_click_password_dialog = "口令红包"; //聊天页面,输入框id public static final string qq_chat_message_input = "com.tencent.mobileqq:id/input"; //聊天页面,发送按钮 public static final string qq_chat_message_send = "com.tencent.mobileqq:id/fun_btn"; }
五、红包问题
用的时候偶尔会被qq检测到用了红包插件,可能是因为抢的速度太快,导致数据不符合正常的点击时间,我有加入一个延迟时间,不知道有没有效果,如果有知道的也可以留言,谢谢。
在qq的主页面上,收到消息的时候通知栏是不会通知的,所以这里不能进行解析通知栏跳转聊天页面,没有找到什么元素可以告诉我怎么进入红包的聊天页面,如果有知道的可以留言,谢谢。
这种辅助服务的方式抢红包,进入聊天页面后,他检索字段只会检索当前页面可视的元素,某些红包要是在聊天记录上面看不见的,需要滑动上去才可以触发解析红包,不过一般不会一次性10个红包都发出来吧,嘿嘿。
六、总结
学习制作了这个项目,也了解了辅助功能的使用,感觉这个还是可以做很多东西的,上面已经贴出了核心代码,要源码的可以点击。抢红包源码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: 干货!这些微博营销技巧你必须知道
推荐阅读
-
Android辅助功能实现自动抢红包(附源码)
-
Android开发实现高仿优酷的客户端图片左右滑动切换功能实例【附源码下载】
-
Android辅助功能实现自动抢红包(附源码)
-
Android开发实现可拖动排序的ListView功能【附源码下载】
-
Android开发实现的简单计算器功能【附完整demo源码下载】
-
Android编程实现的微信支付功能详解【附Demo源码下载】
-
Android开发实现高仿优酷的客户端图片左右滑动切换功能实例【附源码下载】
-
Android开发实现Switch控件修改样式功能示例【附源码下载】
-
Android开发实现可拖动排序的ListView功能【附源码下载】
-
Android开发实现的简单计算器功能【附完整demo源码下载】