Android用AccessibilityService 辅助服务实现微信抢红包APP
Accessibility Service 可以替代应用与用户交流反馈。
抢红包APP的主要思路:当通知栏出现包含“[微信红包]”关键字的微信消息,就自动跳转到该消息的聊天界面,然后找到微信红包对应的View并模拟点击打开红包和拆红包。
下面以抢红包APP为例,详解其使用方法:
一、创建Accessibility Service
创建一个继承于AccessibilityService的类,并在manifest文件中声明这个Service。标明它监听处理android.accessibilityservice.AccessibilityService事件,声明android.permission.BIND_ACCESSIBILITY_SERVIC权限。由于这是系统级服务,安装后还需要用户在“设置”—->“辅助功能”中给该应用授权。
<service
android:label="@string/app_name"
android:name=".QiangHongBaoService"
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>
二、配置Accessibility Service
方法一:Java代码中配置
重写onServiceConnected()方法,并在这里进行Service的配置。
@Override
public void onServiceConnected() {
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
// 需要响应的事件类型
// 此处为通知栏变化事件、界面变化事件
info.eventTypes = AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED |
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
// 如不指定包名,则此服务对所有应用有效
// 此处指定微信包名
info.packageNames = new String[]{"com.tencent.mm"};
// 设置使用的反馈类型
// 此处设为通用类型
info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
// 设置响应时间
info.notificationTimeout = 100;
// 应用参数
this.setServiceInfo(info);
}方法二:XML文件中配置
从Android4.0开始可以res/xml/目录下添加配置文件,并在manifest文件中通过< meta-data >标签指定。一些特性的选项比如canRetrieveWindowContent仅仅可以在XML可以配置。
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_description"
android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged"
android:packageNames="com.tencent.mm"
android:accessibilityFeedbackType="feedbackGeneric"
android:notificationTimeout="100"
android:accessibilityFlags=""
android:canRetrieveWindowContent="true"/>android:description 设置服务的描述,在用户授权的界面可以看到。
android:accessibilityEventTypes 配置要监听的辅助事件。上面只用到typeNotificationStateChanged(通知变化事件)、typeWindowStateChanged(界面变化事件)
android:packageNames 要监听应用的包名,监听多个应用用英文逗号分隔,这里只需要监听微信。
android:accessibilityFeedbackType 设置反馈方式。
| FeedbackType | 描述 |
|---|---|
| feedbackSpoken | 语音反馈 |
| feedbackHaptic | 触感反馈 |
| feedbackAudible | 表示声音(不是语音)反馈 |
| feedbackVisual | 视觉反馈 |
| feedbackGeneric | 通用反馈 |
| feedbackAllMask | 所有以上的反馈 |
android:canRetrieveWindowContent 是否能遍历View层级。可以从产生Accessibility 事件的组件与它的父子组件中提取必要的信息。
三、响应Accessibility Event
/**
* 必须重载的方法
* 接收系统发来的AccessbilityEvent,已经按照配置文件过滤
* 可使用getEventType()来确定事件的类型
* 可使用getContentDescription()来提取产生事件的View的相关的文本标签。
*/
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
//接收事件,如触发了通知栏变化、界面变化等
}
/**
* 必须重载的方法
* 系统想要中断AccessibilityService返给的响应时会调用
* 生命周期中会调用多次
*/
@Override
public void onInterrupt() {
//服务中断,如授权关闭或者将服务杀死
}
/**
* 可选的方法
* 系统会在成功连接上服务时候调用这个方法
* 在这个方法里你可以做一下初始化工作
* 例如设备的声音震动管理,也可以调用setServiceInfo()进行配置工作。
*/
@Override
protected void onServiceConnected() {
super.onServiceConnected();
//连接服务后,一般是在授权成功后会接收到
}
@Override
protected boolean onKeyEvent(KeyEvent event) {
//接收按键事件
return super.onKeyEvent(event);
}
下面是抢微信红包的onAccessibilityEvent方法:
/**
* 必须重载的方法
* 接收系统发来的AccessbilityEvent,已经按照配置文件过滤
*/
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
final int eventType = event.getEventType();
// 通知栏事件
if (eventType == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
// 通知栏出现新信息
// 获取通知栏信息内容
List<CharSequence> texts = event.getText();
// 检查是否有红包信息
if (!texts.isEmpty()) {
for (CharSequence t : texts) {
String text = String.valueOf(t);
if (text.contains(HONGBAO_TEXT_KEY)) {
openNotify(event); // 点击通知
break;
}
}
}
} else if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
// 窗口改变,如果是聊天界面,则调动打开红包
openHongBao(event);
}
}
/**
* 打开通知栏消息
*/
private void openNotify(AccessibilityEvent event){
if (event.getParcelableData() == null || !(event.getParcelableData() instanceof Notification)) {
return;
}
// 将微信的通知栏消息打开
// 获取Notification对象
Notification notification = (Notification) event.getParcelableData();
// 调用其中的PendingIntent,打开微信界面
PendingIntent pendingIntent = notification.contentIntent;
try {
pendingIntent.send();
} catch (CanceledException e) {
e.printStackTrace();
}
}当跳转到微信聊天界面时,系统又会发送一个窗口变化的事件,即AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED。抢红包这个过程共包含三个不同的界面:一是聊天界面,点击抢红包的View;二是拆红包界面;三是拆完红包后,查看红包金额的界面。不同的界面,对应的代码不一样。我们可以根据事件的类名来判断当前处于哪个界面。代码如下:
/**
* 打开微信后,判断是什么界面,并做相应的动作
*/
private void openHongBao(AccessibilityEvent event) {
if ("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI".equals(event.getClassName())) {
// 拆红包界面
getPacket();
} else if ("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI".equals(event.getClassName())) {
// 拆完红包后,看红包金额的界面
// 这里什么都不做
} else if ("com.tencent.mm.ui.LauncherUI".equals(event.getClassName())) {
// 聊天界面
openPacket();
}
}AccessibilityService中的getRootInActiveWindow方法,可以获得当前活动窗口。然后通过findAccessibilityNodeInfosByText方法,可以找到该窗口下包含特定字符串“领取红包“的AccessibilityNodeInfo对象,最后通过AccessibilityNodeInfo下的performAction方法模拟点击来领取红包
/**
* 在聊天界面中点红包
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void openPacket() {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo == null) {
return;
}
// 找到领取红包的点击事件
List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("领取红包");
// 最新的红包领起
for (int i = list.size() - 1 ; i >= 0; i--) {
// 通过调试可知[领取红包]是text,本身不可被点击,用getParent()获取可被点击的对象
AccessibilityNodeInfo parent = list.get(i).getParent();
// 谷歌重写了toString()方法,不能用它获取ClassName@hashCode串
if ( parent != null ) {
parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
break; // 只领最新的一个红包
}
}
}
/**
* 拆红包
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void getPacket() {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo == null) {
return;
}
List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("拆红包");
for (AccessibilityNodeInfo n :list) {
n.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}由于这是系统级服务,需要用户在设置中手动开户,所以在MainActivity中添加如下代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnStart = (Button) findViewById(R.id.start_button);
btnStart.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
open();
}
});
}
private void open(){
try{
Intent intent = new Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
Toast.makeText(this, "找到伸手党抢红包,然后开启服务即可", Toast.LENGTH_LONG).show();
} catch (Exception e){
e.printStackTrace();
}
}
缺点:
1.依赖微信的通知。如果微信群设置了消息免打扰,那么这个群的一切消息,都不会出现在通知栏上,这样抢红包外挂就无法正常工作了。只有手动打开该微信群,才会抢到最新的一个红包。但是注意,这里仅会抢到最新的那个。
2.如果手机待机了,那么抢红包外挂也无法正常工作。
3.如果现在正打开着微信聊天界面,这时聊天对象发红包了,抢红包外挂也不会工作。因为外挂监听的是通知栏变化事件和窗口变化事件。这时由于与发红包的对象正聊天,所以微信不会在通知栏上给提示,系统也不会发送什么事件。当然,如果不是当前聊天的对象,那么还是可以正常抢到红包的。
四、从View层级中提取更多信息
Android 4.0版本中增加了一个新特性,就是能够用AccessibilityService来遍历View层级,并从产生Accessibility 事件的组件与它的父子组件中提取必要的信息。为了实现这个目的,你需要在XML文件中进行如下的配置:
android:canRetrieveWindowContent="true"一旦完成,使用getSource()获取一个AccessibilityNodeInfo对象,如果触发事件的窗口是活动窗口,该调用只返回一个对象,如果不是,它将返回null,做出相应的反响。
下面的示例是一个代码片段,与抢红包APP无关,当它接收到一个事件时,执行以下步骤:
1.立即获取到产生这个事件的Parent
2.在这个Parent中寻找文本标签或勾选框
3.如果找到,创建一个文本内容来反馈给用户,提示内容和是否已勾选。
4.如果当遍历View的时候某处返回了null值,那么就直接结束这个方法。
// Alternative onAccessibilityEvent, that uses AccessibilityNodeInfo
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityNodeInfo source = event.getSource();
if (source == null) {
return;
}
// Grab the parent of the view that fired the event.
AccessibilityNodeInfo rowNode = getListItemNodeInfo(source);
if (rowNode == null) {
return;
}
// Using this parent, get references to both child nodes, the label and the checkbox.
AccessibilityNodeInfo labelNode = rowNode.getChild(0);
if (labelNode == null) {
rowNode.recycle();
return;
}
AccessibilityNodeInfo completeNode = rowNode.getChild(1);
if (completeNode == null) {
rowNode.recycle();
return;
}
// Determine what the task is and whether or not it's complete, based on
// the text inside the label, and the state of the check-box.
if (rowNode.getChildCount() < 2 || !rowNode.getChild(1).isCheckable()) {
rowNode.recycle();
return;
}
CharSequence taskLabel = labelNode.getText();
final boolean isComplete = completeNode.isChecked();
String completeStr = null;
if (isComplete) {
completeStr = getString(R.string.checked);
} else {
completeStr = getString(R.string.not_checked);
}
String reportStr = taskLabel + completeStr;
speakToUser(reportStr);
}版权声明:本文为博主原创文章,未经博主允许不得转载。

浙公网安备 33010602011771号