一、为什么 Mika 不只是一个 AI 聊天工具

在这里插入图片描述

一款普通的 AI 应用可以帮用户写文案、总结资料、生成代码。但当用户说“进入微信群,翻完今天的聊天记录,整理最重要的三件事”时,模型即使知道应该怎么做,也不一定具备打开微信、识别元素、滚动列表、处理加载中状态的能力。

这里有两个完全不同的技术问题:

  • 大模型的推理能力:理解目标,判断该用什么工具、什么时候结束。
  • 执行环境的操作能力:真实地启动 App、读到当前界面、点击正确按钮、确认操作是否生效。

Mika 将两者连接起来。用户直接描述目标,手机端负责执行。项目 README 展示了微信、小红书、电商、航班查询、招聘网站、资讯阅读等场景;这些是仓库提供的演示任务,不能据此推断任意 App、任意系统均有相同成功率。

1.1 官方演示:后台发微信

在这里插入图片描述

图 1|官方动态演示。 左侧为 Mika 对话,右侧为 Agent 后台屏上的微信操作过程。原仓库的演示为加速播放,聊天信息经过处理。来源:官方媒体文件。

1.2 官方演示:翻阅微信群并总结

在这里插入图片描述

图 2|官方动态演示。 典型任务不是一次读完所有内容,而是按当前可见范围逐段观察、滚动,收集材料后形成摘要。来源:官方媒体文件。

1.3 官方演示:小红书内容提取

在这里插入图片描述

图 3|官方动态演示。 对应用界面的读取和跨页面收集,比一句话总结更能反映移动 Agent 的工程难度。来源:官方媒体文件。

1.4 Mika 的关键差异在哪里

传统无障碍自动化往往操作当前物理屏幕,用户看着界面被脚本接管。Mika 使用单独的后台虚拟显示器来运行目标 App,尽可能避免占用用户正在看的主屏。注意这不意味着它能绕过系统权限、绕过第三方 App 的安全保护,或者在所有厂商 ROM 上都能表现一致。

另外,“on-device”不等于大模型默认在手机上本地推理:Mika 把任务执行、运行记录、App 操作保留在设备一侧;LLM 可以是用户选择的云服务,也可以是局域网里的兼容模型服务。使用云端模型时,任务相关文本和必要画面仍可能发送给模型供应商。

二、先看工程结构:Core 负责决策,App 负责 Android 落地

2.1 两个 Gradle 模块

根据仓库的 settings.gradle.kts,主要模块只有两个:

rootProject.name = "Mika"
include(":core", ":app")

:core 是纯 Kotlin/JVM 核心,不需要直接持有 Android 的 Activity、View 或系统服务;这里集中实现 AgentLoop、模型适配、工具契约、上下文、记忆、技能、任务运行、风险判断和评测相关逻辑。

:app 是 Android 宿主,提供 Compose 页面、前台任务服务、shell 辅助进程、虚拟显示器、Accessibility、WebView、文件存储、沙箱等实现。

从依赖方向上看,核心只理解“能力接口”,具体能力通过装配层注入。项目专门有 PackageDependencyTest 约束包间依赖,避免平台与能力模块相互随意引用。这比把 AI 请求、ADB 命令和按钮点击写在同一个 ViewModel 里更可维护。

在这里插入图片描述

图 4|本文根据源码绘制的总体架构图。 任务入口、任务运行时和能力实现分层;不同能力最终经共同的工具与执行机制协作。

2.2 仓库里应该优先读哪些文件

Mika/
├── app/                                    # Android 宿主
│   ├── src/main/java/com/wangzan101/mika/
│   │   ├── task/                           # TaskService / TaskRun / TaskCenter
│   │   ├── cap/phone/                      # 手机能力:虚拟屏、Shell、无障碍
│   │   ├── cap/web/                        # WebView、DevTools、网页能力
│   │   ├── cap/sandbox/                    # 本地 Linux 沙箱
│   │   ├── ui/                             # Compose 界面
│   │   └── mcp/                            # 手机端远程能力
│   └── build.gradle.kts
├── core/
│   └── src/main/kotlin/com/wangzan101/mika/core/
│       ├── agent/                          # AgentLoop / Context / ModelCaller
│       ├── action/                         # ActionExecutor / 风险 / 审批
│       ├── tool/                           # ToolDispatcher / AgentTool
│       ├── run/                            # RunInbox / RunSpec / Outbox
│       ├── uistate/                        # ScreenSnapshot / 等待及核对
│       ├── observation/                    # 统一 Observation
│       ├── memory/                          # 记忆
│       ├── knowledge/                      # App / 网站知识
│       ├── skills/                         # 个人技能
│       └── replay/                         # 成功路线回放
├── docs/dev/architecture.md                # 架构说明
├── docs/dev/roadmap.md                     # 已知问题与路线图
├── eval/                                   # 真机评测用例
└── tools/                                  # CLI / relay / sandbox 构建工具

目录树进行了面向阅读的抽取;完整文件应以仓库实际结构为准。

2.3 构建依赖并不是“随意一个 Android 项目”

核查时主分支的版本目录记录了 Kotlin 2.1.20、Android Gradle Plugin 8.7.3、OkHttp 4.12.0、Coroutines 1.9.0、ML Kit 文字识别 16.0.1。App 使用 Java 17 工具链、Jetpack Compose,compileSdk = 36,minSdk = 30,targetSdk = 35。这些数字是这次阅读源码时的配置,后续升级可能变化。

此外,app/build.gradle.kts 显示主分支 versionName = "0.1.34"。代码版本不等于已发布 APK 版本;安装时应以 Releases 里的实际发布包为准,不能将主分支版本号误写成“最新稳定版”。

三、核心突破:Android 后台虚拟屏究竟如何工作

3.1 “第二块屏幕”不等于第二台虚拟机

Android 原生系统本来就支持多个显示器。Mika 利用的是虚拟显示器上的应用任务运行,不是在手机里再启动一份完整 Android 系统,也不是将所有操作简单画到一张图片上。

  • 主屏幕:用户正常触摸、输入和查看内容。
  • 虚拟显示器:目标 App 在独立显示区域运行,Mika 获取其画面及界面状态。
  • 共同底座:两边仍属于同一 Android 设备,系统资源、App 安全边界与某些状态仍然共享。

在这里插入图片描述

图 5|双屏运行原理。 双显示器改善交互隔离,但不提供与容器或虚拟机等价的账户和系统资源隔离。

3.2 为什么普通 App 不能随便完成这件事

要把其他 App 的 Activity 放进特定显示器,并进行系统级输入操作,普通 APK 往往受到 Android 权限与系统接口限制。因此,Mika 通过手机的 无线调试 / ADB 启动一个以 shell 身份工作的辅助进程。

按照项目文档,手机端引导流程通常包括:打开开发者选项、USB 调试、无线调试,使用配对码完成一次连接。配对之后由内置助手承担所需系统级操作。常规方案不以 Root 为前提,但并不意味着不需要授权或系统条件。

这套实现涉及三个实际组件:

  1. BuiltinShellManager、HelperProcess:启动和管理 shell 辅助进程。
  2. ShellService:以允许的能力建立、管理专用显示器并执行辅助系统操作。
  3. AgentDisplay:在 App 进程内获取后台显示的图像帧和预览。

3.3 源码分析:AgentDisplay 的三个关键点

文件:app/cap/phone/display/AgentDisplay.kt

第一,画面有独立的 Surface。 AgentDisplay 创建 ImageReader,并将其 surface 交给 shell 服务创建显示:

// 摘取的核心实现,省略异常处理与资源释放
private val reader = ImageReader.newInstance(
    bufferWidth,
    bufferHeight,
    PixelFormat.RGBA_8888,
    2
)

val id = shell.createDisplay(
    reader.surface,
    bufferWidth,
    bufferHeight,
    dpi
)

这里的图像来源是虚拟显示器,而不是“不断对物理主屏截图”。这是后台运行的关键差别。

第二,实时预览有采样预算。 源码对预览帧做 100ms 间隔控制,acquireLatestImage() 允许丢弃旧帧;界面预览短边控制在 360px 范围。这样做的工程逻辑很明确:用户看执行过程不需要毫秒级画面,低频预览可以控制内存复制与图像处理开销。

第三,视觉识别可使用原尺寸副本。 latestFrameBitmap() 和 latestFramePng() 用于更完整的图像读取;预览和 OCR 并非一定使用同一幅低分辨率位图。

3.4 焦点、横竖屏和任务收尾

理解 Android 双屏不能只看 createDisplay()。还存在三个容易被忽略的问题。

输入焦点: Android 12/13 的某些操作可能影响主屏焦点;项目推荐 Android 14 及以上,因为独立焦点处理更适合让用户一边输入、一边由 Agent 后台操控目标 App。

屏幕方向: 虚拟显示需要对横屏 App 进行方向和画面坐标处理。相关逻辑在 DisplayOrientationWatch、DisplayFrameGeometry 等实现里,并非固定为竖屏坐标。

完成后 App 去向: 任务结束时,系统需要决定关闭、保留或迁移目标 App,避免把后台使用的 Activity 突然推到用户眼前。这是实际体验与“做个演示”之间的差距。

3.5 Android 版本兼容性

能力要求 / 建议原因
聊天、网页、文件功能Android 11+不依赖手机 App 操作虚拟屏
操作其他 Android AppAndroid 12+依赖后台屏及系统辅助能力
较好的焦点隔离Android 14+ 推荐用户主屏输入更不容易受干扰
本地 Linux 沙箱arm64原生沙箱组件面向该 ABI

注意: 达到 Android 版本要求不等于兼容性已通过。厂商 ROM、后台进程限制、无线调试状态、具体 App 防护策略都会影响结果。Mika 在设置中提供兼容性自检,用真实设备报告判断实际可用程度。

四、如何看懂界面:控件树优先,OCR 与视觉模型补充

“AI 看屏幕”经常被误解成每一步都先截图,再调用昂贵的多模态模型。Mika 的设计并不是这样。

它更接近结构化优先的观测系统:

  1. Accessibility / Web DOM 读取控件、文字、边界、状态、层级。
  2. 控件树缺失或不完整时,ML Kit OCR 补读屏幕文本。
  3. 可用视觉模型时,必要情况下再用图像理解和定位补充。
  4. 将结果统一转换为 ScreenSnapshot / Observation,供 AgentLoop 和校验机制使用。

在这里插入图片描述

图 6|感知链路。 结构化观测可以降低模型阅读无关像素的成本,但并不能消除透明遮罩、Canvas、自定义控件、受保护页面带来的识别风险。

4.1 为什么优先使用 Accessibility

以一个“确定”按钮为例:

Element #18
  role: button
  text: 确定
  bounds: [420, 1280, 660, 1352]
  enabled: true

这段为说明用的示意元素数据,不是仓库运行截图。它比仅给模型一张 1080×2400 的大图更直接:模型可以选目标元素,执行时也可以检查按钮是否仍然存在、是否仍处于同一页面。

需要注意:元素编号只在对应的界面状态中有意义。页面一旦变化,就应重新确认目标,不能把历史编号永久当作稳定定位符。

4.2 为什么 OCR 是补充而不是主角

移动 App 存在自绘 UI、游戏化 Canvas、图片文字、WebView 混合页面。此时无障碍树可能看不到完整文字,OCR 能帮助识别可见区域。但 OCR 有两个局限:识别到文字不等于定位到了可点击控件;识别结果也可能因字体、遮挡、缩放而出错。

因此,Mika 的视觉定位工具会尽量先匹配控件树,再根据需要使用看图定位,并将缺少验证的目标标识为未验证。这类设计能降低盲点,但不会让误点概率变成零。

4.3 动作前后为什么必须重新读屏

假设模型点击了“搜索”,有三种可能:

  • 页面已经跳转,搜索结果出现。
  • 页面还在加载,暂时未出现结果。
  • 点击没生效,或者有弹窗挡住。

如果都当成“成功”,下一个动作很容易操作错对象。Mika 因此把读屏状态、动作效果、等待界面稳定以及前后差异判断整合到工具调用的反馈中。

这就是 Agent 自动化与固定坐标脚本最本质的区别之一:动作不是事实,动作后被验证的状态才是事实。

五、AgentLoop:一句话怎样变成多轮真实操作

项目核心文件:core/agent/AgentLoop.kt。

源码开头的职责描述已经非常明确:主循环只做调度,模型调用交给 ModelCaller,工具交给 ActionExecutor,停止规则、卡住建议、知识推荐和回答策略由各自组件处理。

在这里插入图片描述

图 7|AgentLoop 典型循环。 真实系统还包含提问、确认、取消、重试、插话和终止路径。

5.1 用一段 Kotlin 伪代码理解主循环

// 教学伪代码:不是可直接替换的 Mika 源文件
suspend fun executeGoal(goal: String) {
    val history = mutableListOf<Message>()
    history += Message.user(goal)

    while (true) {
        val response = modelCaller.ask(
            messages = contextBuilder.build(history),
            tools = toolDispatcher.offeredNow()
        )

        if (response.toolCalls.isEmpty()) {
            deliverFinalAnswer(response.text)
            return
        }

        val call = response.toolCalls.first()
        val outcome = actionExecutor.execute(call)

        history += Message.assistant(response)
        history += Message.tool(outcome)

        // 失败、接管、取消、上下文和步骤限制
        // 都由实际项目中各自的策略处理。
    }
}

这段代码的目的,是说明模型不直接执行操作,而是提出工具调用,由程序执行并返回证据。实际 Mika 主循环还有一个重要约束:每轮模型响应的工具调用不会被无条件并发执行,而是有明确的执行策略、守卫和后续处理。

5.2 目标任务 vs 最终回答

Mika 并不是靠“模型说我完成了”就一定相信任务完成。

从源码约定看,不包含工具调用的模型回复可成为最终回答;如果任务只能部分完成、遭遇障碍或确实办不到,则可通过 end_not_done 报告失败或未完成原因。运行记录中的结果与证据可用于后续结局判断。

但要特别注意:仓库路线图记录,开发阶段最终回答独立核查有一部分被关闭。因此,“已产生回答”“被系统记为成功”“在真机上确实办成”是三个不同概念。发布测评时不能混为一谈。

5.3 一次真实任务会经过哪些步骤

以“总结今天某群聊天”为例,理想执行序列可能是:

目标:总结今天群里讨论的重要内容
  1. 查找并启动微信
  2. 定位群聊入口
  3. 打开指定群聊
  4. 读取当前可见消息
  5. 判断是否还有今天更早的消息
  6. 滚动并再次读取,注意不要重复采集
  7. 到达时间边界 / 加载边界,整理有效消息
  8. 返回总结,并提供来源线索

这是一条教学用可能路径,不是对某次官方演示日志的逐步复刻。 实际执行会随微信版本、登录态、消息量、设备和模型改变。

六、工具架构:ToolDispatcher 与 ToolGroups 解决两个不同问题

很多 Agent 项目将所有工具定义一次性发送给模型:打开 App、点击、滑动、读取网页、运行代码、写文件、计划任务……工具数量一多,模型上下文会非常臃肿。

Mika 在 core/tool/ 中定义独立的工具模型,并通过 ToolRegistry 和 ToolDispatcher 统一管理。工具会声明所需能力、风险级别、是否支持批量、能否回放、并发策略等属性。

6.1 ToolDispatcher 管“调用哪个工具”

职责包括查找工具、校验参数、分配能力、组织工具结果、维护证据账本。它知道“有什么工具可调用”,但不应该直接取代统一动作执行入口做风险判定。

6.2 ActionExecutor 管“这个动作能不能做,做完怎样判定”

这部分将在下一节单独讲。它把外部状态修改收敛到统一链路,减少不同工具的行为不一致。

6.3 ToolGroups 管“什么时候让模型看到完整工具”

开局主要提供通用工具及各能力的入口工具。例如,当模型第一次使用手机能力或网页能力时,再展开相应的工具组;展开后的定义在当前任务里保留。

这样做的潜在收益是:

  • 固定提示词更短,减轻上下文压力。
  • 不相关的操作工具在任务开局不会一起干扰模型选择。
  • 服务商的前缀缓存更容易利用稳定内容。

代价也很明确:第一次展开工具组可能增加模型轮次,而且“工具组什么时候展开”的判断本身也需要测试。

对应源码:ToolGroups.kt、AgentTool.kt。

七、统一执行入口:ActionExecutor 是我认为最值得研究的模块

大模型能够提出一个动作,不等于系统应该立即执行。特别是它正在操作真实账号、真实 App、真实文件时,错误点击可能造成不可逆后果。

Mika 的核心设计是:修改外部世界的动作进入统一的 ActionExecutor,无论动作最初来自模型、批量脚本、经验回放,还是其他受支持的工具来源。

在这里插入图片描述
9%A7%E8%A1%8C%E5%85%A5%E5%8F%A3.png&pos_id=img-nFiy3oyt-1793771424060)

图 8|统一执行入口。 在图示中把守卫、系统状态、风险决策、实际操作、效果验证和运行记录拆开,是为了展示责任边界,并非逐行复制源码调用栈。

7.1 统一入口大致执行什么

源码中 ActionExecutor.run()、aimed() 的关键步骤可以归纳为:

模型或脚本提出工具调用
        ↓
识别工具及目标屏幕
        ↓
必要时将语义目标解析为当前控件
        ↓
操作守卫:重复操作、失败、无进展等
        ↓
屏幕、锁屏、访问权限等前置检查
        ↓
风险判断:当前动作是否需要批准或交还用户
        ↓
根据动作性质等待必要节奏
        ↓
真实调用工具
        ↓
读取动作后的状态,核对效果
        ↓
记录来源、结果、观察及失败信息

ActionPass 也是值得注意的约束:写操作的分发需要特定许可,由统一执行路径持有。它不仅是开发规范上的“记得校验”,还通过程序结构增加绕过的难度。

7.2 为什么不能只靠 Prompt 写一句“不要做危险动作”

Prompt 只能影响模型选择,并不能提供强制执行边界。模型可能误解当前页面,也可能错误调用工具。真正可靠的执行系统至少需要这些程序层保护:

  • 工具类型和能力边界:只读工具不能任意变成写工具。
  • 风险动作判定:不同动作走不同控制路径。
  • 当前状态核对:不能用过期的按钮编号执行新页面动作。
  • 可观测运行记录:知道执行了什么,结果是否明确。
  • 对不确定结果采取保守语义:尤其避免重复提交。

7.3 默认询问模式的真实安全边界

这一点必须单独说明,因为非常容易被误写。

Mika 提供“询问模式”和“自主模式”。默认询问模式并不是所有写操作都先问用户。按现有源码及文档,主要针对花钱、下单等步骤请求确认;普通发送消息、发帖、提交表单、删除内容等动作通常不会逐次确认。

登录、验证码、付款密码等需要人亲自处理的场景会请求接管。自主模式还会降低部分付款动作的确认门槛,因此在真实账号上不应盲目开启。

电脑端或无人在场的任务,对需要支付确认的步骤不能简单复用手机上的用户批准能力。用户同意一个动作,也不意味着给后续所有动作永久授权。

这个边界对博客读者非常重要:工具执行系统有治理机制,但它不应被夸大为覆盖所有高风险行为的生产级审批平台。


八、一次多步:为什么 do_steps 可以减少模型往返

一种最慢的 UI Agent 实现是:

模型判断 → 点击 → 调模型 → 点击 → 调模型 → 输入
    → 调模型 → 等待 → 调模型 → 滚动……

如果 LLM 往返耗时相对较高,那么每个小动作都要调用模型,会明显拉长总任务时间。

Mika 的 BatchRunner 提供了 do_steps:在确认动作顺序时,将多个已知子步骤交给程序逐个执行、逐个校验。源码规定每次 1~20 步,并支持等待条件。

8.1 可以等待什么条件

BatchRunner.kt 中可以看到这些字段:

条件字段用途
expect等某段界面文字或目标出现
expect_gone等目标内容消失
expect_app等前台 App 或页面位置符合要求
wait_stable等界面稳定下来
timeout_s设置等待上限

简化示例(用于解释结构,不保证可直接作为 Mika 真实工具调用 JSON):

{
  "steps": [
    {"tool": "phone_launch_app", "args": {"app": "目标应用"}, "expect_app": "目标应用"},
    {"tool": "phone_tap", "args": {"target": "搜索"}, "expect": "搜索输入框"},
    {"tool": "phone_type_text", "args": {"text": "关键词"}, "wait_stable": true}
  ]
}

真正执行时,工具参数必须依据当时实际开放的 Tool Schema 和控件状态构造,不能把上述示意 JSON 当成稳定 API。

8.2 为什么批处理不是无脑加速

Mika 会在子步骤之间检查异常。比如某一步发现意料之外的弹窗、跳到了别的 App、输入文本回读不一致,或者当前任务收到了新的用户插话,就应停止后续步骤,把状态交还模型。

这里体现了一个很好的原则:减少模型往返,而不是减少必要的效果验证。

UntilWait.kt 中还用了事件通知与轮询兜底,避免完全依赖固定 sleep(3000)。它的时间控制包括最小读取间隔、无事件时的重读间隔和突发异常的二次确认;这使“等待页面加载”更接近状态驱动,而不是写死延迟。

源码:BatchRunner.kt、UntilWait.kt。

九、任务运行时:TaskCenter 与 RunInbox 处理真实世界中的打断和并发

如果 Agent 只能在 UI 页面打开时运行、用户中途说话就乱套、网络断开后重复提交,那么它很难成为真正的自动化系统。Mika 在任务运行层做了较多工程工作。

9.1 一个对话只有一个 Run

TaskCenter 以对话为单位索引任务。相同对话已有运行时,新消息通常作为插话或队列中的后续任务处理,而不是同时启动两个相互争用同一上下文的循环。

不同对话则可以排队或在允许时并行。项目文档说明 Android 14+、启用相应速度设置后可使用不同对话并行;默认并行开关是关闭的。并行能力也受设备内存、显示器创建与资源协调约束。

在这里插入图片描述

图 9|任务与资源管理。 同一 App 同时归属多个虚拟屏会引入状态竞争,项目通过共享资源调度器限制这种争用。

9.2 并行时,为什么还需要资源调度器

三类资源最容易冲突:

  1. 同一个 App:两个 Agent 不能同时假定自己独占同一 App 的状态。
  2. 主屏接管:用户只能在主屏上处理当前接管中的操作,接管需要排队。
  3. 剪贴板:部分输入路径借用剪贴板,必须避免多个任务并发改写。

源码 ResourceScheduler 和 TaskCenter.reserve 等接口体现了这种所有权思路。检测到相互等待时,调度器还会避免简单无限期死锁。

9.3 RunInbox 怎样支持“任务执行期间插一句话”

设想 Agent 正在收集群聊,用户补充:“不需要昨天的,只看今天。”

RunInbox 将这句话暂存为 Steer。当模型调用仍在等待返回且可以安全中断时,会触发新的决策请求;已经进入执行入口的真实工具操作不会因为一句插话而自动撤回。对于可中断的等待或批次之间,程序则可以提前结束等待,让新的用户要求尽快加入上下文。

这比“收到第二条消息就把整个任务 cancel 掉”更符合真实交互,但实现上必须仔细处理并发、消息顺序和回执状态。

9.4 进程被杀后为什么不自动从中间继续

Android 可能回收后台进程,模型请求也可能断线。Mika 用 RunStore、RunRecovery、Outbox 记录运行状态、交付结果及异常中断。

一个重要而保守的设计是:进程重启后不会假定过去的 UI 状态仍然存在,也不会盲目从上一次点击继续。 对中断任务,系统更倾向于记录中断、告知用户,并让用户重新发起。

对于“消息可能已经发送,但回执丢了”的场景,重复发送往往比报告“结果不确定”更危险。因此,Outbox 有 Uncertain 状态;类似语义应在企业级 Agent 的每个不可逆写操作里得到认真对待。

十、Web Agent:WebView 与 DevTools 协议不是简单打开网页

Mika 除了原生 App 控制,还提供内置网页浏览和网页自动操作。官方演示包括电商筛选、航班查找以及网页资讯阅读。

在这里插入图片描述

图 10|官方电商演示。 通过网页查找符合预算与评分要求的商品,并将选中商品加入购物车;这是项目公开演示的能力,并不代表任何网站都可稳定复现。来源:官方媒体文件。

在这里插入图片描述

图 11|官方航班查询演示。 跨表单、结果页和排序信息的读取,对等待条件和页面稳定性要求更高。来源:官方媒体文件。

在这里插入图片描述

图 12|官方网页信息提取演示。 与原生 App 不同,网页路线可以利用页面结构来获取更明确的元素信息。来源:官方媒体文件。

10.1 网页为什么也能在后台运行

项目在 App 自己的私有虚拟显示上管理 WebView,不必打开用户正在看的主浏览器。它通过 DevTools 协议处理页面读取和输入,并保留必要的网站状态与 Cookie。

WebView 页面和原生手机虚拟屏虽然都可以在后台使用,但并不是完全相同的执行栈。Web 能力有自己的标签池、网页观察方式、状态管理和接管流程。

10.2 为什么要用 DevTools 而不全靠 JavaScript 注入

一般的脚本注入容易受到页面 CSP、隔离世界、前端框架以及事件真实性判断的影响。Mika 优先通过 DevTools 路线处理部分页面输入,并在不支持的场景保留必要的脚本兜底。

项目文档还提到避免启用某些不必要的 DevTools Runtime 监听,以减少网页感知到调试环境后反复重载的问题。这是由真实兼容性问题驱动的具体工程选择。

10.3 登录态、文件和脚本范围

  • 网页标签池有限额:文档描述每任务最多 4 个、全局最多 8 个常规 Agent 标签。
  • 网站的 Cookie 和登录状态被管理,遇到登录墙、验证码等可要求用户接管。
  • 下载落到当前对话工作区;上传通常限制到工作区已有文件。
  • 只读网页脚本尝试阻止 DOM 写入与新的网络调用,但它不是万能浏览器安全沙箱。

项目路线图也记录了只读脚本在秘密输入框等复杂 DOM 场景中的已知边界。这意味着不能把页面阅读能力视为已经通过全面的敏感信息防泄漏审计。

十一、手机里的 Linux 沙箱:PRoot + Alpine 在 Agent 中的作用

仅靠模型预测结果无法替代可靠计算。一份 CSV 可以有几十万行,让模型逐行分析既慢又贵,还容易把数值弄错。

Mika 在 arm64 设备上提供 PRoot + Alpine Linux 运行环境。项目将运行环境随 APK 提供,按对话建立 /workspace 工作区,支持 Python、Node 和 Shell 等常见任务。

11.1 典型文件任务链路

用户上传 CSV / 文本 / PDF
          ↓
Mika 将文件放进该对话工作区
          ↓
Agent 生成或选择合适脚本
          ↓
run_command 在沙箱中执行
          ↓
读取 stdout、错误与生成文件
          ↓
share_file 分享给用户

11.2 Linux 不是另一台独立服务器

PRoot 是用户空间运行环境,不等于一台物理 Linux 主机,也不等同具有完整隔离语义的强安全容器。Mika 文档明确说明:沙箱默认断网,只有在相关设置显式开放后才允许脚本联网;工作区也不直接挂载整个手机用户存储。

11.3 为什么沙箱调用手机工具仍要经过 ActionExecutor

项目提供沙箱命令和手机操作工具之间的 Bridge。脚本可以在受限条件下请求手机能力,但这些内部动作仍会回到共用执行入口,而不是绕过风险控制、效果记录与工具参数限制。

这一点非常关键:如果脚本拥有“超越模型工具”的隐形写权限,整个 Agent 风险模型就会失效。

十二、记忆、知识、技能、经验回放:不是一个概念

四种机制经常被混在一起。Mika 恰好把它们区分得较明确:

能力主要解决的问题可能存在的风险
记忆 / 画像用户喜欢什么、明确告诉过什么过期、误记、敏感信息保留
App/网站知识某一站点或 App 通常怎么用界面变化、说明过期
个人技能一类任务的可复用操作方法失败案例被错误固化
经验路线回放某条已成功路径再次执行当前页面与历史路线不一致

它们不是“所有任务结束后自动生成一个新 Prompt”那么简单。

内置知识以文本资源存在 core/src/main/resources/knowledge/,技能资源在 skills/。运行时会按当前目标和上下文挑选合适的知识,而不是把全部材料一次性塞给模型。

成功路线回放会在符合条件时尝试按已有步骤执行、核对每一步,并在匹配失败时交还模型。这一设计有机会减少重复探索,但效果取决于页面稳定性与路线匹配质量。

十三、上下文与 Token:为什么“保存全部历史”不是正确答案

Agent 的上下文比普通问答更复杂,因为每一步会产生模型消息、工具参数、工具反馈、屏幕文字、可能的截图,以及不断增长的观察记录。

Mika 的 ContextCompressor 对请求内容进行了分区处理:

  • 区 0:系统提示词和工具定义,在当前任务里尽量保持稳定。
  • 区 1:开局上下文,包括任务初始目标和需要的参考资料。
  • 区 2:历史,以追加方式保留之前的模型/工具往返。
  • 区 3:尾部,放这一轮新读到的界面和运行状态。

在这里插入图片描述

图 13|上下文与成本优化。 实际实现中存在多个压缩策略,示意图只保留其工程原则。

13.1 为什么不每一步都重写历史

许多模型服务支持对稳定提示词前缀做缓存。如果应用每次都重排旧历史,缓存可能失效,从而增加输入成本。项目倾向于保持发过的消息不变,只有接近上下文上限时,才对较旧的工具结果进行裁剪和压缩。

13.2 压缩并不等于“让模型随便总结之前的一切”

源码说明,压缩会优先缩短旧结果和旧截图,必要时将历史动作折成结构化的“之前的步骤”,尽可能保留用户原话、必要交互和工具调用/结果配对关系。若模型报上下文超长,还可以触发更激进的处理路径。

这种设计体现了一个非常现实的优先级:减少冗余观测,不要随意改写用户意图;可以压缩执行证据,但不能把实际未发生的动作总结成已完成。

13.3 如何计算它究竟能省多少钱

不要因为项目使用缓存和批量工具,就声称“性能提升 300%”。正确的方法是对同一任务、同一模型、同一设备做 A/B 对照,并把以下项拆开:

总任务耗时
  = 模型请求耗时
  + 屏幕读取耗时
  + 真实动作耗时
  + 等待 / 重试 / 人工接管耗时
  + 其他调度与序列化开销

近似模型成本
  = 普通输入 Token × 输入单价
  + 缓存输入 Token × 缓存单价
  + 输出 Token × 输出单价

要根据服务商真实计费规则处理缓存与推理 Token,不能在没有测量的情况下给出夸大的节省比例。

十四、CLI / Skill / MCP:电脑上的 Agent 怎样把手机当执行节点

Mika 另一个值得工程开发者关注的地方是:将手机上的真实操作能力以任务接口形式提供给电脑端。

对于电脑 Agent 而言,最理想的抽象不是 tap(532, 873),而是“打开某个 App,找到符合条件的信息并返回结果”。这能够把端侧细节留在 Mika 的 AgentLoop 和 Android 能力层。

在这里插入图片描述

图 14|外部 Agent 接入。 不同客户端可以通过 CLI+Skill 或 MCP 接入共享的手机任务服务,任务执行仍发生在 Android 端。

14.1 三种入口怎么选

场景推荐入口理由
桌面 Agent 支持 Skill、能运行命令Skill + CLI通过已有 CLI 管理任务
脚本、Shell、CI 辅助验证CLI便于传参、拿 JSON 回执
原生支持 MCP 的 AI 客户端MCP按工具协议发现和调用

通常选择其中一种即可,无需为同一个客户端重复配置两套入口。

14.2 CLI 的真实命令示例

官方文档提供的 CLI 用法包括:

mika devices --json
mika connect --device SERIAL --json
mika doctor --device SERIAL --json

mika --device SERIAL task run \
  --goal-file task.txt --json

mika --device SERIAL task status TASK_ID --json
mika --device SERIAL task wait TASK_ID --timeout 45 --json
mika --device SERIAL task steer TASK_ID \
  --text "只整理今天的消息" --json
mika --device SERIAL task cancel TASK_ID --json

注意:这里的 mika 是电脑端已安装的 CLI。它不是“手机 APK 安装完成后 Windows 终端自动就有的命令”。需要先按仓库 computer-install.md 安装。

14.3 任务编号为什么比“请求成功”更重要

对外写操作可能遇到:客户端发出了任务,但网络在返回响应时断开。此时最危险的行为是“没收到回执,立即用新编号重试”,因为任务可能已经在手机执行了。

Mika 的较新远程任务 API 使用请求编号、结果查询和明确的状态语义来降低重复提交风险。accepted 仅代表接收到请求,不代表任务已经执行成功;要用任务状态和最终结果核实。

14.4 安全边界

  • USB + ADB 转发适合本机测试。
  • 私有网络接入需要明确开启并保护令牌;局域网 HTTP 本身不提供链路加密。
  • 额外的公网远程网关是可选部署组件,不能把它理解为 Mika 默认必须连接的云后台。
  • 付款等要求用户亲自确认的环节,不应由电脑端绕过手机的人工交互。

源码/文档:docs/computer.md、docs/cli.md、docs/dev/remote-mcp.md。


十五、从安装到源码构建:开发者实际应该怎样上手

15.1 第一步:先检查手机是否符合要求

先用 ADB 查看设备:

adb devices
adb shell getprop ro.build.version.sdk
adb shell getprop ro.product.cpu.abi

解释:Android API 30 是 Android 11,31 是 Android 12,34 是 Android 14。CPU ABI 为 arm64-v8a 时,才符合项目 Linux 沙箱的主要架构条件。

adb devices 如果返回 unauthorized,需要在手机上同意这台电脑的 USB 调试授权;如果有多台设备,应明确使用 adb -s SERIAL ...,不要不加区分地操作。

15.2 第二步:优先使用官方 Releases 验证功能

项目官方发布页:

https://github.com/wangzan101/Mika/releases/latest

根据当前发布资产选择符合设备 ABI 的 APK。部分版本同时提供 universal 和 arm64 包,但具体文件名称以当次发布页为准,不应根据源码中的 versionName 猜测下载链接。

下载到本地后可以使用:

adb install -r Mika.apk

如果只想体验聊天、网页和文件能力,不必为了开始使用而先完成手机控制配对。

15.3 第三步:配置真正支持 Tool Calling 的模型

在 Mika 设置里添加服务商,或按支持的账号登录方式配置。项目支持多家模型供应商及自定义兼容端点。

如果使用局域网 Ollama 等服务,可在“自定义服务商”里填写类似:

http://192.168.1.10:11434/v1

这只是示例地址;模型应支持 Mika 所需的工具调用能力,网络应确保手机可达。大模型在电脑上运行 ≠ Mika 自动在手机上离线运行大模型。

15.4 第四步:完成手机操作向导

典型路径是:

  1. 开发者选项。
  2. USB 调试。
  3. 根据向导处理无障碍权限。
  4. 无线调试。
  5. 配对码授权。
  6. 连接与兼容性检查。

请只操作向导明确要求的系统开关;不要随意修改可能清除数据或改变系统安全性的其他开发者选项。部分第三方 ROM 不支持所需的后台显示能力,配对成功也不保证最终可用。

15.5 第五步:从源码构建

仓库采用 Gradle Wrapper。准备 Android SDK、JDK 17 和对应环境后:

git clone https://github.com/wangzan101/Mika.git
cd Mika

# macOS / Linux
./gradlew :app:assembleDebug

# Windows PowerShell
.\gradlew.bat :app:assembleDebug

通常生成:

app/build/outputs/apk/debug/app-debug.apk

安装调试包:

adb install -r app/build/outputs/apk/debug/app-debug.apk

项目还提供 :core 单元测试、Android 真机评测及性能相关构建任务。建议先执行:

./gradlew :core:test
./gradlew :app:testDebugUnitTest

声明:上述是根据仓库配置整理的上手步骤,本文没有在指定型号手机上完成完整构建、配对和真机成功率复测,不应把这些命令写成已经实测全部通过。

15.6 出问题时如何定位

现象优先排查不建议的操作
APK 无法安装Android API、ABI、包签名和安装日志直接清空手机数据
手机控制显示未就绪无线调试连接、系统辅助进程状态、ROM 兼容性盲目开启所有开发者开关
画面可见但点按无效当前显示器、元素编号是否过期、页面遮罩无限重试同一坐标
模型一直回复但不操作工具调用支持、模型接口和权限只靠修改 Prompt 猜问题
任务跑到一半停住Run 状态、接管、队列、守卫与运行记录立即重复提交有外部副作用的任务
沙箱不可用是否 arm64、运行环境解包误认为整个 APK 都不能使用

项目自带兼容性自检和运行时间线。首次调试应先解决系统能力是否可用,再优化模型提示词,否则容易把 Android 层问题误判为模型能力问题。


十六、三个案例:从用户目标推导 Agent 的实际执行链路

以下案例用于帮助理解系统,属于基于现有能力的工程推演,不是宣称本人已经跑过对应账号或得到了某个固定成功率。

案例 A:微信聊天摘要

用户任务:“总结今天群里讨论的三个重点,按重要程度排列。”

可能执行过程:

用户输入
  ↓
TaskService 建立任务
  ↓
TaskRun 装配 Phone 能力
  ↓
AgentLoop 选择启动微信
  ↓
后台显示器打开目标 App
  ↓
读取联系人与群聊列表
  ↓
进入群聊,读取当前屏幕
  ↓
必要时滚动、分批采集、按日期去重
  ↓
达到当天消息边界
  ↓
模型生成总结并返回

难点不在总结算法,而是页面读取覆盖范围、滚动是否漏读、重复消息排除、聊天记录隐私和结果是否有可追溯证据。若屏幕被保护或内容读取不完整,应告知用户限制,而不是编造“已读完”。

案例 B:电商按预算与评分筛选

用户任务:“找一个 30 美元以内、4.5 星以上的充电器,加入购物车。”

可能执行过程:

打开网页
  ↓
搜索商品并读取筛选结果
  ↓
核查价格、评分、商家与库存
  ↓
进入具体商品页面
  ↓
检查规格与目标商品是否一致
  ↓
加入购物车
  ↓
核对购物车状态
  ↓
返回结果,不自动推导为“已完成付款”

关键风险: 加购物车不等于下单。下单和付款都涉及更强的风险边界;支付信息、账户权限及网站服务条款也需要单独考虑。

案例 C:每天定时检查“是否下雨”

用户任务:“每天早上八点查一下天气,下雨才告诉我。”

可能执行过程:

解析重复计划 + 条件
  ↓
TriggerDefinition 写入本地计划
  ↓
系统调度到时唤醒
  ↓
只读检查天气信息
  ↓
ConditionOutcome 判定条件
  ↓
为真:通知并写回原对话
为假:不触发执行动作

这里需要考虑系统精确闹钟权限、设备休眠、时区变化、任务失败及条件结果去重。所谓“支持定时任务”不等于在每台 Android 设备上都有严格的秒级触发保证。


十七、如何正确评测 Mika:不要编造“性能提升多少倍”

技术博客最容易出现的问题之一,是把演示时间、单个截图或作者自己声称的效果直接写成普遍性能结论。

Mika 仓库提供 eval/tasks/ 真机评测任务,覆盖读取、写入、网页、障碍、不可逆动作、记忆和技能等类别,并区分调优池与保留池。这说明项目在工程上重视可重复评测,但仓库存在测试用例,并不自动等于本次已经取得真实成功率数据。

在这里插入图片描述

图 15|如何设计可复验的 Agent 评测。 对照任务集合、执行日志、自动规则和人工复核,而不是只看模型最终说了什么。

17.1 核心指标应该如何定义

指标建议定义常见误区
任务成功率成功且结果被证据验证的任务 / 有效测试任务模型口头声称完成也计入成功
错误动作率不符合用户目标或当前界面条件的已执行动作 / 总动作只记录“工具返回 OK”
人工接管率需要用户接管的任务占比把正常登录接管都写成失败
首次反馈延迟从任务开始到实际进展首次可见只用网络首 Token 时间代替
模型往返次数主循环调用模型的有效轮次混淆旁路识别和主模型调用
平均 Token/任务对主调用、旁路调用及缓存统一计费按预估 Token 假装服务商账单
端到端耗时用户发起到任务终态的墙钟时间把等待用户的时间排除却不说明
不可逆误操作数测试期间真实发生的错误发送、删除、下单仅看有无提示窗口

17.2 推荐的对照实验

固定同一台设备、系统版本、App 版本、网络条件和模型配置,比较:

  • 基线 A:普通单步 Agent,频繁调模型。
  • 优化 B:允许 do_steps 进行批量执行。
  • 优化 C:启用按需工具组和上下文压缩。
  • 优化 D:对符合条件的任务启用经验路线回放。

每个任务至少记录:模型调用数、输入/输出/缓存 Token、首次反馈、全程耗时、工具错误、人工接管、最终结果证据。只比较耗时不比较准确率和副作用,是不完整的性能评测。

17.3 一个可直接复用的测试记录模板

{
  "device": "需填写真实机型",
  "android_api": 34,
  "app_version": "记录实际 APK 版本",
  "model": "记录真实模型 ID",
  "task_id": "eval-example-001",
  "category": "read",
  "verified_success": null,
  "duration_ms": null,
  "first_feedback_ms": null,
  "model_round_trips": null,
  "input_tokens": null,
  "cached_input_tokens": null,
  "output_tokens": null,
  "wrong_actions": null,
  "human_takeover": null,
  "notes": "以上值是测试模板字段,非真实测得结果"
}

保留 null 的原因,是这些数据必须来自实际执行,而不是为文章凑数字。评测资料应记录调优集 / 保留集的划分,避免根据保留集调 Prompt 后再拿同一批题宣称泛化提升。

17.4 Mika 与其他 Android 自动化方案如何比较

维度固定 ADB/UI 脚本Accessibility 自动化Mika 类 Agent
适用目标重复且流程固定结构化 UI 固定流程自然语言、多步骤任务
应用 API 依赖通常无需通常无需通常无需,仍受系统与 App 约束
动态页面适应依赖脚本逻辑依赖状态机依赖观测、模型与守卫
可复现性流程固定时较高规则完整时较高模型输出存在非确定性
资源成本通常较低通常较低模型调用与感知有成本
失败形式坐标与页面漂移控件树缺失、状态变化识别误差、模型误判、执行错误
风险控制开发者自行实现开发者自行实现依赖执行入口和具体审批策略

这是对架构特性的定性分析,不是实测排名;传统自动化在高度稳定、重复量大的流程里仍可能更简单、更可靠、更便宜。


十八、安全、局限与生产化改造建议

18.1 不能把“没有 Mika 云后台”理解成“所有内容都不出手机”

Mika 默认不要求使用统一的 Mika 服务器,对话、记忆和运行记录主要保留在设备上。但是,如果配置云模型,用户任务和相关屏幕内容会发送到对应模型服务。使用外部网页时,访问行为也当然会到达目标网站;使用自部署远程网关时,还需要考虑额外的网络与认证边界。

18.2 真实用户数据需要更严格的审批策略

项目现有的询问模式主要把“花钱”作为重点确认边界,不适合作为企业系统所有高风险变更的通用准入方案。

如果将它扩展成企业移动 Agent,建议引入更细粒度的策略,例如:

Read  :读取资料、搜索、查询
Write :更新本地草稿或可回退数据
External:向第三方发送或提交
Critical:付款、删除真实数据、变更安全设置

上面是额外建议,不是声称 Mika 已内置这四级审批。对于 External 与 Critical,应根据业务建立用户确认、操作摘要和审计机制;默认不让无人值守任务执行涉及真实资金或数据不可逆修改的操作。

18.3 Prompt Injection 不是仅靠一个标签就能彻底解决

网页、群聊、文件和第三方内容对 Agent 来说都属于不可信输入。Mika 在观察构造中有标记不可信内容的机制,这是正确方向,但仍需防止模型把页面里出现的“请忽略之前指令”当作用户指令。

一个可行的工程原则是:页面内容只能提供任务数据,不能提升权限,也不能自行授权写工具。

18.4 当前仓库有哪些明确的已知限制

根据 docs/dev/roadmap.md,至少应认真看待:

  • 部分最终回答核查能力在开发阶段被关闭。
  • 真机兼容性、系统辅助进程恢复及长时间运行仍需继续验证。
  • 中断的 UI 任务不从上一步无条件续跑,避免拿失效页面状态操作。
  • 网页只读脚本的某些秘密输入框、iframe 与 DOM 变化场景仍有待改进。
  • 剪贴板输入可能出现系统提示并短暂保留敏感文字。
  • 网页任务不一定能够像原生 App 任务一样安全回放历史路线。

能够把局限写在路线图里,是工程透明度的体现;但它也意味着读者不应把演示版当作已经完成安全审计的关键生产系统。

18.5 如果我要二次开发,优先做哪几件事

从架构价值与风险收益来看,建议优先顺序是:

第一:建立真机兼容性矩阵。 至少覆盖 Android 12、13、14、15 及代表性的主流 ROM,记录虚拟屏创建、输入焦点、后台保活、OCR、转屏和接管。没有这个数据,模型优化往往无法解决底层失败。

第二:完善结果验证与副作用控制。 在高价值动作后收集可核对证据,对不可逆操作执行严格审批,对不确定结果采用请求编号和查询,而不是直接重试。

第三:建立持续回归评测。 将任务集合、主流 App 版本、设备、模型与真实 Token 成本一起记录。只有完成率和错误动作率不倒退时,才允许为了速度引入更激进的批量执行和经验回放。

第四:减少隐私暴露面。 对屏幕文本进行最小必要采集、对日志脱敏、对外部模型输入做范围控制;敏感账号任务尽量使用测试数据和低权限账户。

第五:整理二次开发入口。 让能力定义、风险级别、测试用例、文档示例绑定在同一套可验证契约中,减少未来新增工具破坏统一执行链路的概率。


十九、总结:Mika 代表的是一类完整的 Mobile Agent 工程

我认为 Mika 真正有价值的,不是“又一个能接多个 LLM 的手机 App”,而是它给出了一个较完整的实现方向:

  • Android 虚拟显示解决“AI 在哪里操作”,尽量不侵占用户主屏。
  • Accessibility / OCR / 视觉模型解决“AI 如何观察当前页面”。
  • AgentLoop 与工具系统解决“AI 如何持续规划和调用能力”。
  • ActionExecutor解决“怎样把模型提议变成受约束的真实动作”。
  • TaskCenter / RunInbox / Outbox解决“任务如何排队、插话、停止与交付”。
  • Web、沙箱、MCP让手机不只是一个独立应用,而能成为其他 Agent 的执行节点。
  • 评测与运行记录为观察真实性能和长期可靠性提供基础,而不是只依赖成功演示。

从软件架构角度看,这类产品的关键转折,是让“模型输出”进入一个具有状态、权限、资源所有权、失败语义和可观测性的运行时系统。这恰恰是大部分简单 Agent Demo 欠缺的地方。

但也要理性认识它的现状:后台虚拟屏有系统兼容性边界;多模型和多能力系统仍有成本;审批策略并不覆盖所有高风险动作;真实可用性需要跨机型、跨 App 版本持续评测。

对 Android、Kotlin、移动自动化、Agent 框架、MCP 与端侧应用开发者来说,Mika 非常值得拿来做源码学习与二次实验。建议先从 AgentLoop.kt、ActionExecutor.kt、AgentDisplay.kt 和 TaskRegistry.kt 四个文件开始阅读。


参考链接与配图版权说明

项目与入门

  • 仓库首页:https://github.com/wangzan101/Mika
  • Releases:https://github.com/wangzan101/Mika/releases/latest
  • 中文 README:https://github.com/wangzan101/Mika/blob/main/docs/readme/README.zh-CN.md
  • 模型配置:https://github.com/wangzan101/Mika/blob/main/docs/models.md
  • 手机控制向导:https://github.com/wangzan101/Mika/blob/main/docs/setup-guide.md

架构与源码

  • 整体架构:https://github.com/wangzan101/Mika/blob/main/docs/dev/architecture.md
  • 迭代路线与已知问题:https://github.com/wangzan101/Mika/blob/main/docs/dev/roadmap.md
  • AgentLoop:https://github.com/wangzan101/Mika/blob/main/core/src/main/kotlin/com/wangzan101/mika/core/agent/AgentLoop.kt
  • ActionExecutor:https://github.com/wangzan101/Mika/blob/main/core/src/main/kotlin/com/wangzan101/mika/core/action/ActionExecutor.kt
  • AgentDisplay:https://github.com/wangzan101/Mika/blob/main/app/src/main/java/com/wangzan101/mika/cap/phone/display/AgentDisplay.kt
  • RunInbox:https://github.com/wangzan101/Mika/blob/main/core/src/main/kotlin/com/wangzan101/mika/core/run/RunInbox.kt
  • BatchRunner:https://github.com/wangzan101/Mika/blob/main/core/src/main/kotlin/com/wangzan101/mika/core/agent/BatchRunner.kt
  • ContextCompressor:https://github.com/wangzan101/Mika/blob/main/core/src/main/kotlin/com/wangzan101/mika/core/agent/ContextCompressor.kt
  • 任务评测:https://github.com/wangzan101/Mika/blob/main/eval/README.md
  • 电脑端 CLI / MCP:https://github.com/wangzan101/Mika/blob/main/docs/computer.md
Logo

更多推荐