欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android辅助功能实现自动抢红包(附源码)

程序员文章站 2023-12-03 11:25:58
一、描述 最近看到同事有用抢红包的软件,就想看看抢红包的具体实现是如何的,所以了解了一下,有用辅助功能实现的,所以在下面的示例中会展示一个抢红包的小demo,附带源码抢红...

一、描述

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

二、效果图

Android辅助功能实现自动抢红包(附源码)

在桌面收到红包进行抢

Android辅助功能实现自动抢红包(附源码)

在聊天页面收到口令红包

三、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抢红包

(一)抢红包流程:

  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<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个红包都发出来吧,嘿嘿。

六、总结

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。