commit
This commit is contained in:
@@ -7,6 +7,7 @@ import android.content.Intent;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
@@ -42,6 +43,12 @@ public class AblService extends AccessibilityService {
|
||||
private Disposable mdDisposable;
|
||||
private String TAG = "commongendan";
|
||||
public String SHEBEITIPE_VILUETHREE = "3";
|
||||
|
||||
// 钉钉打卡相关
|
||||
private static final String DINGDING_PACKAGE = "com.alibaba.android.rimet";
|
||||
private boolean isDingDingDakaStarted = false; // 是否已启动钉钉打卡流程
|
||||
private int dingDingDakaStep = 0; // 打卡步骤:0-未开始,1-等待启动,2-点击首页打卡,3-点击打卡页面打卡
|
||||
private Handler dingDingHandler = new Handler(); // 钉钉打卡专用 Handler
|
||||
|
||||
public static AblService getInstance() {
|
||||
if (mAblService == null) {
|
||||
@@ -147,7 +154,14 @@ public class AblService extends AccessibilityService {
|
||||
//当窗口状态发生改变的时候
|
||||
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
|
||||
String classname = event.getClassName().toString();
|
||||
Log.e("TIAOSHI", "有弹出==22222" + "==classname==" + classname);
|
||||
String packageName = event.getPackageName() != null ? event.getPackageName().toString() : "";
|
||||
Log.e("TIAOSHI", "有弹出==22222" + "==classname==" + classname + ", packageName=" + packageName);
|
||||
|
||||
// 检测钉钉应用窗口,处理打卡流程
|
||||
if (DINGDING_PACKAGE.equals(packageName) && isDingDingDakaStarted) {
|
||||
handleDingDingDakaWindow(classname);
|
||||
}
|
||||
|
||||
List<CharSequence> text = event.getText();
|
||||
if (text.size() > 0) {
|
||||
CharSequence charSequence = text.get(0);
|
||||
@@ -613,4 +627,187 @@ public class AblService extends AccessibilityService {
|
||||
}, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动钉钉打卡流程
|
||||
* 由 MainActivity 调用
|
||||
*/
|
||||
public void startDingDingDaka() {
|
||||
Log.e("TIAOSHI###", "AblService: 启动钉钉打卡流程");
|
||||
isDingDingDakaStarted = true;
|
||||
dingDingDakaStep = 1; // 步骤1:等待应用启动
|
||||
// 延迟2秒后执行步骤2
|
||||
dingDingHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isDingDingDakaStarted && dingDingDakaStep == 1) {
|
||||
Log.e("TIAOSHI###", "AblService: 钉钉打卡-步骤1完成,开始步骤2");
|
||||
dingDingDakaStep = 2;
|
||||
executeDingDingDakaStep2();
|
||||
}
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理钉钉应用窗口状态变化
|
||||
*/
|
||||
private void handleDingDingDakaWindow(String className) {
|
||||
Log.e("TIAOSHI###", "AblService: 检测到钉钉窗口变化,className=" + className + ", step=" + dingDingDakaStep);
|
||||
// 如果正在执行打卡流程,根据步骤执行相应操作
|
||||
if (dingDingDakaStep == 2) {
|
||||
// 步骤2:在首页查找并点击打卡按钮
|
||||
executeDingDingDakaStep2();
|
||||
} else if (dingDingDakaStep == 3) {
|
||||
// 步骤3:在打卡页面点击打卡按钮
|
||||
executeDingDingDakaStep3();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行步骤2:在钉钉首页查找并点击"打卡"按钮
|
||||
*/
|
||||
private void executeDingDingDakaStep2() {
|
||||
Log.e("TIAOSHI###", "AblService: 钉钉打卡-步骤2: 查找并点击打卡按钮");
|
||||
if (clickDakaButton("打卡")) {
|
||||
Log.e("TIAOSHI###", "AblService: 成功点击打卡按钮,等待跳转到打卡页面");
|
||||
dingDingDakaStep = 3;
|
||||
// 延迟2秒后执行步骤3,等待页面跳转完成
|
||||
dingDingHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isDingDingDakaStarted && dingDingDakaStep == 3) {
|
||||
Log.e("TIAOSHI###", "AblService: 钉钉打卡-步骤2完成,开始步骤3");
|
||||
executeDingDingDakaStep3();
|
||||
}
|
||||
}
|
||||
}, 2000);
|
||||
} else {
|
||||
// 尝试其他可能的文本
|
||||
Log.e("TIAOSHI###", "AblService: 未找到'打卡',尝试其他文本");
|
||||
if (clickDakaButton("考勤打卡") || clickDakaButton("工作") || clickDakaButton("工作台")) {
|
||||
Log.e("TIAOSHI###", "AblService: 找到并点击了其他入口");
|
||||
dingDingDakaStep = 3;
|
||||
dingDingHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isDingDingDakaStarted && dingDingDakaStep == 3) {
|
||||
executeDingDingDakaStep3();
|
||||
}
|
||||
}
|
||||
}, 2000);
|
||||
} else {
|
||||
Log.e("TIAOSHI###", "AblService: 未找到打卡入口,结束流程");
|
||||
resetDingDingDaka();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行步骤3:在打卡页面查找并点击"打卡"按钮
|
||||
*/
|
||||
private void executeDingDingDakaStep3() {
|
||||
Log.e("TIAOSHI###", "AblService: 钉钉打卡-步骤3: 在打卡页面点击打卡");
|
||||
// if (clickDakaButton("打卡")) {
|
||||
// Log.e("TIAOSHI###", "AblService: 成功完成打卡操作");
|
||||
// resetDingDingDaka();
|
||||
// } else {
|
||||
// // 尝试其他可能的打卡按钮文本
|
||||
// if (clickDakaButton("上班打卡") || clickDakaButton("下班打卡") ||
|
||||
// clickDakaButton("外勤打卡") || clickDakaButton("立即打卡")) {
|
||||
// Log.e("TIAOSHI###", "AblService: 成功完成打卡操作");
|
||||
// resetDingDingDaka();
|
||||
// } else {
|
||||
// Log.e("TIAOSHI###", "AblService: 未找到打卡按钮,结束流程");
|
||||
// resetDingDingDaka();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找并点击包含指定文本的按钮
|
||||
*/
|
||||
private boolean clickDakaButton(String text) {
|
||||
try {
|
||||
AccessibilityNodeInfo root = getRootInActiveWindow();
|
||||
if (root == null) {
|
||||
Log.e("TIAOSHI###", "AblService: 无法获取根节点");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 遍历所有节点查找文本
|
||||
return findAndClickDakaNode(root, text);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("TIAOSHI###", "AblService: 查找打卡按钮异常: " + text, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归查找并点击包含指定文本的节点
|
||||
*/
|
||||
private boolean findAndClickDakaNode(AccessibilityNodeInfo node, String text) {
|
||||
if (node == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 检查当前节点的文本和描述
|
||||
CharSequence nodeText = node.getText();
|
||||
CharSequence nodeContentDesc = node.getContentDescription();
|
||||
|
||||
// 输出节点信息用于调试
|
||||
if (nodeText != null || nodeContentDesc != null) {
|
||||
Log.e("TIAOSHI###", "AblService: 节点: " + node.getClassName() + " 文本: " + nodeText + " 描述: " + nodeContentDesc);
|
||||
}
|
||||
|
||||
// 检查是否包含目标文本
|
||||
if ((nodeText != null && nodeText.toString().contains(text)) ||
|
||||
(nodeContentDesc != null && nodeContentDesc.toString().contains(text))) {
|
||||
|
||||
Rect rect = new Rect();
|
||||
node.getBoundsInScreen(rect);
|
||||
if (rect.width() > 0 && rect.height() > 0 && node.isEnabled()) {
|
||||
Log.e("TIAOSHI###", "AblService: 找到匹配节点: " + text + " 坐标: [" + rect.left + "," + rect.top + "][" + rect.right + "," + rect.bottom + "]");
|
||||
|
||||
// 尝试点击节点
|
||||
if (node.isClickable()) {
|
||||
boolean clicked = node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
||||
if (clicked) {
|
||||
Log.e("TIAOSHI###", "AblService: 成功点击节点: " + text);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// 如果节点不可点击,使用 performViewClick(会自动向上查找可点击的父节点)
|
||||
AblViewUtil.performViewClick(node);
|
||||
Log.e("TIAOSHI###", "AblService: 使用 performViewClick 点击: " + text);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 递归查找子节点
|
||||
for (int i = 0; i < node.getChildCount(); i++) {
|
||||
AccessibilityNodeInfo child = node.getChild(i);
|
||||
if (findAndClickDakaNode(child, text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w("TIAOSHI###", "AblService: 查找节点异常", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置钉钉打卡状态
|
||||
*/
|
||||
private void resetDingDingDaka() {
|
||||
Log.e("TIAOSHI###", "AblService: 重置钉钉打卡状态");
|
||||
isDingDingDakaStarted = false;
|
||||
dingDingDakaStep = 0;
|
||||
dingDingHandler.removeCallbacksAndMessages(null); // 清除所有延迟任务
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -638,15 +638,18 @@ public class MainActivity extends BaseActivity implements InfoMessage {
|
||||
}
|
||||
|
||||
// 启动自动化打卡流程
|
||||
// 延迟1秒后发送 STEP_270 消息,让钉钉应用有时间启动
|
||||
// 延迟1秒后启动打卡流程,让钉钉应用有时间启动
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.e("TIAOSHI###", "MainActivity: 启动钉钉打卡自动化流程");
|
||||
// 必须先设置 setStop(false),否则消息不会被处理
|
||||
AblStepHandler.getInstance().setStop(false);
|
||||
Log.e("TIAOSHI###", "MainActivity: 已设置 setStop(false),发送 STEP_270 消息");
|
||||
AblStepHandler.sendMsg(AblSteps.STEP_270);
|
||||
try {
|
||||
AblService.getInstance().startDingDingDaka();
|
||||
Log.e("TIAOSHI###", "MainActivity: 已调用 AblService.startDingDingDaka()");
|
||||
} catch (Exception e) {
|
||||
Log.e("MainActivity", "启动钉钉打卡流程失败", e);
|
||||
SafeToastUtils.showShort(MainActivity.this, "启动打卡流程失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
} catch (Exception e) {
|
||||
|
||||
167
钉钉打卡.txt
Normal file
167
钉钉打卡.txt
Normal file
@@ -0,0 +1,167 @@
|
||||
钉钉打卡功能实现文档
|
||||
====================
|
||||
|
||||
一、功能概述
|
||||
-----------
|
||||
实现了钉钉应用的自动化打卡功能,通过无障碍服务(AccessibilityService)自动完成以下操作:
|
||||
1. 启动钉钉应用
|
||||
2. 在钉钉首页查找并点击"打卡"按钮
|
||||
3. 等待跳转到打卡页面
|
||||
4. 在打卡页面点击"打卡"按钮完成打卡
|
||||
|
||||
二、实现方案
|
||||
-----------
|
||||
将钉钉打卡的业务逻辑直接集成到 AblService(无障碍服务)中,而不是使用 AblStepHandler。
|
||||
这样做的优势:
|
||||
- 避免 Handler 消息丢失:业务逻辑在无障碍服务中执行,即使主应用在后台也能正常运行
|
||||
- 更可靠:无障碍服务是系统级服务,不会被轻易暂停
|
||||
- 代码集中:所有打卡逻辑都在 AblService 中,便于维护
|
||||
|
||||
三、修改文件清单
|
||||
---------------
|
||||
|
||||
1. app/src/main/java/com/fisherbone/fuzhu/abllib/AblService.java
|
||||
2. app/src/main/java/com/fisherbone/fuzhu/activity/MainActivity.java
|
||||
|
||||
四、详细修改说明
|
||||
---------------
|
||||
|
||||
【文件1】AblService.java
|
||||
|
||||
1. 添加导入
|
||||
- import android.os.Handler;
|
||||
|
||||
2. 添加成员变量(第47-51行)
|
||||
```java
|
||||
// 钉钉打卡相关
|
||||
private static final String DINGDING_PACKAGE = "com.alibaba.android.rimet";
|
||||
private boolean isDingDingDakaStarted = false; // 是否已启动钉钉打卡流程
|
||||
private int dingDingDakaStep = 0; // 打卡步骤:0-未开始,1-等待启动,2-点击首页打卡,3-点击打卡页面打卡
|
||||
private Handler dingDingHandler = new Handler(); // 钉钉打卡专用 Handler
|
||||
```
|
||||
|
||||
3. 修改 onAccessibilityEvent 方法(第155-162行)
|
||||
在 TYPE_WINDOW_STATE_CHANGED 事件处理中添加钉钉窗口检测:
|
||||
```java
|
||||
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
|
||||
String classname = event.getClassName().toString();
|
||||
String packageName = event.getPackageName() != null ? event.getPackageName().toString() : "";
|
||||
Log.e("TIAOSHI", "有弹出==22222" + "==classname==" + classname + ", packageName=" + packageName);
|
||||
|
||||
// 检测钉钉应用窗口,处理打卡流程
|
||||
if (DINGDING_PACKAGE.equals(packageName) && isDingDingDakaStarted) {
|
||||
handleDingDingDakaWindow(classname);
|
||||
}
|
||||
```
|
||||
|
||||
4. 添加核心方法
|
||||
|
||||
a) startDingDingDaka() - 启动打卡流程(第634-649行)
|
||||
- 由 MainActivity 调用
|
||||
- 设置 isDingDingDakaStarted = true
|
||||
- 设置 dingDingDakaStep = 1(等待应用启动)
|
||||
- 延迟2秒后执行步骤2
|
||||
|
||||
b) handleDingDingDakaWindow() - 处理钉钉窗口状态变化(第654-664行)
|
||||
- 当检测到钉钉应用窗口变化时调用
|
||||
- 根据当前步骤执行相应操作
|
||||
|
||||
c) executeDingDingDakaStep2() - 执行步骤2:点击首页打卡按钮(第669-703行)
|
||||
- 查找并点击包含"打卡"文本的按钮
|
||||
- 如果未找到,尝试"考勤打卡"、"工作"、"工作台"
|
||||
- 点击成功后,延迟2秒执行步骤3
|
||||
|
||||
d) executeDingDingDakaStep3() - 执行步骤3:点击打卡页面打卡按钮(第708-724行)
|
||||
- 当前已注释,等待用户根据实际需求实现
|
||||
- 可以查找并点击"打卡"、"上班打卡"、"下班打卡"、"外勤打卡"、"立即打卡"等按钮
|
||||
|
||||
e) clickDakaButton() - 查找并点击包含指定文本的按钮(第729-744行)
|
||||
- 获取根节点
|
||||
- 调用 findAndClickDakaNode 递归查找
|
||||
|
||||
f) findAndClickDakaNode() - 递归查找并点击节点(第749-801行)
|
||||
- 检查节点的文本和描述是否包含目标文本
|
||||
- 如果节点可点击,直接点击
|
||||
- 如果节点不可点击,使用 AblViewUtil.performViewClick() 自动查找可点击的父节点
|
||||
- 递归查找子节点
|
||||
|
||||
g) resetDingDingDaka() - 重置打卡状态(第806-811行)
|
||||
- 重置所有状态变量
|
||||
- 清除所有延迟任务
|
||||
|
||||
【文件2】MainActivity.java
|
||||
|
||||
1. 修改 executeDingDingDaka() 方法(第640-654行)
|
||||
将原来的 AblStepHandler 调用改为直接调用 AblService 的方法:
|
||||
|
||||
原代码:
|
||||
```java
|
||||
AblStepHandler.getInstance().setStop(false);
|
||||
AblStepHandler.sendMsg(AblSteps.STEP_270);
|
||||
```
|
||||
|
||||
新代码:
|
||||
```java
|
||||
AblService.getInstance().startDingDingDaka();
|
||||
```
|
||||
|
||||
五、执行流程
|
||||
-----------
|
||||
|
||||
1. 用户在 MainActivity 中点击"钉钉打卡"按钮
|
||||
2. MainActivity.executeDingDingDaka() 被调用
|
||||
3. 检查钉钉应用是否安装(使用多种方法:getPackageInfo、getLaunchIntentForPackage、queryIntentActivities)
|
||||
4. 启动钉钉应用(优先使用 getLaunchIntentForPackage,失败则使用 LaunchHomeActivity)
|
||||
5. 延迟1秒后调用 AblService.startDingDingDaka()
|
||||
6. AblService 设置 isDingDingDakaStarted = true,dingDingDakaStep = 1
|
||||
7. 延迟2秒后执行步骤2(executeDingDingDakaStep2)
|
||||
8. 在钉钉首页查找并点击"打卡"按钮
|
||||
9. 点击成功后,设置 dingDingDakaStep = 3,延迟2秒后执行步骤3
|
||||
10. 在打卡页面查找并点击"打卡"按钮(当前已注释,需要根据实际需求实现)
|
||||
11. 打卡完成后调用 resetDingDingDaka() 重置状态
|
||||
|
||||
六、关键日志标识
|
||||
---------------
|
||||
所有钉钉打卡相关的日志都使用 "TIAOSHI###" 作为标识,便于过滤和调试:
|
||||
- "AblService: 启动钉钉打卡流程"
|
||||
- "AblService: 钉钉打卡-步骤1完成,开始步骤2"
|
||||
- "AblService: 钉钉打卡-步骤2: 查找并点击打卡按钮"
|
||||
- "AblService: 钉钉打卡-步骤3: 在打卡页面点击打卡"
|
||||
- "AblService: 找到匹配节点: ..."
|
||||
- "AblService: 成功点击节点: ..."
|
||||
|
||||
七、注意事项
|
||||
-----------
|
||||
|
||||
1. 无障碍服务必须已开启,否则 AblService.getInstance() 会抛出异常
|
||||
2. 钉钉应用的包名是 "com.alibaba.android.rimet"
|
||||
3. 步骤3(executeDingDingDakaStep3)当前已注释,需要根据实际打卡页面的UI实现
|
||||
4. 所有延迟时间(2秒)可以根据实际情况调整
|
||||
5. 如果找不到打卡按钮,会尝试多种可能的文本,如果都找不到则结束流程
|
||||
|
||||
八、待完善功能
|
||||
-----------
|
||||
|
||||
1. executeDingDingDakaStep3() 方法当前已注释,需要根据实际打卡页面的UI实现
|
||||
2. 可以添加更多的错误处理和重试机制
|
||||
3. 可以添加打卡结果的通知或回调
|
||||
|
||||
九、测试建议
|
||||
-----------
|
||||
|
||||
1. 确保无障碍服务已开启
|
||||
2. 确保钉钉应用已安装
|
||||
3. 在 MainActivity 中点击"钉钉打卡"按钮
|
||||
4. 观察日志输出,确认每个步骤是否正常执行
|
||||
5. 如果某个步骤失败,检查日志中的节点信息,调整查找逻辑
|
||||
|
||||
十、相关文件
|
||||
-----------
|
||||
|
||||
- app/src/main/java/com/fisherbone/fuzhu/abllib/AblService.java
|
||||
- app/src/main/java/com/fisherbone/fuzhu/activity/MainActivity.java
|
||||
- app/src/main/java/com/fisherbone/fuzhu/abllib/utils/AblViewUtil.java(提供 performViewClick 方法)
|
||||
- app/src/main/res/layout/activity_main.xml(包含"钉钉打卡"UI元素)
|
||||
|
||||
---
|
||||
文档生成时间:2026-01-06
|
||||
Reference in New Issue
Block a user