android拆轮子系列之一步一步教你写微信抢红包插件

上一章《AccessibilityService讲解》我们对AccessibilityService做了基本的讲解,本章我们就利用AccessibilityService做一个抢红包插件。

1.前言

本章编写的代码仅供个人学习使用,禁止用于其他非法行为。技术是把双刃剑希望大家正确对待!

在文章的开头奉送上代码,方便大家对照着学习。

2.代码编写

2.1抢包流程:

做抢红包的插件我们得一步一步来,首先我们应该了解抢红包的流程是怎么样的,回顾一下抢红包的流程:

1. 状态栏出现”[微信红包]”的消息提示,点击进入聊天界面 
2. 点击相应的红包信息,弹出抢红包界面 
3. 在抢红包界面点击”开”,打开红包 
4. 在红包详情页面,查看详情,点击返回按钮返回微信聊天界面.

上面就是抢红包的整个流程。虽然这是整个流程,但是当前微信处于的界面可能出现以下几种:

1.微信在后台运行,而当前手机在浏览其他APP(需要执行1->2->3->4)
2.当前处于微信的首页(需要执行1->2->3->4)
3.处于和某个人聊天的页面(2->3->4)

2.1代码编写

理解了抢红包的流程,我们开始编写代码。上一节讲了,创建一个AccessibilityService的流程很简单如下:
1.继承AccessibilityService类,重写里面的方法
2.在AndroidMainifest中注册
3.添加配置文件

1.下面我们来看一下AccessibilityService的编写

package com.czh.service;
import java.util.ArrayList;
import java.util.List;
import android.accessibilityservice.AccessibilityService;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Intent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;

/**
 * @author 作者 YYD
 * @version 创建时间:2016年12月14日 下午2:46:49
 * @function 未添加
 */
public class RobService extends AccessibilityService {
    /**
     * 此方法用来接收我的需要的各种事件 在accessibility.xml中我们监听了以下事件:
     * typeNotificationStateChanged typeWindowStateChanged
     * typeWindowContentChanged
     * http://www.jianshu.com/p/ba298b8d5a6e  看看这个是否被拆了判断
     */
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        switch (eventType) {
        // 当通知栏发生改变的时候
        case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
            List<CharSequence> texts = event.getText();
            if (!texts.isEmpty()) {
                for (CharSequence text : texts) {
                    String content = text.toString();
                    if (content.contains("[微信红包]")) {
                        if ((event.getParcelableData() != null)
                                && (event.getParcelableData() instanceof Notification)) {
                            Notification notification = (Notification) event
                                    .getParcelableData();
                            PendingIntent pendingIntent = notification.contentIntent;
                            try {
                                pendingIntent.send();
                            } catch (Exception e) {
                            }
                        }
                    }
                }
            }
            break;
        // 当窗口内容发生改变的时候
        case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:

            break;
        // 当窗口状态发生改变的时候
        case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
            String className = event.getClassName().toString();
            if (className.equals("com.tencent.mm.ui.LauncherUI")) { //聊天页
//              getLastPacket();//注释的原因我在方法中写明了,如果放开这个方法就会出现死循环,大家可以尝试一下。
//              inputClick("com.tencent.mm:id/fz");//这条语句是聊天页面返回键的id,放不放开都没用。
            } else if (className  //拆红包页,点击“开”,抢红包
                    .equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI")) {
                inputClick("com.tencent.mm:id/bg7");
            } else if (className //红包详情页,点击返回键
                    .equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI")) {
                inputClick("com.tencent.mm:id/gd");
            }
            break;
        }
    }
    /**
     * 根据id,获取AccessibilityNodeInfo,并点击。
     */
    private void inputClick(String clickId) {
        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
        if (nodeInfo != null) {
            List<AccessibilityNodeInfo> list = nodeInfo
                    .findAccessibilityNodeInfosByViewId(clickId);
            for (AccessibilityNodeInfo item : list) {
                item.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
        }
    }
/**
 * 这个方法有缺陷。要是能加 过滤已拆红包就好了。我实在想不出好的办法所以注释掉了。
 * 如果大家有好的过滤方案,请告诉我一声,不胜感激!这是我的一个遗憾
 * 因为这一行注释掉,就不是我们想要的全自动的抢红包了。
 */
    private void getLastPacket() {
        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
        recycle(nodeInfo);
        if (parents.size() > 0) {
                parents.get(parents.size() - 1).performAction(
                        AccessibilityNodeInfo.ACTION_CLICK);
        }

    }

    @Override
    public void onInterrupt() {

    }

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        Toast.makeText(RobService.this, "成功与微信绑定,开始监听", Toast.LENGTH_SHORT)
                .show();
    }

    ArrayList<AccessibilityNodeInfo> parents = new ArrayList<AccessibilityNodeInfo>();
/**
 * 这个方法是用递归的方式,遍历节点树。
 * 如果找到“领取红包”和“查看红包”所在叶子节点,就用while不断的找自己父节点,这个父节点要求可以被点击。(也是是说找最近一个可以点击的父节点)
 */
    private void recycle(AccessibilityNodeInfo info) {
        if (info.getChildCount() == 0) {
            if (info.getText() != null) {
                if ("领取红包".equals(info.getText().toString())
                        ||"查看红包".equals(info.getText().toString()
                                )) {
//                  if (info.isClickable()) {
//                      info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
//                  }
                    AccessibilityNodeInfo parent = info.getParent();
                    while (parent != null) {
                        if (parent.isClickable()) {
                            parents.add(parent);//找到了添加到列表并推出循环,否则继续往上找父节点。
                            break;
                        }
                        parent = parent.getParent();
                    }
                }
            }
        } else {
            for (int i = 0; i < info.getChildCount(); i++) {
                if (info.getChild(i) != null) {
                    recycle(info.getChild(i));
                }
            }
        }
    }
    @Override
    public boolean onUnbind(Intent intent) {
        Toast.makeText(this, "断开与微信绑定,停止监听", Toast.LENGTH_SHORT).show();
        return super.onUnbind(intent);
    }
}

这里我们对一些语句做一些解释:

1.getRootInActiveWindow(); //获取当前窗口的根节点。
2.findAccessibilityNodeInfosByViewId//根据id获取节点,获取的是一个列表
3.AccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);//模拟点击事件
4.AccessibilityNodeInfo.getParent()获取父节点

2.MainActivity类的编写

public class MainActivity extends Activity implements OnClickListener{
    private Button settingBtn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findView();
    }
    private void findView(){
        settingBtn = (Button) findViewById(R.id.settingBtn);
        settingBtn.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch(v.getId()){
            case R.id.settingBtn://跳转到辅助设置页
                Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
                startActivity(intent);
            break;
        }
    }
}

3.看一下menifest中的注册

     <service
            android:name="com.czh.service.RobService"
            android:enabled="true"
            android:exported="true"
            android:label="@string/servicelable"
            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>

这里对menifest讲解一下:

android:label="@string/servicelable"是服务描述

效果如下:
这里写图片描述

<service
        android:name="com.czh.service.RobService"
        android:enabled="true"
        android:exported="true"
        android:label="@string/servicelable"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
        <intent-filter>
            <action android:name="android.accessibilityservice.AccessibilityService" />
        </intent-filter>
    </service>

这一块是固定的写法,没什么好说的。注意权限是必须要加上!

<meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility" />

meta-data是配置文件,配置信息在accessibility.xml中

3.看一下accessibility.xml文件
在res文件夹下面新建一个文件夹xml –> 在xml文件夹中新建一个accessibility.xml文件。

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    android:notificationTimeout="100"
    android:description="@string/description"
    android:packageNames="com.tencent.mm" />

对这个accessibility.xml讲解一下:

1.android:accessibilityEventTypes 表示监听的事件类型,这本章中我们监听通知,屏幕状态变化,屏幕内容变化。typeAllMask表示监听所有类型
2.android:accessibilityFeedbackType 表示手机用什么方式将信息反馈给用户,语音震动等等,这里默认;
3.android:canRetrieveWindowContent="true",允许检索窗口内容,这一条必须要加上。
4.android:description 描述
5.android:packageNames="com.tencent.mm"这里我们监听微信的包名,如果你想监听多个App的话可以这样写:
android:packageNames="com.tencent.mm|com.tencent.qq"    

在配置文件中android:description添加后的效果图是这样的:

这里写图片描述

3.看一下strings.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">微信抢红包</string>
    <string name="servicelable">微信自动抢红包</string>
    <string name="description">本插件仅供娱乐,切勿用于不当用途,出现问题后果自负!</string>
</resources>    

3.补充

大家可能在想,findAccessibilityNodeInfosByViewId()方法中的ID是如何知道的呢?下面我们就来解释一下:
在devices中有这么一个按钮,点击进入,效果图如下:
这里写图片描述
进入之后你会看到下面界面:
这里写图片描述
通过图你可以看出,左面是界面,又面是布局和控件的详细信息(我们可以通过这个来学习人家的布局规律),当你点击某个控件的时候,右面就会显示该控件的信息,图中我们标记出来了。

4.不足之处

我在写这个东西上时候有一个bug,一直不知道如何解决:新版微信无法区分未拆红包和已拆红包。就是因为这个原因程序会把已拆红包当成未拆红包来拆,导致死循环,如果有知道解决方案的朋友请告知我,不胜感激!

5.结尾

在文章的结尾奉送上代码,方便大家对照着学习。

在技术上我依旧是个小渣渣,加油勉励自己。

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序编织梦想

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值