一、调试环境搭建:
基础调试环境可以用手机真机,shell权限即可或者android 模拟器,推荐无广告,并且android高版本支持的genymotion (模拟器有root权限),可以在官网自行下载注册用于学习:https://www.genymotion.com/,由于是X86架构,需要自己下载X86兼容arm的依赖包Genymotion-ARM-Translation:https://github.com/m9rco/Genymotion_ARM_Translation,
然后下载一个android 8.0的版本启动模拟器就可以开始基础调试了;我们不用hook的方法,在高版本Android机型可以用Android AccessibilityService(无障碍服务)来实现微信自动抢红包;夜神模拟器等商用模拟器android版本更新太慢,很多机型适配可能存在不少问题,开发实用性不够;
二、具体代码实现可以参考:https://github.com/xiaxiaxa/RedWechat (近期整理并且debug的一个基础版本)
1、在AndroidManifest.xml中注册服务MyAccessibilityService,具体可以参考官方文档
像其他Service服务一样,需要在AndroidManifest.xml中声明该服务,还必须配置以下两项:
a:配置,其name为固定的:android.accessibilityservice.AccessibilityService;
b:声明BIND_ACCESSIBILITY_SERVICE权限,以便系统能够绑定该服务(4.0版本后要求);
注意:任何一点配置错误,系统都无反应,因此其固定配置如下
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:canPerformGestures="true"
android:theme="@style/AppTheme">
<activity android:name="com.example.redwechat.QHBActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.example.redwechat.MyAccessibilityService"
android:enabled="true"
android:exported="true"
android:label="@string/label"
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_config" />
</service>
</application>
AccessbilityService的生命周期由系统管理,遵循Service的基本生命周期,只能由用户自己在设置中手动启动,系统绑定到服务后,会调用它的onServiceConnected()方法。当用户手动在设置中关闭服务,或者开发者调用disableSelf()方法时,该服务会被关闭销毁。
2、具体自动抢红包的实现基本上在自定义服务MyAccessibilityService里面实现MyAccessibilityService继承AccessbilityService;我们可以看看AccessibilityService类中具体有哪些方法:
方法 | 作用 |
---|---|
disableSelf() | 禁用当前服务,也就是在服务可以通过该方法停止运行 |
findFoucs(int falg) | 查找拥有特定焦点类型的控件 |
getRootInActiveWindow() | 如果配置能够获取窗口内容,则会返回当前活动窗口的根结点 |
getSeviceInfo() | 获取当前服务的配置信息 |
onAccessibilityEvent(AccessibilityEvent event) | 有关AccessibilityEvent事件的回调函数.系统通过sendAccessibiliyEvent()不断的发送AccessibilityEvent到此处 |
performGlobalAction(int action) | 执行全局操作,比如返回,回到主页,打开最近等操作 |
setServiceInfo(AccessibilityServiceInfo info) | 设置当前服务的配置信息 |
getSystemService(String name) | 获取系统服务 |
onKeyEvent(KeyEvent event) | 如果允许服务监听按键操作,该方法是按键事件的回调,需要注意,这个过程发生了系统处理按键事件之前 |
onServiceConnected() | 系统成功绑定该服务时被触发,也就是当你在设置中开启相应的服务,系统成功的绑定了该服务时会触发,通常我们可以在这里做一些初始化操作 |
AccessibilityEvent中,几种常用的:
类型 | 作用 | 注释 |
---|---|---|
TYPE_WINDOW_STATE_CHANGED | 页面状态发生变化 | 对于activity页面onResume就会调用一次, 弹出dialog,popwindow,menu,也都会监听 |
TYPE_WINDOW_STATE_CHANGED | 页面有内容发生改变 | 比如添加或者删除一个view,checkbox选中变成非选中,一个textview的内容变化了等等 |
TYPE_VIEW_CLICKEDD | 当View被点击时发送此事件 | |
TYPE_VIEW_LONG_CLICKED | 当View被长按时发送此事件 | |
TYPE_VIEW_FOCUSED | 当View获取到焦点时发送此事件 | |
TYPE_NOTIFICATION_STATE_CHANGED | 判断辅助服务触发的事件是否是通知栏改变事件 |
基础功能code主要实现在TYPE_WINDOW_CONTENT_CHANGED事件跟TYPE_WINDOW_STATE_CHANGED事件
打开微信聊天收到红包或者在聊天页面收到信息更新收到红包的情况;
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
String className = event.getClassName().toString();
AccessibilityNodeInfo rootNode = getRootInActiveWindow();
if (WECHAT_UI.equals(className) || WECHAT_UI_2.equals(className)) {
findRedPacket(rootNode);
}
if (WECHAT_RED.equals(className)) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
openRedPacket();
isOpenDetail = true;
}
}, DELAY_TIME);
}
if (isOpenDetail && WECHAT_RED_DETAIL.equals(className)) {
isOpenDetail = false;
release();
}
break;
}
这里就要讲到微信的几个对应的布局界面:
布局界面 | 名称 |
---|---|
微信聊天界面 | com.tencent.mm.ui.LauncherUI |
弹出拆红包的界面 | com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI |
拆开红包后的详情页 | com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI |
对应的红包拆字按钮 | android.widget.Button |
3、详细实现:
在聊天界面有信息内容更新或者聊天界面状态发生变化的时候,我们需要遍历查找红包;如何查询呢?从当前页面布局的最后一行开始,判断是否已经打开过那个最新的红包了,是的话就跳出for循环,不是的话继续遍历;
/**
* 遍历查找红包
*/
private void findRedPacket(AccessibilityNodeInfo accessibilityNodeInfo) {
if (accessibilityNodeInfo != null) {
for (int i = accessibilityNodeInfo.getChildCount() - 1; i >= 0; i--) {
AccessibilityNodeInfo accessibilityNodeInfoChild = accessibilityNodeInfo.getChild(i);
if (accessibilityNodeInfoChild == null) {
continue;
}
CharSequence text = accessibilityNodeInfoChild.getText();
if (text != null && text.toString().equals(WECHAT_RED_NAME)) {
AccessibilityNodeInfo parent = accessibilityNodeInfoChild.getParent();
while (parent != null) {
if (parent.isClickable()) {
if (!parent.getChild(1).getText().equals(IS_GET_WECHAT_RED_NAME) &&
!parent.getChild(1).getText().equals(IS_ALL_GET_WECHAT_RED_NAME)) {
parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
isOpenRP = true;
}
break;
}
parent = parent.getParent();
}
}
findRedPacket(accessibilityNodeInfoChild);
if (isOpenRP) {
break;
} else {
findRedPacket(accessibilityNodeInfoChild);
}
}
}
}
** 找到红包后,拆开红包:**
/**
* 开始打开红包
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
private void openRedPacket() {
AccessibilityNodeInfo rootNode = getRootInActiveWindow();
for (int i = 0; i < rootNode.getChildCount(); i++) {
if (rootNode.getChildCount() == 0) {
continue;
}
AccessibilityNodeInfo accessibilityNodeInfo = rootNode.getChild(i);
accessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
if (WECHAT_RED_BUTTON.equals(accessibilityNodeInfo.getClassName())) {
accessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;
} else {
openRedPacket(accessibilityNodeInfo);
}
}
}
private void openRedPacket(AccessibilityNodeInfo rootNode) {
for (int i = 0; i < rootNode.getChildCount(); i++) {
if (rootNode.getChildCount() == 0) {
continue;
}
AccessibilityNodeInfo accessibilityNodeInfo = rootNode.getChild(i);
accessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
if (WECHAT_RED_BUTTON.equals(accessibilityNodeInfo.getClassName())) {
accessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
之前模拟过几种方法或多或少都有些其他问题,比如模拟按键屏幕位置(时间发现android 10.0小米手机未生效,模拟单点失败),cmd命令模拟adb shell put时间也失败了,然后现在拆字是个图片,没有找到资源ID的情况下也无法精准的点击,所以通过上述方法做了一个规避,从而实现了红包的点击;部分关键字的时候筛选过滤掉了相应的信息,实现了自动点击红包,拆开红包后需要对已经点击过的红包进行处理,监听TYPE_WINDOW_STATE_CHANGED事件:
private final static String WECHAT_RED_NAME = "微信红包";
private final static String WECHAT = "微信";
private final static String IS_GET_WECHAT_RED_NAME = "已领取";
private final static String IS_ALL_GET_WECHAT_RED_NAME = "已被领完";
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
String className = event.getClassName().toString();
AccessibilityNodeInfo rootNode = getRootInActiveWindow();
if (WECHAT_UI.equals(className) || WECHAT_UI_2.equals(className)) {
findRedPacket(rootNode);
}
if (WECHAT_RED.equals(className)) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
openRedPacket();
isOpenDetail = true;
}
}, DELAY_TIME);
}
if (isOpenDetail && WECHAT_RED_DETAIL.equals(className)) {
isOpenDetail = false;
release();
}
break;
}
这里可以进行锁屏、释放资源、终止服务等操作,由于事件问题,基础code里面并没有新增灭屏,唤醒,以及后台自动抢红包功能,后面会继续补充;
由于AccessibilityService服务很容易断开,所以我们需要将我们的App设置为白名单,另外需要重写MyAccessibilityService的终端监听等,上面基础code仅仅能支持微信聊天页面自动抢红包,拆红包,稳定性优化后续补充;
apk安装后记得在手机中打开无障碍服务:
apk下载:
https://download.csdn.net/download/xiaweilihai/14927105