文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android抢红包插件实现原理浅析

2022-06-06 08:16

关注

抢红包,先看效果图~

实现自动抢红包,解决问题有两点:

一:如何实时监听发红包的事件

二:如何在红包到来的时候自动进入页面并自动点击红包

一、如何获取红包到来的事件

为了获取红包到来状态栏的变化,我们要用到一个类:Accessibility

许多Android使用者因为各种情况导致他们要以不同的方式与手机交互。
这包括了有些用户由于视力上,身体上,年龄上的问题致使他们不能看完整的屏幕或者使用触屏,也包括了无法很好接收到语音信息和提示的听力能力比较弱的用户。
Android提供了Accessibility功能和服务帮助这些用户更加简单地操作设备,包括文字转语音(这个不支持中文),触觉反馈,手势操作,轨迹球和手柄操作。

OK,了解到这一点,那么接下来就顺利点了,首先来看看Accessibility以及AccessibilityService的使用
 1.新建一个类继承AccessibilityService,并在AndroidManifest文件里注册它:


 <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" /> 
<application> 
<service  android:name="com.zkhb.weixinqinghongbao.service.QiangHongBaoService"    
android:label="@string/app_name"   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/qianghongbao_service_config" />
   </service>
</application>

在子类QiangHongBaoService里实现几个重要的重载方法:

onServiceConnected() - 可选。系统会在成功连接上你的服务的时候调用这个方法,在这个方法里你可以做一下初始化工作,例如设备的声音震动管理,也可以调用setServiceInfo()进行配置工作。
onAccessibilityEvent() - 必须。通过这个函数可以接收系统发送来的AccessibilityEvent,接收来的AccessibilityEvent是经过过滤的,过滤是在配置工作时设置的。

onInterrupt() - 必须。这个在系统想要中断AccessibilityService返给的响应时会调用。在整个生命周期里会被调用多次。
onUnbind() - 可选。在系统将要关闭这个AccessibilityService会被调用。在这个方法中进行一些释放资源的工作。

然后在/res/xml/accessibility_service_config.xml:


<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged"
  android:accessibilityFeedbackType="feedbackGeneric"
  android:accessibilityFlags=""
  android:canRetrieveWindowContent="true"
  android:description="@string/accessibility_description"
  android:notificationTimeout="100"
  android:packageNames="com.tencent.mm" />

二、主要关注点以及如何在红包到来的时候自动进入页面并自动点击红包

在onAccessibilityEvent方法中监听状态栏的变化,主要有:

AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED、

AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED、

AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED 

在响应窗体以及窗体内容变化时处理相关逻辑,获取带微信消息时以下代码打开消息:


 //将微信的通知栏消息打开
    Notification notification = (Notification) event.getParcelableData();
    PendingIntent pendingIntent = notification.contentIntent;
    try {
      pendingIntent.send();
    } catch (PendingIntent.CanceledException e) {
      e.printStackTrace();
    }

具体代码网上有很多,一般都是通过:findAccessibilityNodeInfosByText、findAccessibilityNodeInfosByViewId查找文本或者资源节点进行点击操作,但新版微信开红包页面进行了处理,没有文本信息,而如果采用:

图中resouces-id这种形式就可能出现这种情况:

在了解整个核心后,获取事件不外乎就是通过文本与id判断,那么就可以将文本改为图标方式,将id改为动态id(每次显示都是随机生成),这样一来就可以提高外挂的门槛。

如何进行规避呢,目前我想的是既然开红包的页面文本和ID都有可能会变,那我们能不能找个不变的进行判断呢,考虑过后,我觉得最可能不变的地方就是开红包这个按钮的位置,也就是图中的bounds,于是在思考过后有了下面的处理: 


 for (int i = 0; i < nodeInfo.getChildCount(); i++) {
      //Log.e("TAG", "getViewIdResourceName :"+nodeInfo.getChild(i).getViewIdResourceName());
      Rect outBounds = new Rect();
      nodeInfo.getChild(i).getBoundsInScreen(outBounds);
      int left_dp = px2dip(this, 400);
      int top_dp = px2dip(this, 1035);
      int right_dp = px2dip(this, 682);
      int bottom_dp = px2dip(this, 1320);
      int left_px = dip2px(this, left_dp);
      int top_px = dip2px(this, top_dp);
      int right_px = dip2px(this, right_dp);
      int bottom_px = dip2px(this, bottom_dp);
      Rect mStandar = new Rect(left_px,top_px,right_px,bottom_px);
      if(mStandar.contains(outBounds)){
        Log.e("TAG", "outBounds.left :"+outBounds.left+";outBounds.top :"+outBounds.top+";outBounds.right :"+outBounds.right+";outBounds.bottom :"+outBounds.bottom);
        nodeInfo.getChild(i).performAction(AccessibilityNodeInfo.ACTION_CLICK);
        break;
      }
    }

这里取的矩形区域要比按钮区域稍大点,然后判断到开红包页面按钮是否在我们预先设置的区域内,如果在,我们直接进行点击开红包操作:AccessibilityNodeInfo.ACTION_CLICK。
其他比如如何防止重复抢等细节问题,也是要处理的问题。
好了,直接放下关键代码,仅供参考:


package com.zkhb.weixinqinghongbao.service;
import java.util.Date;
import java.util.List;
import android.accessibilityservice.AccessibilityService;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.app.KeyguardManager.KeyguardLock;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
import com.zkhb.weixinqinghongbao.MainActivity;
import com.zkhb.weixinqinghongbao.R;
import com.zkhb.weixinqinghongbao.entity.HongBaoInfo;
import com.zkhb.weixinqinghongbao.util.DateFormatUtils;
import com.zkhb.weixinqinghongbao.util.LogUtil;

@SuppressLint("NewApi") 
public class QiangHongBaoService extends AccessibilityService {
//  static final String TAG = "QiangHongBao";
  
  static final String WECHAT_PACKAGENAME = "com.tencent.mm";
  
  static final String HONGBAO_TEXT_KEY = "[微信红包]";
  
  static final String HONGBAO_TEXT_KEY1 = "微信红包";
  private static final int ENVELOPE_RETURN = 0;
  private static final String LOCK_TAG = "屏幕";
  Handler handler = new Handler();
  
//  public boolean isInMM=false;
  
//  public boolean ISCLICKED=false;
  
  public static boolean ISCOMINQIANGCHB=false;
  //真正的
  public static boolean ISCOMINQIANGCHB2=false;
  //真正的判断
  public static boolean ISCOMINQIANGCHB3=false;
   
  private static boolean ISCOMNOTIFY=false;
  private PowerManager pm;
  //点亮屏幕
  private WakeLock mWakeLock;
   //解锁锁定屏幕
  private KeyguardLock keyguardLock;
  
  private static boolean islock=false;
  
  private NotificationManager n_manager;
  public void unlock(){
    if(pm==null){
      pm = (PowerManager) getApplication().getSystemService(Context.POWER_SERVICE);
    }
    boolean isScreenOn = pm.isScreenOn();//如果为true,则表示屏幕“亮”了,否则屏幕“暗”了。
    if(!isScreenOn){
      islock=true;
      KeyguardManager keyguardManager = (KeyguardManager)getSystemService(KEYGUARD_SERVICE);
      keyguardLock = keyguardManager.newKeyguardLock(LOCK_TAG);
      keyguardLock.disableKeyguard();
      mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, LOCK_TAG); 
      mWakeLock.acquire();
    }
  }
  public void lock(){
    if(islock){
      //释放屏幕常亮锁
      if(null != mWakeLock) {
        mWakeLock.release();
      }
      //屏幕锁定
      if(keyguardLock!=null){
        keyguardLock.reenableKeyguard();
      }
    }
  }
  @Override
  public void onAccessibilityEvent(AccessibilityEvent event) {
    final int eventType = event.getEventType();
    LogUtil.info("事件---->" + event);
    //通知栏事件
    if(eventType == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
      unlock();
      List<CharSequence> texts = event.getText();
      if(!texts.isEmpty()) {
        for(CharSequence t : texts) {
          String text = String.valueOf(t);
          if(text.contains(HONGBAO_TEXT_KEY)) {
            ISCOMNOTIFY=true;
            ISCOMINQIANGCHB=false;
            openNotify(event);
            break;
          }
        }
      }
    } else if(eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
      unlock();
      openHongBao(event);
//      AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
//      List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText = nodeInfo.findAccessibilityNodeInfosByText("领取红包");
//      if(findAccessibilityNodeInfosByText.isEmpty()){
//       isInMM=false;
//      }else{
//       isInMM=true;
//      }
//      List<CharSequence> text = event.getText();
//      if(text.size()>=0){
//       CharSequence charSequence = text.get(0);
////        if(charSequence.equals("微信")){
////          isInMM=true;
////        }else{
////          isInMM=false;
////        }
//      }
    }else if(eventType==AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED && ISCOMNOTIFY){
      unlock();
      openHongBao(event);
      List<AccessibilityNodeInfo> InfoText = getRootInActiveWindow().findAccessibilityNodeInfosByText("领取红包");
      if(!InfoText.isEmpty()){
        checkKey2();
        ISCOMNOTIFY=false;
      }
    }
  }
  
  @Override
  public boolean onUnbind(Intent intent) {
    Toast.makeText(this, "断开抢红包服务", Toast.LENGTH_SHORT).show();
    ISCOMINQIANGCHB=false;
    islock=false;
    ISCOMINQIANGCHB2=false;
    ISCOMINQIANGCHB3=false;
    ISCOMNOTIFY=false;
    setNotification("已关闭抢红包小助手服务~~",Notification.FLAG_AUTO_CANCEL,"已关闭抢红包小助手服务~~~");
    return super.onUnbind(intent);
  }
  @Override
  public void onInterrupt() {
    Toast.makeText(this, "中断抢红包服务", Toast.LENGTH_SHORT).show();
  }
  @Override
  protected void onServiceConnected() {
    super.onServiceConnected();
    ISCOMINQIANGCHB=false;
    ISCOMINQIANGCHB2=false;
    ISCOMINQIANGCHB3=false;
    islock=false;
    ISCOMNOTIFY=false;
    setNotification("已开启抢红包小助手服务~~",Notification.FLAG_NO_CLEAR,"已开启抢红包小助手服务~~~");
    Toast.makeText(this, "连接抢红包服务", Toast.LENGTH_SHORT).show();
  }
  private void setNotification(String content,int flags,String title) {
    if(n_manager==null){
      n_manager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
    }
    n_manager.cancelAll();
    Notification notification=new Notification(R.drawable.ic_launcher, content, System.currentTimeMillis());
//    notification.defaults |= Notification.DEFAULT_VIBRATE;
//    long[] vibrate = {0,100,200,300}; //0毫秒后开始振动,振动100毫秒后停止,再过200毫秒后再次振动300毫秒
//    notification.vibrate=vibrate;
    notification.flags |= flags; //表明在点击了通知栏中的"清除通知"后,此通知不清除,
    Intent notificationIntent = new Intent(this,MainActivity.class); //点击该通知后要跳转的Activity
    PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(),0,notificationIntent,0);
    notification.setLatestEventInfo(getApplicationContext(), title, "进入微信抢红包~~", contentIntent);
    n_manager.notify(0, notification);
  }
  private void sendNotifyEvent(){
    AccessibilityManager manager= (AccessibilityManager)getSystemService(ACCESSIBILITY_SERVICE);
    if (!manager.isEnabled()) {
      return;
    }
    AccessibilityEvent event=AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
    event.setPackageName(WECHAT_PACKAGENAME);
    event.setClassName(Notification.class.getName());
    CharSequence tickerText = HONGBAO_TEXT_KEY;
    event.getText().add(tickerText);
    manager.sendAccessibilityEvent(event);
  }
  
  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  private void openNotify(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();
    }
  }
  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  private void openHongBao(AccessibilityEvent event) {
    if("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI".equals(event.getClassName())) {
      //点中了红包,下一步就是去拆红包
      ISCOMINQIANGCHB=true;
      ISCOMINQIANGCHB2=true;
      checkKey1();
    } else if("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI".equals(event.getClassName())) {
      //拆完红包后看详细的纪录界面
       LogUtil.info("事件---->com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI");
       //nonething
//     if(ISCOMINQIANGCHB){
//       ISCOMINQIANGCHB=false;
//     }
       if(ISCOMINQIANGCHB2){
         ISCOMINQIANGCHB3=true;
       }else{
         ISCOMINQIANGCHB3=false;
       }
      checkKey3();
      ISCOMINQIANGCHB=true;
      ISCOMINQIANGCHB2=false;
      performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
      if(getSharedPreferences("config", Context.MODE_PRIVATE).getBoolean("auto", false)){
        performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME);
      }
      lock();
    } else if("com.tencent.mm.ui.LauncherUI".equals(event.getClassName())) {
//     isInMM=true;
      //在聊天界面,去点中红包
      LogUtil.info("事件---->com.tencent.mm.ui.LauncherUI");
      checkKey2();
    }
  }
  @SuppressLint("NewApi") @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  private void checkKey3() {
    AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
     if(nodeInfo == null) {
       LogUtil.info("rootWindow为空333");
       return;
     }
     //TODO
     List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText = nodeInfo.findAccessibilityNodeInfosByText("元");
     if(findAccessibilityNodeInfosByText.size()>=0){
       AccessibilityNodeInfo accessibilityNodeInfo2 = findAccessibilityNodeInfosByText.get(0).getParent();
       CharSequence money = accessibilityNodeInfo2.getChild(2).getText();
       CharSequence name = accessibilityNodeInfo2.getChild(0).getText();
       if(ISCOMINQIANGCHB3){
         HongBaoInfo info=new HongBaoInfo();
         info.setStrDateTime(DateFormatUtils.format("yyyy-MM-dd HH:mm:ss", new Date()));
         info.setStrMoney(money+"");
         info.setStrName(name+"");
         info.save();
       }
       Toast.makeText(getApplicationContext(), money+":::"+name, 0).show();
     }
//    List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/aw8");
//    AccessibilityNodeInfo accessibilityNodeInfo = findAccessibilityNodeInfosByViewId.get(0);
//    CharSequence text = accessibilityNodeInfo.getText(); 
  }
  private void checkKey1() {
    AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
    if(nodeInfo == null) {
      LogUtil.info("rootWindow为空11111");
      return;
    }
    for (int i = 0; i < nodeInfo.getChildCount(); i++) {
      Log.e("TAG", "getViewIdResourceName :"+nodeInfo.getChild(i).getViewIdResourceName());
      Rect outBounds = new Rect();
      nodeInfo.getChild(i).getBoundsInScreen(outBounds);
      int left_dp = px2dip(this, 400);
      int top_dp = px2dip(this, 1035);
      int right_dp = px2dip(this, 682);
      int bottom_dp = px2dip(this, 1320);
      int left_px = dip2px(this, left_dp);
      int top_px = dip2px(this, top_dp);
      int right_px = dip2px(this, right_dp);
      int bottom_px = dip2px(this, bottom_dp);
      Rect mStandar = new Rect(left_px,top_px,right_px,bottom_px);
      if(mStandar.contains(outBounds)){
        Log.e("TAG", "outBounds.left :"+outBounds.left+";outBounds.top :"+outBounds.top+";outBounds.right :"+outBounds.right+";outBounds.bottom :"+outBounds.bottom);
        nodeInfo.getChild(i).performAction(AccessibilityNodeInfo.ACTION_CLICK);
        break;
      }
//     nodeInfo.performAction(action)//[405,1042][675,1312]
    }
    //List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("拆红包");
// List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/b5d");
//    for(AccessibilityNodeInfo n : list) {
//      n.performAction(AccessibilityNodeInfo.ACTION_CLICK);
//    }
  }
  private void checkKey2() {
    AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
    if(nodeInfo == null) {
      LogUtil.info("rootWindow为空222222");
      return;
    }
    List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("领取红包");
    if(list.isEmpty()) {
      list = nodeInfo.findAccessibilityNodeInfosByText(HONGBAO_TEXT_KEY);
      for(AccessibilityNodeInfo n : list) {
        LogUtil.info("-->微信红包:" + n);
        n.performAction(AccessibilityNodeInfo.ACTION_CLICK);
        break;
      }
    } else {
      //最新的红包领起
      AccessibilityNodeInfo parent = list.get(list.size() - 1).getParent();
//     Log.w(TAG, "ISCLICKED::"+ISCLICKED)!ISCLICKED;
      if(parent != null && !ISCOMINQIANGCHB) {
        parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
      }
//      for(int i = list.size() - 1; i >= 0; i --) {
//        AccessibilityNodeInfo parent = list.get(i).getParent();
//        Log.i(TAG, "-->领取红包:" + parent);
//        if(parent != null && parent.isClickable()) {
//          parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
//          break;
//        }
//      }
//      performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
    }
  }
  
  @SuppressLint("NewApi") 
  public void recycle(AccessibilityNodeInfo info, String matchFlag, int type) {
    if (info != null) {
      if (info.getChildCount() == 0) {
        CharSequence desrc = info.getContentDescription();
        switch (type) {
          case ENVELOPE_RETURN://返回
            if (desrc != null && matchFlag.equals(info.getContentDescription().toString().trim())) {
              if (info.isCheckable()) {
                info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
              } else {
                performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
              }
            }
            break;
        }
      } else {
        int size = info.getChildCount();
        for (int i = 0; i < size; i++) {
          AccessibilityNodeInfo childInfo = info.getChild(i);
          if (childInfo != null) {
            LogUtil.info("index: " + i + " info" + childInfo.getClassName() + " : " + childInfo.getContentDescription()+" : "+info.getText());
            recycle(childInfo, matchFlag, type);
          }
        }
      }
    }
  }
  @Override
  public void onDestroy() {
    super.onDestroy();
    lock();
  }
   
  public static int dip2px(Context context, float dpValue) { 
    final float scale = context.getResources().getDisplayMetrics().density; 
    return (int) (dpValue * scale + 0.5f); 
  } 
   
  public static int px2dip(Context context, float pxValue) { 
//    final float scale = context.getResources().getDisplayMetrics().density; 
    return (int) (pxValue / 3 + 0.5f); //3是本人手机的设备密度
  } 
}

AccessibilityService还可以用在智能安装、虚拟按键上都有很多应用,这样的实践只是给我们一种解决问题另外的一种思路,问题嘛,总还是要解决的。

您可能感兴趣的文章:教你一步步实现Android微信自动抢红包Android辅助功能AccessibilityService与抢红包辅助Android实现QQ抢红包插件Android中微信抢红包插件原理解析及开发思路Android辅助功能实现自动抢红包(附源码)Android实现红包雨动画效果分享Android微信红包插件Android实现微信自动抢红包的程序Android微信自动抢红包插件优化和实现SurfaceView实现红包雨平移动画


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯