最近 Android 官方在 AI 这块的动作说实话有点超出预期。AppFunctions、官方 MCP 支持、再加上系统级 OpenClaw 雏形这套说法本质上是在讲同一件事AI 不再满足于在聊天窗口里跟你一问一答而是要直接伸手打开你手机里的每一个 App去调用里面的具体功能。这件事对整个移动开发的影响可能比当年深链Deep Link的出现还要大。这篇文章不蹭热度只讲干货。我会把这次官方动作背后的技术逻辑拆开讲清楚 AppFunctions 和 MCP 到底是什么关系所谓的“系统级 OpenClaw 雏形”到底长什么样然后给出一套可以直接上手的 Android 接入方案最后把我实测时踩过的一些坑也一并列出。文章目标读者是移动端开发者、AI 应用开发者以及对系统智能体感兴趣的进阶玩家。1. 这次官宣背后到底在解决什么问题1.1 传统 App 与 AI 之间的“语言鸿沟”先说一个现实问题App 里的功能AI 一直很难直接调用。过去想让大模型帮用户发一条笔记、建一个日程通常只有两条路一条是做 App 内的私有接口也就是你在 App 里内置自己的 AI 助手通过写死的代码去调内部功能。这条路的问题是封闭只有你自己的 App 能用跨 App 根本走不通。另一条是用无障碍服务模拟点击让 AI 像真人一样去操作界面。这个方案看起来很聪明实际上非常脆界面布局改一个 id脚本立刻废掉遇到弹窗、加载动画、折叠菜单定位就乱了。而且无障碍权限属于敏感权限用户侧也有很多顾虑。这两条路都绕开了问题的本质AI 和 App 之间没有一种双方都认可的“能力描述语言”。App 不知道 AI 需要什么AI 也不知道 App 能干什么。最终结果就是AI 只能当一个“嘴强王者”真正动手的事还得靠人。1.2 为什么是 MCP 而不是各做各的MCP 的全称是 Model Context Protocol模型上下文协议。这名字听起来抽象实际理解起来很简单它是给 AI 和外部工具之间制定的一套标准化通话协议。一次请求、一个返回工具是什么、参数是什么、结果是什么大家按同一个模板说话。我习惯把 MCP 类比成 AI 世界的 USB-C 接口。以前每个设备都有自己的充电线现在大家统一用一口插哪都能充。MCP 做的事情就是把这个“统一接口”下沉到 Android 系统层让任意 AI 助手都能通过同一套协议去调用任意 App 暴露出来的功能。Android 官方这次选择 MCP 而不是自己另搞一套协议原因很清楚第一MCP 已经有很强的社区生态大量模型客户端原生支持这套协议接入成本低。第二MCP 采用 JSON-RPC 2.0 作为通信基础语言无关、实现简单移动端跑起来毫无负担。第三也是最重要的一点MCP 天然区分了“工具声明”和“工具调用”两个阶段。AI 可以先问“你有什么工具”拿到工具清单后再按需调用这正好契合大模型 function calling 的交互模式。1.3 系统级 OpenClaw 雏形到底指什么“OpenClaw 雏形”这个说法圈内讨论很多。我理解它指的是一种系统级 AI 代理的雏形名字里的 Claw爪子很形象AI 像一只手直接伸进系统抓取各个 App 的能力。它不再是某一个 App 里的内置助手而是活在系统层面的调度者。在传统 Android 架构里系统服务和 App 之间存在明确的边界App 只能通过 Intent、ContentProvider 等固定通道对外暴露能力。这套机制虽然安全但无法满足 AI 动态探索和调用能力的需求。Android 官方现在做的相当于在系统层放了一张“能力总目录”每个 App 可以把自己的功能登记上去AI 代理通过这张目录发现能力、调用能力、拿到结果。系统不再只是一个被动执行指令的操作系统而是一个可以主动调度资源的智能体平台。说它是“雏形”是因为目前能看到的还只是基础设施层跨 App 的复杂任务编排、多代理协作这些更上层的形态还没有完全展开。但地基已经打好了后续生长空间非常大。2. AppFunctions 核心机制拆解AI 如何看懂你的 App2.1 声明式注册把功能写成一张函数清单AppFunctions 这套机制核心设计思想用一句话总结让 App 用结构化的方式把自己的功能“说”给 AI 听。传统做法下开发者暴露一个功能需要写很多意图处理和路由逻辑AI 并不知道这个功能存在。AppFunctions 改变了这个思路你只需要在配置里声明一个函数清单告诉系统“我的 App 能做这些事”AI 就能按图索骥。一个典型的功能声明大致是这样一个结构{ functions: [ { name: create_note, description: 新建一条文本笔记并返回笔记 ID, parameters: { type: object, properties: { title: { type: string, description: 笔记标题例如购物清单 }, content: { type: string, description: 笔记正文内容 } }, required: [title, content] } } ] }这份 JSON 在 AI 眼里的意义分三层第一层是“名字”告诉 AI 这个功能叫什么对应代码里的哪个入口。第二层是“描述”用自然语言说明这个功能在什么场景下用、有哪些限制。写清楚描述非常关键因为大模型靠自然语言理解来选择工具描述写得模糊模型就会瞎猜。第三层是“参数结构”用 JSON Schema 描述每个参数的名称、类型、是否必填。这层相当于给 AI 提供了填表格式的操作说明书。你可能会问这套声明机制和之前 Android 的 App Actions 有什么区别。区别主要在于覆盖范围。App Actions 走的是 BIIBuilt-in Intent模板系统规定一批固定动作开发者只能在这些预设里挑模型发挥空间有限。AppFunctions 则是把能力的定义权交还给开发者你想暴露什么就暴露什么相当于从“系统出题”变成了“开发者自己出题”AI 按题作答。2.2 动态接口调用的完整链条光有功能声明还不够AI 真正发起调用时还需要一条动态链路把请求送到你的代码里。我拆一下这条链路AI 向系统层发出工具调用请求携带函数名和参数。系统层根据函数名匹配到对应 App 的注册入口启动并拉起目标组件。你的 App 收到请求后进行参数校验、业务逻辑处理返回结果。系统层把结果包装成标准响应回传给 AI。这里面有个容易被忽略的设计参数校验不能依赖 AI。大模型生成参数时存在幻觉可能给你传一个根本不在枚举里的值也可能把必填字段漏掉。所以 App 侧收到参数后必须用本地逻辑再校验一遍校验失败时返回清晰的错误原因而不是直接 crash。2.3 本地执行优先隐私与低延迟兼顾Android 官方这套设计里一个很明显的特点是尽量在设备端完成执行。AI 在云端理解意图但在本地调用 App 能力、读取结果。这就带来两个直接收益一个是隐私。用户的数据不需要全部上传到云端App 内部的敏感信息只留在本机。用户授权时只需要为某一次具体操作授权而不是把整个 App 的所有权限都交出去。另一个是低延迟。本地调用比云端来回传递快得多尤其在网络不稳定的场景下本地函数执行基本是无感的。不过这也意味着开发者需要更重视权限交互设计AI 要调用一个功能时系统如何提示用户用户如何知道此刻是 AI 在操作而不是自己点按这些提示如果做得不清楚用户授权时会不安。合理的方案是参考无障碍服务的授权弹窗逻辑尽量把“每个功能单独授权”做成默认原则同时保留用户主动撤销授权的入口。3. 从零搭建一个可被 AI 调用的 Android 功能模块3.1 环境准备与最小依赖在这一节我说说实际动手时需要准备什么。工具方面不需要额外的重型组件一台装有最新稳定版 Android Studio 的电脑一个 API 级别不要太低的模拟器或真机即可。我在测试时使用的主流大模型 API 来模拟云端 AI同时也用本地小模型跑过一版整体流程是兼容的。依赖方面AppFunctions 的功能入口是按照官方 SDK 接入这里不展开具体包名以免后续版本更新产生误导。MCP Server 这边理论上不需要额外引第三方库因为协议本身是 JSON-RPC用标准库也能写出来只是代码量会多一些。如果你想省事也可以引入社区社区维护的基础通信库但核心逻辑建议自己控制后面排障会轻松很多。3.2 定义一个笔记 App 的对外能力为了演示完整流程我用一个极简的笔记 App 做样例。这个 App 只有两个核心功能创建笔记、查询笔记列表。整体的功能声明如下{ functions: [ { name: create_note, description: 新建一条笔记标题必填正文可为空, parameters: { type: object, properties: { title: { type: string, description: 笔记标题不超过 50 字 }, content: { type: string, description: 笔记正文支持纯文本 } }, required: [title] } }, { name: query_notes, description: 返回当前全部笔记的列表每项包含笔记 ID、标题、创建时间, parameters: { type: object, properties: { limit: { type: integer, description: 返回条数上限默认 10 } } } } ] }注意这里给参数都写了明确的边界说明标题不超过 50 字、content 可为空、limit 有默认值。这些说明不是给开发者看的是给模型看的决定了模型填参数时的行为边界。3.3 用 Kotlin 写一个简单的功能分发器有了声明清单下一步是在 App 内部写一个功能分发器负责接收请求、调用对应方法、返回结果。这里没必要引入花哨的框架一个简单的 when 分支就够了class NoteFunctionDispatcher { fun handleAction(action: String, args: JsonObject): JsonObject { return try { when (action) { create_note - createNote(args) query_notes - queryNotes(args) else - errorResult(unknown_function, 未知函数: $action) } } catch (e: Exception) { errorResult(internal_error, e.message ?: 执行失败) } } private fun createNote(args: JsonObject): JsonObject { val title args.string(title) ?: return errorResult(invalid_params, 缺少 title 参数) if (title.length 50) { return errorResult(invalid_params, title 不能超过 50 字) } val content args.string(content).orEmpty() val id NoteRepository.add(title, content) return successResult(note_id to id, created to true) } private fun queryNotes(args: JsonObject): JsonObject { val limit args.int(limit) ?: 10 val notes NoteRepository.list(limit) return successResult(notes to notes) } }几个细节提醒一下每个分支入口都做了 try-catch函数执行过程中出现的任何异常都要转成标准错误返回不能让异常直接冒泡到系统层否则 AI 拿到一堆看不懂的堆栈信息什么都做不了。参数从 JSON 取出时类型转换要显式处理不能假设模型传的一定是整数或字符串。实际测试中我遇到过布尔值变成字符串的情况健壮性全靠这里兜底。成功和失败都返回标准化结构失败信息要带机器可读的错误码方便上层做策略判断。3.4 对接 MCP用标准协议包一层功能分发器是 App 的内部实现但 AI 不认识它。为了让 AI 能通过标准方式调用还需要把它包成 MCP Server。MCP 的核心交互是 JSON-RPC 2.0主要涉及两个方法tools/list 用来列出所有工具tools/call 用来调用具体工具。一个最小可用的 MCP Server 逻辑如下class NoteMcpServer(private val dispatcher: NoteFunctionDispatcher) { fun handleRequest(request: JsonObject): JsonObject { val method request.string(method) ?: return errorOf(-32601, method not found) val id request[id] ?: JsonNull.INSTANCE return when (method) { tools/list - JsonObject().apply { add(jsonrpc, JsonPrimitive(2.0)) add(id, id) add(result, toolsListResult()) } tools/call - { val params request[params].asJsonObject val name params.string(name) ?: val args params[arguments]?.asJsonObject ?: JsonObject() JsonObject().apply { add(jsonrpc, JsonPrimitive(2.0)) add(id, id) add(result, callToolResult(name, args)) } } else - errorOf(-32601, method not found).also { it.add(id, id) } } } private fun callToolResult(name: String, args: JsonObject): JsonObject { return when (name) { create_note, query_notes - JsonObject().apply { add(content, JsonArray().apply { add(JsonObject().apply { add(type, JsonPrimitive(text)) add(text, dispatcher.handleAction(name, args).toString()) }) }) } else - JsonObject().apply { add(isError, JsonPrimitive(true)) add(content, JsonArray().apply { add(JsonObject().apply { add(type, JsonPrimitive(text)) add(text, unknown tool: $name) }) }) } } } }这段代码的核心意图是把业务分发器和协议层彻底解耦。协议层只关心 JSON-RPC 的请求响应格式业务层只关心笔记功能两者通过标准结构衔接。以后哪怕协议从 MCP 升级成别的形态业务逻辑也不用动。服务跑起来后通过 adb 端口转发电脑端的 MCP 客户端就能连上设备上的服务进行验证。这一步能确认协议交互是否正确也能观察真实请求长什么样。4. 避坑指南我实测中遇到的 5 个常见问题4.1 权限声明遗漏AI 什么都看不到第一次接入时最容易踩的坑是功能声明写好了AI 却完全看不到任何工具。原因通常不在协议层而在权限配置向外暴露功能这一行为本身需要在系统层面有过明确的声明授权。漏了这一步系统会直接拒绝你的注册请求而且不报明显错误只会在日志里留一条安静的信息。排查方法很简单先用系统自带的工具查询能力列表看看你自己的函数有没有出现在里面。如果没有第一检查声明配置文件是否合入构建产物第二检查权限声明是否到位第三再检查组件是否配置了正确的导出规则。不要一上来就怀疑协议代码。4.2 动态参数校验不能省模型比你想象中笨这个坑我反复遇到了好几次。大模型在生成函数参数时实际做到“完美遵守 schema”的概率远没有想象中高。比如我定义了一个 status 参数枚举值是todo、done模型却在参数里传了completed。字符串大小写不一致都算轻的类型传错也偶尔出现。处理方式就是我在分发器里做的那样每个参数都要在本地重新校验类型、枚举、长度、边界都要管。校验失败时返回错误码让模型知晓并调整而不是直接尝试执行业务逻辑。别嫌代码啰嗦这是整套系统稳定性的底线。4.3 函数描述写得太空模型根本不会调用我觉得这是最难从文档里学到的一点。数据分析岗位看函数描述的其实是模型而不是人。开发者写的“新建一条笔记”五个字人看得懂模型却缺少触发场景的判断依据。对比一下这两种描述描述 A新建一条笔记描述 B当用户表达“记录一下”“帮我存个事”“新建备忘”等意图时使用。标题用于快速索引正文用于细节存储。如果用户只说了“帮我记一下买牛奶”title 填“购物提醒”content 填“买牛奶”。同样的函数第二种描述能让模型在更多场景下正确触发调用。描述里要包含触发意图、参数取值建议、边界情况处理。这一点值得花时间反复打磨它会直接决定 AI 调用你 App 功能的频次和成功率。4.4 线程模型与超时控制MCP Server 在 Android 上跑起来后要特别注意 thread 和 time 的问题。处理函数请求时如果直接在主线程执行耗时操作比如读数据库、访问网络很可能会卡住严重时引发响应超时。建议把耗时的业务逻辑放到独立协程或线程池中执行再通过回调把结果返回到协议层。同时要给工具调用设置合理超时上限我的经验是普通本地操作给 5 秒涉及网络的操作给 15 秒。超时要触发兜底返回让 AI 收到明确超时信号而不是一直等着。4.5 本地小模型与云端大模型的差异如果你的目标是接入本地小模型就要有预期小模型对复杂工具调用的理解能力明显弱一些。我实测时发现本地模型经常在工具选择阶段犹豫甚至同时传多个工具名称导致系统匹配失败。应对方案有两个一是把函数拆细每个函数只做一件极简单的事参数控制在一到两个。不要试图在一开始就做一个全功能大对象模型消化不了。二是做好工具描述精简本地模型对长文本描述的注意力不如云端模型。把描述压缩成短句关键信息前置能提高不少调用成功率。顺带说一下云端大模型对工具描述的响应质量更高所以如果只是做技术验证优先用云端大模型能少踩不少坑。5. 这套架构的后续想象空间5.1 从“单个 App 被调用”到“跨 App 任务编排”AppFunctions 和 MCP 的组合往前再走一步就是跨 App 任务编排。现在的 AI 调用是一个函数对一个动作未来一定会出现这样的情况用户说“帮我查一下明天的会议然后订一间附近的餐厅”系统拆解任务后分别调用日历 App 的查询会议功能、地图 App 的查找餐厅功能、订座 App 的下单功能最后汇总结果给用户。这个过程每个 App 不需要知道其他 App 的存在它们只需要把自己的能力通过 AppFunctions 暴露出来。真正干活的是系统级代理也就是前面说的 OpenClaw 雏形。这对开发者的启发是你现在需要开始考虑你的 App 哪些功能值得暴露给 AI。因为未来用户可能不再点开你的 App而是直接通过系统助手调用你的功能。如果你的功能不在系统能力列表里你就等于缺席了未来的人机交互入口。5.2 开发者现在可以做的准备我的建议是不要等官方 API 全面稳定了再行动现在就可以开始准备一是梳理核心功能。找一张纸把你 App 里用户最常用的 5 个功能列出来思考它们应该以什么名字、什么参数暴露给外部系统。二是写好函数描述。按照我前面说的触发意图加参数建议的方式把描述文案打磨出来。这是纯文案工作不需要等 API。三是搭一个最小可用的 MCP 服务做验证。不需要完整功能先让 AI 通过协议成功调用你的一个函数就行。这个过程能帮你发现很多架构和设计上的问题比以后在生产环境里碰壁强得多。四是想清楚隐私边界。哪些功能可以被 AI 免授权调用哪些必须二次授权授权文案怎么写用户才能理解这些产品层面的事要提前规划。我自己的体会是Android 这波动作最大的意义不是多了一个接口而是把“AI 调用 App”从一次性的实验变成了体系化的系统能力。AppFunctions 是入口MCP 是语言系统级代理是最终形态。三件事放在一起看方向已经很清晰了。最后再分享一个小技巧给函数加日志。不要只在业务方法里打日志还要在协议层记录每一次工具请求的参数、耗时、成功率。当你的 App 接入 AI 后这些日志就是你和模型协作的“会话历史”调问题全靠它们。我第一次联调时就是靠一层层的调用日志才定位到某个参数描述歧义导致模型反复填错值。日志整洁有序调试体验完全不一样。
阅读完成 · 觉得有帮助?