前言
来啦老铁!
咱们在前一阵子的文章 老铁,原来做个Android App这么简单~ 中学习了如何动手做 Android App,当时只是很简单的涉及了一下,今天咱们来稍微应用一下:
-
微信抢红包助手实践;
我个人挺好奇抢红包助手是怎么实现的,几年前有听过一个朋友说这个东西挺简单的,我倒想知道,到底有多简单,顺便为自己做个抢红包助手,他不香嘛~
本文章代码已上传 LuckyDog GitHub 仓库,欢迎取阅。
整体步骤
- 下载安装Android Studio;
- 新建Android项目;
- 创建 service 包;
- 创建 LuckyService 类;
- 在 LuckyService 类中完成抢红包逻辑;
- 编写无障碍配置;
- AndroidManifest.xml 中注册 LuckyService 类和无障碍配置;
- 创建配置信息文件;
- 安装抢红包助手;
- 设置抢红包助手;
- 自动抢红包;
1. 下载安装Android Studio;
参考前一阵子的文章 老铁,原来做个Android App这么简单~
新建Android项目2. 新建Android项目;
项目结构3. 创建 service 包;
4. 创建 LuckyService 类;
package com.dylanz.luckydog.service;
import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;
public class LuckyService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
@Override
public void onInterrupt() {
}
}
5. 在 LuckyService 类中完成抢红包逻辑;
package com.dylanz.luckydog.service;
import android.accessibilityservice.AccessibilityService;
import android.app.Notification;
import android.app.PendingIntent;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.annotation.TargetApi;
import android.os.Build;
import androidx.annotation.RequiresApi;
import java.util.List;
public class LuckyService extends AccessibilityService {
/**
* 日志的 tag,随意
*/
public static final String TAG = "LuckyService";
/**
* 红包是否打开的状态记录变量
*/
private boolean isRedPacketOpened = false;
/**
* 红包消息辨别关键字
*/
private static final String HONG_BAO_TXT = "[微信红包]";
/**
* 有"开"的那个小弹窗的 className
*/
private static final String ACTIVITY_DIALOG_LUCKY_MONEY = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI";
/**
* 红包领取后的详情页面的 className
*/
private static final String LUCKY_MONEY_DETAIL = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI";
/**
* 收到的红包,整个控件的 id
*/
private static final String RED_PACKET_ID = "com.tencent.mm:id/tv";
/**
* 已领过的红包有个"已领取"字眼,这个字眼对应的控件 id
*/
private static final String OPENED_ID = "com.tencent.mm:id/tt";
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
//获取当前界面包名
CharSequence packageNameChar = event.getPackageName();
Log.e(TAG, "packageNameChar:" + packageNameChar);
if (packageNameChar == null || !packageNameChar.toString().equals("com.tencent.mm")) {
return;
}
isRedPacketOpened = false;
//获取当前类名
String className = event.getClassName().toString();
//红包领取后的详情页面,自动返回
if (className.equals(LUCKY_MONEY_DETAIL)) {
Log.e(TAG, "红包已领取,返回聊天页面:");
performGlobalAction(GLOBAL_ACTION_BACK);
return;
}
//当前为红包弹出窗(有"开"的那个小弹窗)
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && className.equals(ACTIVITY_DIALOG_LUCKY_MONEY)) {
//有可能会由于网络原因,"开"的那个小弹框会需要加载后才显示,我们此处最多等 5 秒钟
for (int i = 0; i < 1000; i++) {
if (isRedPacketOpened) {
break;
}
AccessibilityNodeInfo rootNode = getRootInActiveWindow();
openRedPacket(rootNode);
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return;
}
//遍历消息列表的每个消息,点击红包控件
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
jumpIntoRedPacket(nodeInfo);
//是否微信聊天页面的类
if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {
Log.e(TAG, "接收到通知栏消息");
Notification notification = (Notification) event.getParcelableData();
//获取通知消息详情
if (notification.tickerText == null) {
return;
}
String content = notification.tickerText.toString();
//解析消息
String[] msg = content.split(":");
if (msg.length == 0) {
return;
}
String text = msg[1].trim();
if (text.contains(HONG_BAO_TXT)) {
Log.e(TAG, "接收到通知栏红包消息,点击消息,进入聊天界面");
//打开通知栏的intent,即打开对应的聊天界面
PendingIntent pendingIntent = notification.contentIntent;
try {
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
}
}
}
/**
* 点击"开"按钮
*
* @param rootNode rootNode
*/
private void openRedPacket(AccessibilityNodeInfo rootNode) {
if (rootNode == null) {
return;
}
for (int i = 0; i < rootNode.getChildCount(); i++) {
AccessibilityNodeInfo node = rootNode.getChild(i);
if ("android.widget.Button".equals(node.getClassName().toString())) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
isRedPacketOpened = true;
break;
}
openRedPacket(node);
}
}
@Override
public void onInterrupt() {
}
/**
* 点击"开"按钮
*
* @param rootNode rootNode
*/
private void jumpIntoRedPacket(AccessibilityNodeInfo rootNode) {
if (rootNode == null) {
return;
}
for (int i = 0; i < rootNode.getChildCount(); i++) {
//获取到子控件
AccessibilityNodeInfo node = rootNode.getChild(i);
//获取红包控件
AccessibilityNodeInfo target = findViewByID(node, RED_PACKET_ID);
if (target != null) {
//已领取这个控件为空,红包还没有被领取
if (findViewByID(node, OPENED_ID) == null) {
Log.e(TAG, "找到未领取的红包,点击红包");
performViewClick(target);
break;
}
}
jumpIntoRedPacket(node);
}
}
/**
* 模拟点击事件
*
* @param nodeInfo nodeInfo
*/
public void performViewClick(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null) {
return;
}
while (nodeInfo != null) {
Log.e(TAG, "打开红包1");
if (nodeInfo.isClickable()) {
Log.e(TAG, "打开红包2");
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;
}
nodeInfo = nodeInfo.getParent();
}
}
/**
* 查找对应ID的View
*
* @param accessibilityNodeInfo AccessibilityNodeInfo
* @param id id
* @return View
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public AccessibilityNodeInfo findViewByID(AccessibilityNodeInfo accessibilityNodeInfo, String id) {
if (accessibilityNodeInfo == null) {
return null;
}
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null) {
return nodeInfo;
}
}
}
return null;
}
}
6. 编写无障碍配置;
res 下新建 xml 文件夹(名字任意),创建一个 xml 文件,如:accessible_service_wx_config.xml,写入:
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canRetrieveWindowContent="true"
android:description="@string/app_description"
android:notificationTimeout="10"/>
参数解读:后续有机会补上;
7. AndroidManifest.xml 中注册 LuckyService 类和无障碍配置;
修改前:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dylanz.luckydog">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.LuckyDog">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
修改后:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dylanz.luckydog">
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.LuckyDog">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".service.LuckyService"
android:label="@string/app_description"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessible_service_wx_config" />
</service>
</application>
</manifest>
注:android:exported 代表是否支持其它应用调用当前组件,默认值:若包含有intent-filter 默认值为true,若没有intent-filter 则默认值为false,我们此处应设置为true;
8. 创建配置信息文件;
res/values 下创建 strings.xml 文件,内容如下:
<resources>
<string name="app_name">LuckyDog微信红包助手 - 基础版</string>
<string name="app_description">LuckyDog抢红包助手</string>
</resources>
注:这里头的配置信息,就是 accessible_service_wx_config.xml 文件和 AndroidManifest.xml 文件中会用到的 @string/app_name 和 @string/app_description,此步骤根据 Android Studio 提示完成;
9. 安装抢红包助手;
- 在 Android Studio 中编译后,手机弹出安装提示后,安装助手;
咱们也可以找到编译出来的 apk 文件,后续就不用再在 Android Studio 里头编译安装了,直接用 apk 文件安装就好啦!
apk 文件apk 文件安装法也是将红包助手与其他人分享的手段。
- 安装过程:
这个页面不要怕,咱们自己开发的软件,没有发布,都会这样,当然咱们没有做什么见不得人的功能;
安装完成10. 设置抢红包助手;
点击抢红包助手页面上的“红包助手 - 无障碍设置”,打开设置页面后,选择“LuckyDog抢红包助手”,打开“使用LuckyDog抢红包助手”即可;
11. 自动抢红包;
以上设置好了之后,返回检查助手是否是启动状态,是的话就开始了自动抢红包辅助了!不难吧~
助手启动状态目前支持的自动抢红包场景:
- 在聊天页面自动抢红包;
- 未开启消息免打扰时,微信后台运行;
- 未开启消息免打扰时,退出微信;
- 开启消息免打扰,微信界面后台运行,人工进入聊天页面;
- 聊天界面有多个红包,自动全部抢;
咱们出发点就想做个简单的抢红包助手,没有考虑特别多的其他交互,如发感谢信、红包统计等功能,如果您有兴趣,可以自行研究,原理与本文章介绍的类似。
感谢其他简书作者的参考文献,包括并不限于:Android微信抢红包辅助
好了,熬了好几个晚上的助手终于可以告一段落了,敬默默努力的每一位朋友~
如果本文对您有帮助,麻烦动动手指点点赞?
非常感谢!
网友评论