Skip to content

Commit 18eb3d6

Browse files
whjstcclaude
andcommitted
feat: Add ExternalControlReceiver for silent background automation
Adds a BroadcastReceiver allowing Tasker and other automation tools to control Clash (START/STOP/TOGGLE) without triggering any UI or screen activity, complementing the existing App Shortcuts approach (#676). Root cause of previous unreliability: the initial implementation checked Remote.broadcasts.clashRunning (in-memory state reset to false when app goes background), so the stop condition always failed in background. Fixed by querying StatusClient.currentProfile() != null for real-time state via ContentProvider IPC, independent of app foreground status. No persistent notification or foreground service required. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5f481ed commit 18eb3d6

3 files changed

Lines changed: 318 additions & 0 deletions

File tree

TASKER_GUIDE.md

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Tasker 自动化配置指南
2+
3+
本指南详细说明如何在 Tasker 中配置 Clash Meta for Android (CMFA) 的自动化控制。
4+
5+
## 📌 重要提示
6+
7+
**使用 BroadcastReceiver 方式可以实现完全后台控制,不会弹出任何界面!**
8+
9+
## 前提条件
10+
11+
1. 已安装 CMFA(编译包含 ExternalControlReceiver 的版本)
12+
2. 已安装 Tasker
13+
3. 已授予 Tasker 必要的权限
14+
4. **首次使用前,必须在 CMFA 中手动启动一次 VPN 并授予权限**
15+
5. **确认你的应用包名**(见下方说明)
16+
17+
### 📦 如何确认应用包名
18+
19+
**非常重要**:不同的编译版本和配置会有不同的包名。请使用以下方法确认你的应用包名:
20+
21+
**方法 1:通过 ADB(推荐)**
22+
```bash
23+
adb shell pm list packages | grep clash
24+
```
25+
26+
**方法 2:通过应用信息**
27+
1. 长按 CMFA 应用图标
28+
2. 点击"应用信息"
29+
3. 查看应用详情中的"包名"字段
30+
31+
常见的包名:
32+
- 自定义构建版本:`com.github.kr328.clash.tasker`(或其他自定义名称)
33+
- Alpha 官方版本:`com.github.kr328.clash.alpha`
34+
- Meta 官方版本:`com.github.metacubex.clash.meta`
35+
36+
**在下面的配置中,请将 `YOUR_PACKAGE_NAME` 替换为你实际的包名!**
37+
38+
⚠️ **重要:首次 VPN 权限授予**
39+
40+
在使用 Tasker 自动化之前,必须:
41+
1. 打开 CMFA 应用
42+
2. 手动启动一次代理(会弹出 VPN 权限请求)
43+
3. 授予 VPN 权限并勾选"记住选择"
44+
4. 停止代理
45+
46+
**之后的 Tasker 自动化才能正常工作!**
47+
48+
## 方案一:BroadcastReceiver 方式(推荐)
49+
50+
### 优势
51+
-**完全后台运行**,不会触发任何界面
52+
- ✅ 适用于**所有 ROM**(包括 Flyme、MIUI、ColorOS 等国产 ROM)
53+
- ✅ 无需 Root 权限
54+
- ✅ 不受系统"后台启动限制"影响
55+
56+
### 步骤 1:创建启动 Clash 的 Task
57+
58+
1. 打开 Tasker,点击底部 **"TASKS"** 标签
59+
2. 点击右下角 **"+"** 按钮,创建新任务
60+
3. 输入任务名称:`启动 Clash`
61+
4. 点击 **"+"** 添加动作
62+
5. 选择 **System****Send Intent**
63+
6. 填写以下参数:
64+
65+
| 参数 ||
66+
|------|-----|
67+
| **Action** | `com.github.metacubex.clash.meta.action.START_CLASH` |
68+
| **Cat** | 留空 |
69+
| **Mime Type** | 留空 |
70+
| **Data** | 留空 |
71+
| **Extra** | 留空 |
72+
| **Package** | `YOUR_PACKAGE_NAME` ⚠️(替换为你的实际包名,例如 `com.github.kr328.clash.tasker`|
73+
| **Class** | 留空(重要!) |
74+
| **Target** | **Broadcast Receiver**(非常重要!) |
75+
76+
7. 点击 **返回** 保存
77+
78+
**示例**:如果你的包名是 `com.github.kr328.clash.tasker`,则 Package 字段应填写:`com.github.kr328.clash.tasker`
79+
80+
### 步骤 2:创建停止 Clash 的 Task
81+
82+
重复步骤 1,但修改以下内容:
83+
- 任务名称:`停止 Clash`
84+
- **Action**`com.github.metacubex.clash.meta.action.STOP_CLASH`
85+
- 其他参数保持不变
86+
87+
### 步骤 3:创建切换 Clash 的 Task(可选)
88+
89+
如果你想要一个单键切换开关:
90+
- 任务名称:`切换 Clash`
91+
- **Action**`com.github.metacubex.clash.meta.action.TOGGLE_CLASH`
92+
- 其他参数保持不变
93+
94+
### 步骤 4:创建自动化 Profile
95+
96+
#### 场景 1:连接家庭 Wi-Fi 时自动关闭 Clash
97+
98+
1. 点击底部 **"PROFILES"** 标签
99+
2. 点击右下角 **"+"** 创建新 Profile
100+
3. 选择 **State****Net****Wifi Connected**
101+
4.**SSID** 字段输入你的家庭 Wi-Fi 名称(例如:`My Home WiFi`
102+
5. 点击返回
103+
6. 在弹出的任务选择窗口中,选择 **`停止 Clash`**
104+
7. 完成!当连接到指定 Wi-Fi 时,Clash 会自动停止
105+
106+
#### 场景 2:离开家庭 Wi-Fi 时自动启动 Clash
107+
108+
1. 长按上面创建的 Profile
109+
2. 点击 **"Add Exit Task"**(添加退出任务)
110+
3. 选择 **`启动 Clash`**
111+
4. 完成!当断开指定 Wi-Fi 时,Clash 会自动启动
112+
113+
#### 场景 3:充电时启动,拔电时停止
114+
115+
**充电时启动:**
116+
1. 创建新 Profile:**State****Power****Power**
117+
2. 选择 **Any**(任何充电方式)
118+
3. 关联任务:**`启动 Clash`**
119+
120+
**拔电时停止:**
121+
1. 长按上面的 Profile
122+
2. 点击 **"Add Exit Task"**
123+
3. 选择 **`停止 Clash`**
124+
125+
#### 场景 4:特定时间段自动控制
126+
127+
**晚上 11 点自动关闭:**
128+
1. 创建新 Profile:**Time** → 设置时间为 `23:00`
129+
2. 关联任务:**`停止 Clash`**
130+
131+
**早上 7 点自动启动:**
132+
1. 创建新 Profile:**Time** → 设置时间为 `07:00`
133+
2. 关联任务:**`启动 Clash`**
134+
135+
### 步骤 5:测试
136+
137+
1. 手动运行任务:在 TASKS 界面,点击任务名称旁的播放按钮
138+
2. 观察手机屏幕:**应该不会弹出任何界面**
139+
3. 打开 CMFA 应用,检查服务状态是否改变
140+
4. 触发 Profile 条件(如连接/断开 Wi-Fi),验证自动化是否生效
141+
142+
## 方案二:Activity 方式(传统方式)
143+
144+
**注意:** 此方式在 Flyme 等国产 ROM 上可能会短暂弹出界面,不推荐使用。
145+
146+
### 配置方法
147+
148+
与方案一基本相同,只需修改:
149+
- **Target****Activity**(而非 Broadcast Receiver)
150+
- **Class**`com.github.kr328.clash.ExternalControlActivity`
151+
152+
## 常见问题
153+
154+
### Q1: 为什么还是会弹出界面?
155+
156+
**A:** 请确认以下几点:
157+
1. 你编译的 APK 包含了 `ExternalControlReceiver`
158+
2. Tasker 中 **Target** 设置为 **Broadcast Receiver**(不是 Activity)
159+
3. **Class** 字段留空(非常重要!)
160+
161+
### Q2: 提示"找不到组件"或"Intent 发送失败"
162+
163+
**A:** 检查:
164+
1. **Package** 是否正确:`com.github.metacubex.clash.meta`
165+
2. **Action** 是否正确(区分大小写)
166+
3. CMFA 是否已正确安装
167+
4. 是否使用了包含 BroadcastReceiver 的版本
168+
169+
### Q3: 自动化不生效
170+
171+
**A:** 排查步骤:
172+
1. 在 Tasker 中手动运行任务,看是否能控制 Clash
173+
2. 检查 Profile 的触发条件是否正确
174+
3. 确认 Tasker 有足够的权限(电池优化白名单、后台运行权限等)
175+
4. 查看 Tasker 的日志(运行日志功能)
176+
177+
### Q4: 首次启动 VPN 时还是会弹出权限请求
178+
179+
**A:** 这是正常的。Android 要求用户首次授予 VPN 权限时必须有用户交互。解决方法:
180+
1. 首次手动在 CMFA 中启动一次,授予 VPN 权限
181+
2. 勾选"记住选择"或"不再提示"
182+
3. 之后的自动化控制就不会再弹窗了
183+
184+
### Q5: 如何验证使用的是 BroadcastReceiver 方式?
185+
186+
**A:**
187+
1. 运行 Tasker 任务
188+
2. 如果屏幕**完全没有任何反应**(不闪屏、不弹窗),说明使用的是 BroadcastReceiver
189+
3. 如果短暂看到 CMFA 界面,说明还是在使用 Activity 方式
190+
191+
## 高级技巧
192+
193+
### 结合其他条件
194+
195+
你可以在 Profile 中添加多个条件(AND 逻辑):
196+
197+
**例如:工作日早上 8-18 点,且不在家庭 Wi-Fi 时,启动 Clash**
198+
199+
1. 创建 Profile
200+
2. 添加条件 1:**Time** → 08:00 to 18:00
201+
3. 点击左上角 **"+"** 添加条件 2:**Day** → 选择周一到周五
202+
4. 再添加条件 3:**State****Wifi Connected****Invert**(反选)→ 输入家庭 Wi-Fi SSID
203+
5. 关联任务:**`启动 Clash`**
204+
205+
### 创建桌面快捷方式
206+
207+
1. 长按任务
208+
2. 选择 **"Create Widget"**
209+
3. 拖动到桌面
210+
4. 点击桌面图标即可一键控制 Clash
211+
212+
## 对比:BroadcastReceiver vs Activity
213+
214+
| 特性 | BroadcastReceiver | Activity |
215+
|------|------------------|----------|
216+
| 后台运行 | ✅ 完全后台 | ⚠️ 可能弹窗 |
217+
| ROM 兼容性 | ✅ 所有 ROM | ⚠️ Flyme 等会前台化 |
218+
| 实现复杂度 | 简单 | 简单 |
219+
| 需要改源码 | ✅ 是(已完成) | ❌ 否(官方已支持) |
220+
| 用户体验 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
221+
222+
## 总结
223+
224+
使用 **BroadcastReceiver 方式**,你可以在**任何 ROM**上实现**完全后台**的 Clash 自动化控制,不会有任何界面干扰。配置完成后,Clash 会根据你设定的条件(Wi-Fi、时间、充电状态等)自动启停,真正做到"无感知"自动化。
225+
226+
## 反馈
227+
228+
如果遇到任何问题,请检查:
229+
1. Tasker 配置是否正确(特别是 Target 字段)
230+
2. CMFA 版本是否包含 `ExternalControlReceiver`
231+
3. 系统权限是否充足
232+
233+
祝你使用愉快!🎉

app/src/main/AndroidManifest.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,5 +217,16 @@
217217
<data android:scheme="android_secret_code" android:host="252746382" />
218218
</intent-filter>
219219
</receiver>
220+
221+
<!-- ExternalControlReceiver: silent background control for Tasker and other automation tools -->
222+
<receiver
223+
android:name=".ExternalControlReceiver"
224+
android:exported="true">
225+
<intent-filter>
226+
<action android:name="com.github.metacubex.clash.meta.action.START_CLASH" />
227+
<action android:name="com.github.metacubex.clash.meta.action.STOP_CLASH" />
228+
<action android:name="com.github.metacubex.clash.meta.action.TOGGLE_CLASH" />
229+
</intent-filter>
230+
</receiver>
220231
</application>
221232
</manifest>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.github.kr328.clash
2+
3+
import android.content.BroadcastReceiver
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.util.Log
7+
import com.github.kr328.clash.remote.StatusClient
8+
import com.github.kr328.clash.util.startClashService
9+
import com.github.kr328.clash.util.stopClashService
10+
11+
/**
12+
* ExternalControlReceiver - 用于 Tasker 等自动化工具的后台控制接收器
13+
*
14+
* 直接在 onReceive 中执行操作,无需启动 Activity。
15+
*
16+
* 使用方法(Tasker):
17+
* - 动作类型:Send Intent
18+
* - Action:com.github.metacubex.clash.meta.action.START_CLASH (或 STOP_CLASH / TOGGLE_CLASH)
19+
* - Target:Broadcast Receiver
20+
* - Package:com.github.metacubex.clash.meta
21+
*/
22+
class ExternalControlReceiver : BroadcastReceiver() {
23+
companion object {
24+
private const val TAG = "ExternalControlReceiver"
25+
}
26+
27+
override fun onReceive(context: Context, intent: Intent) {
28+
Log.d(TAG, "收到广播: action=${intent.action}")
29+
30+
// 通过 StatusClient 实时查询服务状态,避免依赖仅在前台维护的内存状态
31+
val clashRunning = StatusClient(context).currentProfile() != null
32+
33+
when (intent.action) {
34+
"com.github.metacubex.clash.meta.action.START_CLASH" -> {
35+
Log.d(TAG, "处理 START_CLASH,当前状态: $clashRunning")
36+
if (!clashRunning) {
37+
val vpnRequest = context.startClashService()
38+
if (vpnRequest != null) {
39+
Log.e(TAG, "需要 VPN 权限,请先在应用中手动启动一次")
40+
} else {
41+
Log.d(TAG, "Clash 服务已启动")
42+
}
43+
} else {
44+
Log.d(TAG, "Clash 已在运行")
45+
}
46+
}
47+
48+
"com.github.metacubex.clash.meta.action.STOP_CLASH" -> {
49+
Log.d(TAG, "处理 STOP_CLASH,当前状态: $clashRunning")
50+
if (clashRunning) {
51+
context.stopClashService()
52+
Log.d(TAG, "Clash 服务已停止")
53+
} else {
54+
Log.d(TAG, "Clash 未在运行")
55+
}
56+
}
57+
58+
"com.github.metacubex.clash.meta.action.TOGGLE_CLASH" -> {
59+
Log.d(TAG, "处理 TOGGLE_CLASH,当前状态: $clashRunning")
60+
if (clashRunning) {
61+
context.stopClashService()
62+
Log.d(TAG, "Clash 服务已停止")
63+
} else {
64+
val vpnRequest = context.startClashService()
65+
if (vpnRequest != null) {
66+
Log.e(TAG, "需要 VPN 权限,请先在应用中手动启动一次")
67+
} else {
68+
Log.d(TAG, "Clash 服务已启动")
69+
}
70+
}
71+
}
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)