Android辅助功能实现自动抢红包
发布日期:2021-09-03 18:04:47 浏览次数:3 分类:技术文章

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

###一、描述

最近看到同事有用抢红包的软件,就想看看抢红包的具体实现是如何的,所以了解了一下,有用辅助功能实现的,所以在下面的示例中会展示一个抢红包的小Demo,附带源码。

###二、效果图

###三、AccessibilityService使用

  1. 创建辅助服务类,继承AccessibilityService,实现两个接口,接收系统的事件
public class MyService extends AccessibilityService {    @Override    public void onAccessibilityEvent(AccessibilityEvent event) {            }    @Override    public void onInterrupt() {            }}复制代码
2. 辅助服务的配置文件,配置事件,在 res/xml下创建accessibility_service_info.xml
//具体属性的说明在第5点有说明
复制代码
3. 注册Service辅助服务,并且为Service附加上第二步创建的xml,看清除下面的一些属性,必须要加,如果有的没加的话是没效果的
复制代码
4 清单文件中添加权限 ``` ```
  1. 辅助服务配置文件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()获取对应的节点的idandroid: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
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
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
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
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
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抢红包

(一)抢红包流程:

  1. 通知栏收到QQ的消息,发现是QQ红包,模拟点击消息进入聊天页面
  2. 检索页面上的所有元素,发现有包含“点击拆开”的字眼,就模拟点击打开红包窗口
  3. 一两秒后执行Back操作,关闭红包窗口。
  4. 继续等待消息来到。

(二)实现功能:

  1. 锁屏抢红包(不可以有密码或者图案之类的锁屏)
  2. 口令红包,自动输入口令并且发送
  3. 抢完红包后,自动回复感谢语,可在红包设置里自行设置内容
  4. 其他的功能就没继续往下做了,知道方法,其他都可能慢慢研究出来。

(三)抢红包辅助功能类,注释都写好了,很好理解,类中有用到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
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
envelope = findViewListByText(QQConstant.QQ_CLICK_TAKE_APART, false); //处理普通红包 progressNormal(envelope); //口令红包,检索口令红包的字眼。 List
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
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
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
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); } } } } } } /** * 唤醒屏幕并解锁权限 *
*/ @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";}复制代码

###五、红包问题

  1. 用的时候偶尔会被QQ检测到用了红包插件,可能是因为抢的速度太快,导致数据不符合正常的点击时间,我有加入一个延迟时间,不知道有没有效果,如果有知道的也可以留言,谢谢。

  2. 在QQ的主页面上,收到消息的时候通知栏是不会通知的,所以这里不能进行解析通知栏跳转聊天页面,没有找到什么元素可以告诉我怎么进入红包的聊天页面,如果有知道的可以留言,谢谢。

  3. 这种辅助服务的方式抢红包,进入聊天页面后,他检索字段只会检索当前页面可视的元素,某些红包要是在聊天记录上面看不见的,需要滑动上去才可以触发解析红包,不过一般不会一次性10个红包都发出来吧,嘿嘿。

###六、总结

学习制作了这个项目,也了解了辅助功能的使用,感觉这个还是可以做很多东西的,上面已经贴出了核心代码,要源码的可以点击。

欢迎关注我的微信公众号,分享更多技术文章。

转载于:https://juejin.im/post/5b3ac634e51d4555a73e3d53

转载地址:https://blog.csdn.net/weixin_34194317/article/details/91486745 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:逆袭!? 期待下一个“BCH”出现
下一篇:java B2B2C Springboot多租户电子商城系统-Spring Cloud Stream(消息驱动)

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年03月29日 03时54分57秒