概述
转至:http://blog.csdn.net/jwzhangjie/article/details/47205299
转至:http://blog.csdn.net/shineflowers/article/details/47109349
转至:https://segmentfault.com/a/1190000004260128
最近要实现微信自动抢红包的功能,使用AccessibilityService来开发,这里主要写一下逻辑以及注意点。
注意点
1、搜索关键字
我们实现某个功能比如点击等需要找到对应的对象然后模拟点击事件,所以首先就是怎么样找到对象,下面说三种方式:
(1)findAccessibilityNodeInfosByText通过文字来实现查找,返回的是List<AccessibilityNodeInfo>,所以需要通过for循环来具体判断需要的关键字的对象
(2)findAccessibilityNodeInfosByViewId通过控件的id来查询,返回的是List<AccessibilityNodeInfo>,虽然也是返回List但是一般只有一个,查找的准确性高,不过需要系统的版本API>=18
(3)搭配findAccessibilityNodeInfosByText来查找,在微信中使用uiautomatorviewer查看布局,发现不同的手机相同的控件id是不一样的,比如我们需要查询获取红包的数量时,需要先查找'元',然后获取其父控件,然后查找金额所在的位置,这个是不变的。
2、对于返回功能
一般我们领取红包后进入红包详情界面,这时我们要返回到聊天界面使用uiautomatorviewer查看返回箭头,查看其属性他的clickable=false这样的话我们就无法通过
accessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);来实现点击事件来实现返回的功能,不过查看AccessibilityService源码里面有对应的全局事件,下面说两种实现返回功能的方法
(1)查找界面上对应的返回按钮然后通过AccessibilityNodeInfo的performAction(AccessibilityNodeInfo.ACTION_CLICK)实现点击,不过在操作之前先判断一下isCheckable()如果是false则无法实现其功能
(2)使用AccessibilityService的performGlobalAction的方法,介绍如下:
- /**
- * Performs a global action. Such an action can be performed
- * at any moment regardless of the current application or user
- * location in that application. For example going back, going
- * home, opening recents, etc.
- *
- * @param action The action to perform.
- * @return Whether the action was successfully performed.
- *
- * @see #GLOBAL_ACTION_BACK
- * @see #GLOBAL_ACTION_HOME
- * @see #GLOBAL_ACTION_NOTIFICATIONS
- * @see #GLOBAL_ACTION_RECENTS
- */
- public final boolean performGlobalAction(int action) {
- IAccessibilityServiceConnection connection =
- AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
- if (connection != null) {
- try {
- return connection.performGlobalAction(action);
- } catch (RemoteException re) {
- Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
- }
- }
- return false;
- }
所以要实现返回功能只需要调用performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);,当然如果想实现Home,通知,最近的应用换成对应的action就可以了
3、涉及微信界面的类
- /**
- * 微信的包名
- */
- static final String WECHAT_PACKAGENAME = "com.tencent.mm";
- /**
- * 拆红包类
- */
- static final String WECHAT_RECEIVER_CALSS = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI";
- /**
- * 红包详情类
- */
- static final String WECHAT_DETAIL = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI";
- /**
- * 微信主界面或者是聊天界面
- */
- static final String WECHAT_LAUNCHER = "com.tencent.mm.ui.LauncherUI";
这里需要注意的是WECHAT_LAUNCHER,微信主界面以及聊天界面应该采用的FragmentActivity+Fragment这样导致如果用户进入到微信主界面则会调用AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,导致再次进入微信聊天界面不会再调用AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,而会调用AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,而AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED只要内容改变后都会调用,所以一般是使用
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED来作为监测事件的,所以解决这个问题的方式就是加入判断条件:
(1)触发AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED这个事件搜索列表界面是否有"领取红包"字样,如果没有则设置一个变量
(2)如果没有触发AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED而触发了AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,则去判断之前设置的变量综合来判断
4、增加红包获取量避免重复金额
在聊天界面的红包虽然会有"领取红包"的字样,但是其实是已经拆过的,判断的标识就是是否有"拆红包",如果有拆红包则计算对应详情中的金额。
5、如何循环查询所有的子控件
- /**
- * @param info 当前节点
- * @param matchFlag 需要匹配的文字
- * @param type 操作的类型
- */
- 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) {
- Log.e(TAG, "index: " + i + " info" + childInfo.getClassName() + " : " + childInfo.getContentDescription()+" : "+info.getText());
- recycle(childInfo, matchFlag, type);
- }
- }
- }
- }
- }
网上关于抢红包的源码比较多,由于其他原因我们这里的不会公布,可以根据网上的源码进行修改,能够实现功能:
(1)截取通知栏中有[微信红包]字样的通知,然后跳到微信红包界面
(2)进入群聊界面会自动查询当前界面所有"领取红包",然后循环点击查找增加红包的概率
(3)准确的保存领取的红包金额和日期
简单实现了微信自动抢红包的服务,原理就是根据关键字找到相应的View, 然后自动点击。主要是用到AccessibilityService这个辅助服务,基本可以满足自动抢红包的功能,但是有些逻辑需要优化,比如,拆完一个红包后,必须手动点击返回键,才能进行下一次自动抢红包。
AndroidManifest.xml
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.jackie.webchatenvelope" >
-
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:theme="@style/AppTheme" >
- <activity
- android:name=".MainActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
-
- <service
- android:enabled="true"
- android:exported="true"
- android:label="@string/app_name"
- android:name=".EnvelopeService"
- 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/envelope_service_config"/>
- </service>
- </application>
-
- </manifest>
envelope_service_config.xml
- <?xml version="1.0" encoding="utf-8"?>
- <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
- android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged"
- android:accessibilityFeedbackType="feedbackGeneric"
- android:accessibilityFlags=""
- android:canRetrieveWindowContent="true"
- android:description="@string/accessibility_description"
- android:notificationTimeout="100"
- android:packageNames="com.tencent.mm" />
activity_main.xml
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- tools:context=".MainActivity">
-
- <Button
- android:id="@+id/start"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="10dp"
- android:layout_centerInParent="true"
- android:textSize="18sp"
- android:text="打开辅助服务"/>
-
- </RelativeLayout>
MainActivity.java
- package com.jackie.webchatenvelope;
-
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- import android.view.Menu;
- import android.view.MenuItem;
- import android.view.View;
- import android.widget.Button;
- import android.widget.Toast;
-
- public class MainActivity extends Activity {
- private Button startBtn;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- startBtn = (Button) findViewById(R.id.start);
- startBtn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- try {
- //打开系统设置中辅助功能
- Intent intent = new Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS);
- startActivity(intent);
- Toast.makeText(MainActivity.this, "找到抢红包,然后开启服务即可", Toast.LENGTH_LONG).show();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- });
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.menu_main, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle action bar item clicks here. The action bar will
- // automatically handle clicks on the Home/Up button, so long
- // as you specify a parent activity in AndroidManifest.xml.
- int id = item.getItemId();
-
- //noinspection SimplifiableIfStatement
- if (id == R.id.action_settings) {
- return true;
- }
-
- return super.onOptionsItemSelected(item);
- }
- }
EnvelopeService.java
- package com.jackie.webchatenvelope;
-
- import android.accessibilityservice.AccessibilityService;
- import android.annotation.TargetApi;
- import android.app.Notification;
- import android.app.PendingIntent;
- import android.os.Build;
- import android.os.Handler;
- import android.util.Log;
- import android.view.accessibility.AccessibilityEvent;
- import android.view.accessibility.AccessibilityManager;
- import android.view.accessibility.AccessibilityNodeInfo;
- import android.widget.Toast;
-
- import java.util.List;
-
- /**
- * <p>Created by Administrator</p>
- * <p/>
- * 抢红包外挂服务
- */
- public class EnvelopeService extends AccessibilityService {
-
- static final String TAG = "Jackie";
-
- /**
- * 微信的包名
- */
- static final String WECHAT_PACKAGENAME = "com.tencent.mm";
- /**
- * 红包消息的关键字
- */
- static final String ENVELOPE_TEXT_KEY = "[微信红包]";
-
- Handler handler = new Handler();
-
- @Override
- public void onAccessibilityEvent(AccessibilityEvent event) {
- final int eventType = event.getEventType();
-
- Log.d(TAG, "事件---->" + event);
-
- //通知栏事件
- 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(ENVELOPE_TEXT_KEY)) {
- openNotification(event);
- break;
- }
- }
- }
- } else if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
- openEnvelope(event);
- }
- }
-
- /*@Override
- protected boolean onKeyEvent(KeyEvent event) {
- //return super.onKeyEvent(event);
- return true;
- }*/
-
- @Override
- public void onInterrupt() {
- Toast.makeText(this, "中断抢红包服务", Toast.LENGTH_SHORT).show();
- }
-
- @Override
- protected void onServiceConnected() {
- super.onServiceConnected();
- Toast.makeText(this, "连接抢红包服务", Toast.LENGTH_SHORT).show();
- }
-
- private void sendNotificationEvent() {
- 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 = ENVELOPE_TEXT_KEY;
- event.getText().add(tickerText);
- manager.sendAccessibilityEvent(event);
- }
-
- /**
- * 打开通知栏消息
- */
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- private void openNotification(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 openEnvelope(AccessibilityEvent event) {
- if ("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI".equals(event.getClassName())) {
- //点中了红包,下一步就是去拆红包
- checkKey1();
- } else if ("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI".equals(event.getClassName())) {
- //拆完红包后看详细的纪录界面
- //nonething
- } else if ("com.tencent.mm.ui.LauncherUI".equals(event.getClassName())) {
- //在聊天界面,去点中红包
- checkKey2();
- }
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- private void checkKey1() {
- AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
- if (nodeInfo == null) {
- Log.w(TAG, "rootWindow为空");
- return;
- }
- List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("拆红包");
- for (AccessibilityNodeInfo n : list) {
- n.performAction(AccessibilityNodeInfo.ACTION_CLICK);
- }
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- private void checkKey2() {
- AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
- if (nodeInfo == null) {
- Log.w(TAG, "rootWindow为空");
- return;
- }
- List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("领取红包");
- if (list.isEmpty()) {
- list = nodeInfo.findAccessibilityNodeInfosByText(ENVELOPE_TEXT_KEY);
- for (AccessibilityNodeInfo n : list) {
- Log.i(TAG, "-->微信红包:" + n);
- n.performAction(AccessibilityNodeInfo.ACTION_CLICK);
- break;
- }
- } else {
- //最新的红包领起
- for (int i = list.size() - 1; i >= 0; i--) {
- AccessibilityNodeInfo parent = list.get(i).getParent();
- Log.i(TAG, "-->领取红包:" + parent);
- if (parent != null) {
- parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
- break;
- }
- }
- }
- }
- }
版本归作者 Leon 所有,所以在此注明出处:http://www.happycodeboy.com/index.php/archives/10/
源码下载地址:https://github.com/lendylongli/qianghongbao
apk下载地址 : 百度云下载 http://pan.baidu.com/s/1qWBZwUK
继微信删好友之后,又被另外一位同学安利了一款抢红包的外挂,俗称过年必备神器= =
预览效果
技术详述
关于 AccessibilityService
先看看官网的介绍 Accessibility`
Many Android users have different abilities that require them to interact with their Android devices in different ways. These include users who have visual, physical or age-related limitations that prevent them from fully seeing or using a touchscreen, and users with hearing loss who may not be able to perceive audible information and alerts...
Android 官网详解 accessibility
上面大概的意思就是 Accessibility 是一个辅助服务,主要是面向一些使用 Android 手机的用户有相关障碍(年龄、视觉、听力、身体等),这个功能可以更容易使用手机,可以帮用户在点击屏幕或者显示方面得到帮助等等。接下来就是查找相关 API,看能做到哪个地步。
Accessibility 相关 API 描述
当然accessibility
除了可以辅助点击界面的事件外,还可以用作自动化测试,或者一键返回,是一个非常强大与实用的功能
关于抢红包的流程
在有以上的一些关于辅助服务的基础知识后,我们就可以分析怎样自动化抢红包。 大家使用过微信都知道,如果不是在微信的可见界面范围(在桌面或者在使用其它应用时),在收到新的消息,就会在通知栏提醒用户。而在微信的消息列表界面,就不会弹出通知栏,所以可以区分这两种情况。然后抓取相关关键字作进一步处理。
1、在非微信消息列表界面,收到通知消息的事件,判断通知栏里的文本是否有[微信红包]的关键字,有则可以判断为用户收到红包的消息(当然,你可以故意发一条包括这个关键字的文本消息去整蛊你的朋友)。然后,我们就自动化触发这个消息的意图事件(Intent
);
2、在通知栏跳进微信界面后,是去到com.tencent.mm.ui.LauncherUI
这个Activity
界面。我们知道,红包的消息上,包括了关键字领取红包或者View
的id
,那我们就根据这个关键字找到相应的View
,然后再触发ACTION_CLICK
(点击事件);
3、在点击红包后,会跳到com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI
这个拆红包的Activity
,当然老方法,找关键字拆红包或id
,然后触发自动化点击事件。
这样就可以完成整个自动化完成抢红包的流程了,所以核心就是找关键字,然后模拟用户点击事件,就这么简单。以下详细说一下代码的实现。
以下是通过DDMS
工具里的Dump View Hierarchy For UI Automator
去分析微信UI
结构。
使用 AccessibilityService去一步步监听微信的动作
1、新建一个继承AccessibilityService
的类,如QiangHongBaoService
,然后在AndroidManifest.xml
里声明组件,如下
<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>
在 meta-data 里声明的是辅助配置,这个是 Android4.0 之后才支持的写法,在 4.0 之前的系统要在代码里声明。
2、在res/xml
目录下生成辅助服务的配置文件qianghongbao_service_config.xml
<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 所有以上的反馈
详细看 AccessibilityServiceInfo 类文档描述
3、在以上都配置好后,我们就可以在QiangHongBaoService
这个服务里进行编码了,要做的就是将整个 UI 跳转流程与逻辑串联起来。
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
//接收事件,如触发了通知栏变化、界面变化等
}
@Override
protected boolean onKeyEvent(KeyEvent event) {
//接收按键事件
return super.onKeyEvent(event);
}
@Override
public void onInterrupt() {
//服务中断,如授权关闭或者将服务杀死
}
@Override
protected void onServiceConnected() {
super.onServiceConnected();
//连接服务后,一般是在授权成功后会接收到
}
具体内容请看源码
其它
如何防止外挂
在了解整个核心后,获取事件不外乎就是通过文本与 id 判断,那么就可以将文本改为图标方式,将 id 改为动态 id(每次显示都是随机生成),这样一来就可以提高外挂的门槛。
如何发红包会安全点
现在抢红包就看谁的外挂工具反应够快,如何去干扰这些外挂,其实也有点小技巧,就是在发红包前,发送文本[微信红包],可以导致部分外挂工具失效。
版本归作者所有,转载请注明出处:http://www.happycodeboy.com/index.php/archives/10/
根据Id查找
nodeInfo.findAccessibilityNodeInfosByViewId("com.longfei.myapplication:id/tvBack");
最后
以上就是忐忑月光为你收集整理的Android AccessibilityService实现微信自动抢红包1、搜索关键字2、对于返回功能3、涉及微信界面的类4、增加红包获取量避免重复金额5、如何循环查询所有的子控件的全部内容,希望文章能够帮你解决Android AccessibilityService实现微信自动抢红包1、搜索关键字2、对于返回功能3、涉及微信界面的类4、增加红包获取量避免重复金额5、如何循环查询所有的子控件所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复