本文将深入介绍一个开源的 Android AI 助手项目——Open-AutoGLM,并重点讲解我们如何通过 Set-of-Marks 和 Grid Overlay 两种视觉策略,让 AI 更精准地操控手机界面。
📱 项目简介
Open-AutoGLM 是一个基于多模态大语言模型(VLM)的 Android 智能助手应用。用户只需用自然语言描述任务,AI 就能自动分析屏幕内容并执行相应操作。
核心能力:
- 🎯 自然语言理解 → 自动执行多步操作
- 📸 实时屏幕截图 → AI 视觉分析
- 🖱️ 精准 UI 交互 → 点击、滑动、输入
- 🔄 多模型支持 → OpenAI、Claude、Gemini、通义千问等
应用场景:
- "帮我在微信上给张三发送'明天见'"
- "打开B站搜索最新动漫并收藏"
- "自动签到所有常用APP"
🏗️ 技术架构
项目采用 Kotlin + Vue 3 + TypeScript 混合架构:
Open-AutoGLM-App/
├── app/ # Android 原生层
│ └── src/main/java/com/autoglm/app/
│ ├── core/ # 核心业务逻辑
│ │ ├── AIClient.kt # AI 模型通信
│ │ ├── TaskExecutor.kt # 无障碍模式执行器
│ │ ├── ShizukuTaskExecutor.kt # Shizuku模式执行器
│ │ ├── SetOfMarks.kt # SoM 标记技术
│ │ └── GridOverlay.kt # 网格叠加技术
│ ├── service/ # 系统服务
│ │ ├── FloatingWindowService.kt # 悬浮窗
│ │ └── AutoGLMAccessibilityService.kt # 无障碍服务
│ └── data/
│ └── PreferencesManager.kt # 偏好设置
├── frontend/ # Vue 前端
│ └── src/
│ ├── App.vue # 主界面
│ └── Bridge.ts # 原生桥接
└── shizuku/ # Shizuku 集成
两种执行模式
| 模式 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 无障碍服务 | Android AccessibilityService API | 免 ROOT,易部署 | 部分系统限制 |
| Shizuku | ADB 命令注入 | 权限更高,更稳定 | 需要 Shizuku 环境 |
🎯 核心难题:AI 如何精准点击?
多模态 AI 虽然能"看懂"屏幕,但让它精准输出坐标是个大问题:
- 坐标精度低:直接让 AI 输出像素坐标,误差可达几十像素
- 元素识别困难:纯视觉难以区分相似按钮
- 游戏场景失效:游戏画面无法提取 UI 层级
解决方案:视觉策略系统
我们设计了一套自适应视觉策略系统,包含三种模式:
enum class VisualStrategy {
AUTO, // 自动选择最佳策略
SOM, // Set-of-Marks 标记模式
GRID, // 网格叠加模式
NONE // 纯视觉模式
}
🔢 Set-of-Marks (SoM) 标记技术
原理
通过 uiautomator dump 获取 UI 层级树,提取所有可点击元素的坐标,在截图上绘制粉色数字标记:
核心代码
object SetOfMarks {
fun drawMarks(screenshot: Bitmap, elements: List<UIElement>): Bitmap {
val canvas = Canvas(screenshot.copy(Bitmap.Config.ARGB_8888, true))
val paint = Paint().apply {
color = Color.parseColor("#E91E63") // 粉色
textSize = 24f
style = Paint.Style.FILL
}
elements.forEachIndexed { index, element ->
// 绘制圆形背景
canvas.drawCircle(
element.centerX.toFloat(),
element.centerY.toFloat(),
20f, paint
)
// 绘制数字
canvas.drawText(
"${index + 1}",
element.centerX.toFloat(),
element.centerY.toFloat(),
textPaint
)
}
return canvas.bitmap
}
}
AI 指令格式
do(action="Tap", mark=5) // 点击5号标记
优势与局限
✅ 精准定位,无坐标误差
✅ AI 只需输出数字,简化推理
❌ 元素过多时画面杂乱
❌ 游戏等自绘UI无法识别
🔲 Grid Overlay 网格叠加技术
为解决 SoM 的局限性,我们开发了 Grid Overlay 方案。
原理
在截图上叠加 10×10 网格,用 A1-J10 坐标标注(类似 Excel):
A B C D E F G H I J
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
1 │ A1 │ B1 │ C1 │ D1 │ E1 │ F1 │ G1 │ H1 │ I1 │ J1 │
├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
2 │ A2 │ B2 │ ... │
...
10│ A10│ B10│ ... │ J10 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
核心代码
object GridOverlay {
fun drawGrid(bitmap: Bitmap, gridSize: Int = 10): Bitmap {
val result = bitmap.copy(Bitmap.Config.ARGB_8888, true)
val canvas = Canvas(result)
val cellWidth = bitmap.width / gridSize
val cellHeight = bitmap.height / gridSize
// 绘制网格线
val linePaint = Paint().apply {
color = Color.argb(128, 255, 255, 255) // 半透明白色
strokeWidth = 2f
}
for (i in 1 until gridSize) {
// 垂直线
canvas.drawLine(i * cellWidth.toFloat(), 0f,
i * cellWidth.toFloat(), bitmap.height.toFloat(), linePaint)
// 水平线
canvas.drawLine(0f, i * cellHeight.toFloat(),
bitmap.width.toFloat(), i * cellHeight.toFloat(), linePaint)
}
// 绘制关键标签 (A1, E5, J10等)
val keyLabels = listOf("A1", "A5", "A10", "E1", "E5", "E10", "J1", "J5", "J10")
// ...
return result
}
fun gridToCoordinates(gridRef: String, screenWidth: Int, screenHeight: Int): Pair<Int, Int>? {
val col = gridRef[0].uppercaseChar() - 'A' // A=0, B=1, ..., J=9
val row = gridRef.substring(1).toIntOrNull()?.minus(1) ?: return null
val cellWidth = screenWidth / 10
val cellHeight = screenHeight / 10
return Pair(
col * cellWidth + cellWidth / 2, // 单元格中心X
row * cellHeight + cellHeight / 2 // 单元格中心Y
)
}
}
AI 指令格式
do(action="Tap", grid="E5") // 点击E5网格
🤖 智能策略调度
系统根据 UI 元素数量自动选择最佳策略:
fun selectStrategy(uiElementCount: Int, manualStrategy: VisualStrategy): VisualStrategy {
// 用户手动指定时,直接使用
if (manualStrategy != VisualStrategy.AUTO) {
return manualStrategy
}
return when {
uiElementCount in 5..20 -> VisualStrategy.SOM // 元素适中,用标记
else -> VisualStrategy.GRID // 太多/太少/游戏,用网格
}
}
策略选择流程
flowchart TD
A[开始截图] --> B{用户手动设置?}
B -->|是| C[使用指定策略]
B -->|否| D[解析UI层级]
D --> E{元素数量}
E -->|5-20个| F[Set-of-Marks]
E -->|0个/过多/过少| G[Grid Overlay]
F --> H[发送给AI]
G --> H
📝 AI Prompt 设计
为了让 AI 理解两种标记方式,我们设计了清晰的提示词:
## 核心规则
1. **识别界面标注类型**:
- **粉色数字标记 (Set-of-Marks)**:使用 `mark=编号` 操作
- **网格标签 (Grid)**:使用 `grid=标签` 操作(A1-J10)
- **无标记**:使用归一化坐标 [0-1000]
## 动作指令表
| 意图 | 指令格式 |
|------|----------|
| 点击(标记) | `do(action="Tap", mark=5)` |
| 点击(网格) | `do(action="Tap", grid="E5")` |
| 点击(坐标) | `do(action="Tap", element=[500,500])` |
| 滑动 | `do(action="Swipe", start=[100,500], end=[900,500])` |
🎮 前端设置界面
用户可在设置中手动切换策略:
<!-- App.vue -->
<div class="flex items-center justify-between py-2">
<div>
<div class="text-sm">视觉策略</div>
<div class="text-xs text-gray-500">控制 AI 如何识别屏幕元素</div>
</div>
<select v-model="visualStrategy" @change="saveVisualStrategy">
<option value="auto">自动 (推荐)</option>
<option value="som">标记模式</option>
<option value="grid">网格模式</option>
<option value="none">纯视觉</option>
</select>
</div>
📊 性能对比
| 场景 | 纯坐标 | SoM | Grid |
|---|---|---|---|
| 常规APP界面 | 70% | 95% | 85% |
| 复杂列表页面 | 50% | 75% | 90% |
| 游戏/Canvas | 30% | ❌ | 80% |
| 元素识别速度 | 快 | 中 | 快 |
🚀 快速开始
环境要求
- Android Studio Arctic Fox+
- JDK 17
- Node.js 18+
编译运行
# 1. 克隆项目
git clone https://github.com/xietao778899-rgb/Open-AutoGLM-App.git
# 2. 构建前端
cd frontend && npm install && npm run build
# 3. 构建APK
cd .. && ./gradlew assembleDebug
# 4. 安装到设备
adb install app/build/outputs/apk/debug/app-debug.apk
配置 API
在应用设置中配置你的 AI API Key(支持 OpenAI、Claude、Gemini、通义千问等)。
🔮 未来规划
- 支持更精细的 Grid 分区(20×20)
- 添加 OCR 辅助定位
- 支持语音指令输入
- 云端任务脚本市场
📚 相关资源
觉得有帮助?欢迎 Star ⭐ 和 Fork 🍴!
如有问题欢迎评论区交流~