上一章《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.结尾
在文章的结尾奉送上代码,方便大家对照着学习。
在技术上我依旧是个小渣渣,加油勉励自己。