前言
今日,Google 在 Android Developers 上正式发布了 AppFunctions。
即Android 版的 MCP。今天 Carson 来带大家把这个 API 彻底讲透!

一、AppFunctions 是什么?

AppFunctions = Android App 把自己的功能「标准化暴露」给 AI Agent 的一套官方协议。
它由三部分组成:
- 1:一组 Jetpack 注解 (
@AppFunction、@AppFunctionSerializable); - 2:一个 KSP 编译期处理器(把注解转成 XML schema);
- 3:一套 OS 级注册中心(Android 平台维护,Agent 通过它发现你的 App)。
对比之前的方案
过去几年,Android 上其实一直有人在尝试解决「AI 怎么调 App」的问题:
- Intent / Deep Link:粗粒度、参数少、需要 App 前台或复杂配置;
- App Actions:Assistant 时代产物,绑定预定义意图,扩展性差;
- Slice / Widgets:给用户看的 UI 片段,不是给 AI 用的调用接口;
- 无障碍模拟点击:脏、慢、易碎,不讨论。
而 2024--2025 年整个 LLM 生态搞出来的通用解法叫 MCP(Model Context Protocol) ------一个让 LLM 发现工具、调用工具的开放协议。问题是:MCP 是为云端设计的,跑在服务器上,通过网络暴露 tools 给云端 LLM。
Google 这次做的事,就是把 MCP 的思路搬到了手机上。
这才是 AppFunctions 的历史地位------Android 端上第一个专为 AI Agent 设计的调用协议。
二、为什么说它是「本地版 MCP」?

官方文档:
"AppFunctions is an Android platform API with an accompanying Jetpack library to simplify Android MCP integration."
翻译过来就是------AppFunctions ≈ Android 设备本地的 MCP 服务器。
云端 MCP vs 本地 AppFunctions
| 维度 | 标准 MCP(云端) | AppFunctions(本地) |
|---|---|---|
| 执行位置 | 服务器 | 设备本地 |
| 网络依赖 | 必须联网 | 不需要 |
| 延迟 | 有网络往返 | 无往返 |
| 状态访问 | 需在 App 外维护 | 直接复用 App 现有状态 |
| 隐私 | 数据出端 | 数据不出端 |
| 集成方式 | 外部服务 | OS 级钩子 |
最容易被忽视的一条:直接复用 App 现有状态
这一条被严重低估了。
想象一下你在写一个记账 App。
云端 MCP 想让 LLM 帮用户记账,你得先在服务器上重建一整套业务逻辑------数据库、账户体系、鉴权、同步......工作量堪比再做一个后端。
而 AppFunctions 里,Gemini 直接调用你 App 里已经存在的 addExpense()。Room 数据库、DI 容器、Repository 层------你写好的东西 Agent 全都能用。
云端 MCP 是"从零搭一个新后端",AppFunctions 是"给现有 App 加个门"。
三、架构拆解:三个角色,一条链路

- MCP Server = 你的 App:声明可暴露的功能;
- 注册中心 = Android 平台:维护一张全设备的 AppFunctions 注册表;
- MCP Client = 系统 Agent(如 Gemini):拥有系统级特权后访问注册中心,发现并调用工具。
一次典型调用的完整链路
用户说了一句"给巴黎旅行加一笔 5 美元咖啡",背后发生了什么?
go
用户语音:"给巴黎旅行加一笔 5 美元咖啡"
│
▼
Gemini(Agent)解析意图,判断可用 AppFunction
│
▼
查询 Android 平台的 AppFunction 元数据(含 KDoc 描述)
│
▼
Gemini 选中 `addExpense`,把参数解析出来
│ {tripName: "巴黎", amount: 5, currency: "USD", category: "食品"}
▼
系统在后台调用你的 App 的 `addExpense()`
│
▼
函数返回结果 → Agent 汇总 → 用户看到确认
两个反直觉的技术细节
看到这里你可能觉得"就这?跟写个普通函数没啥区别"。但有两个坑,官方在博客里专门点名了。
① KDoc 是「AI 提示词」
你写在函数上的 KDoc 注释,会被 KSP 编译进 XML schema,最终变成 LLM 看到的 tool description。
kotlin
/**
* 添加一笔旅行消费记录。
*
* @param tripId 旅行的唯一 ID,必填。
* @param amount 金额,单位由 currency 决定。
* @param currency ISO 4217 货币代码,如 "USD"、"CNY"。
* @param category 消费类别,如 "食品"、"交通"、"住宿",可选。
*/
@AppFunction(isDescribedByKDoc = true)
suspend fun addExpense(
tripId: String,
amount: Double,
currency: String,
category: String? = null,
): Expense = TODO()
KDoc 写得越好,Gemini 调得越准。
这跟传统 Android 开发的注释文化完全不一样------过去我们写 KDoc 是给同事看的,现在是给 AI 看的。
你注释写得敷衍,Agent 就有可能把 currency 填成 "美元" 而不是 "USD",最后你的 addExpense 直接报参数错误。
② AppFunction 默认跑在 UI 线程
这是官方博客里明确点出的坑。所有 AppFunction 默认在 UI 线程执行,涉及 IO 或数据库操作必须显式切换:
kotlin
@AppFunction(isDescribedByKDoc = true)
suspend fun searchTrip(...): List<TripSerializable> {
return withContext(Dispatchers.IO) {
// 数据库查询、网络请求等
}
}
不切线程,轻则 StrictMode 报警,重则 ANR。
记住这两条,你就避开了 80% 的接入坑。
四、实战接入:把 App 变成 Gemini 的工具
以官方 Jetpacker(旅行 App)的「搜索行程」功能为例,实际接入就三步。
Step 1:引依赖
kotlin
// build.gradle.kts
implementation("androidx.appfunctions:appfunctions:1.0.0-alpha10")
ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha10")
注意:目前是 alpha10,API 表面可能还会调整,别在生产环境提前 all-in。
Step 2:定义可序列化的数据模型
Gemini 调你的函数、拿你的返回值,数据得能序列化。用 @AppFunctionSerializable 标注 data class:
kotlin
@AppFunctionSerializable(isDescribedByKDoc = true)
data class TripSerializable(
/** The trip's unique identifier. */
val id: String,
/** The trip's title. */
val title: String,
/** The trip's destination location. */
val location: String,
/** The trip's start date in milliseconds. */
val startDate: Long,
/** The trip's end date in milliseconds. */
val endDate: Long,
/** A list of participants. */
val participants: List<String>,
)
每个字段都要写 KDoc,因为这些注释也会被写进 schema 给 Gemini 看。
Step 3:暴露函数 + 注册 Service
先用 @AppFunction 标记要暴露的函数:
kotlin
/**
* Looks for trips based on optional filters like id, title, location, and dates.
*
* @param id The unique identifier of the trip.
* @param title The title or name of the trip.
* @param location The destination location.
* @param startDate The minimum start date in milliseconds.
* @param endDate The maximum end date in milliseconds.
* @return A list of trips matching the filters.
*/
@AppFunction(isDescribedByKDoc = true)
suspend fun searchTrip(
id: String? = null,
title: String? = null,
location: String? = null,
startDate: Long? = null,
endDate: Long? = null,
): List<TripSerializable> = withContext(Dispatchers.IO) {
tripDao.search(id, title, location, startDate, endDate)
.map { it.toSerializable() }
}
然后写一个 Service 入口,把这堆 AppFunction 挂上去:
kotlin
@RequiresApi(36)
@AndroidEntryPoint
@AppFunctionServiceEntryPoint(
serviceName = "JetPackerAppFunctionService",
appFunctionXmlFileName = "jetpacker_app_function_service"
)
abstract class BaseJetPackerAppFunctionService : AppFunctionService() {
@Inject internal lateinit var tripDao: TripDao
@Inject internal lateinit var expenseDao: ExpenseDao
// 其他依赖...
}
KSP 编译期会自动生成子类,你只需要在 AndroidManifest.xml 里注册元数据文件。
Hilt 天然就能用 ,因为 AppFunctionService 本质还是一个 Android Service。
验证:ADB 一把梭
到这一步,你的 App 就已经出现在系统的 AppFunctions 注册表里了。Android 17+ 上,直接用 ADB 命令查看:
bash
# 列出设备上所有已注册的 AppFunctions
adb shell cmd app_function list-app-functions
# 执行某个 AppFunction(JSON 传参)
adb shell cmd app_function execute-app-function
Google 还提供了一个 AppFunctions Testing Agent 图形化工具,可以直接模拟真实对话流程------比自己写测试快得多。
三步下来,你的 App 已经能被 Gemini「看见」了。
五、Jetpacker 官方样板:都做了什么?

Google 官方博客里,Ben Weiss 挑了三类**"语音比点击更快"**的功能做示范:
| 功能 | AppFunction | 用户可以怎么用 |
|---|---|---|
| 费用记录 | addExpense / getExpenses |
"给巴黎旅行加一笔 5 美元咖啡" |
| 行程管理 | getItinerary / addItineraryEvent |
"我在巴黎接下来要做什么?" |
| 免提笔记 | addVoiceNote |
走路时说一段感想,App 自动转写入库 |
选型逻辑:语音是否比点击更快
这里的选型逻辑非常重要 ------不是所有功能都值得做成 AppFunction。判断标准就一句话:语音是否比点击更快。
- ✅ 适合做:参数明确、状态简单、频繁使用(记账、快速搜索、加日程);
- ❌ 不适合:需要复杂 UI 决策(挑商品、编辑图片、多步表单)。
多步复杂流程 Google 留给了系列文章第五篇讲的 A2UI + ADK Agentic Workflow(应用内多智能体编排),是另一条线,Carson 下期再聊。
先放一张 Jetpacker 里的 Booking Assistant 截图感受下------这就是 Agentic Workflow 的典型场景:

记住这条选型铁律:一句话能干完的事才做 AppFunction,需要选择的事留给 Agentic Workflow。
六、你的 App 该不该接?三档选型

接入 AppFunctions 的开发者分成三档,可自行对号入座
L1 · 观望型:先接 3 个最高频功能
选出你 App 里用户使用频率最高的 3 个功能(通常是搜索、创建、快速切换)。
包装成 AppFunction,KDoc 写好,让 Gemini 能发现、能调用。零成本试水。
L2 · 认真型:把整个「数据操作层」暴露
把 Repository 层里所有 CRUD 方法都过一遍------查询、创建、更新、删除。
对 Agent 友好的 App,本质是把领域模型(Domain Model)以函数形式对外暴露。
用户跟 Gemini 说"把我上周去东京的行程分享给同事",如果你没暴露 getTrip、shareTrip,Gemini 就调不到。
L3 · 激进型:重构信息架构
如果你相信 Agent 是未来主要入口之一,那 App 的信息架构本身就要为 Agent 优化。
核心操作全部先以函数形式定义,UI 只是这些函数的一种展示形式。
这跟很多团队目前的「UI 驱动开发」顺序刚好相反。
大部分团队从 L1 起步就够了,L3 是给押注 AI Agent 的公司准备的。
七、最后

AppFunctions 不是 Android 加了一个 API,而是 Android 定义了 Agent 时代 App 的第一份「接口标准」:
- 对 Google:Gemini 的能力半径从"回答问题"扩展到"用你手机上的所有 App 帮你干活",这是 Assistant 从来没做成的事;
- 对 App 开发者 :多了一条新的用户触达路径------用户不再需要打开你的 App ,只要问 Gemini。但反过来看,没接入 AppFunctions 的 App,Gemini 根本看不见,语音场景里就用不到你;
- 对整个生态 :iOS 那边有 Apple Intelligence 的 App Intents(同类东西),两大平台都在往同一个方向走------从"人操作 App"到"AI 操作 App"。
过去 15 年的四次范式迁移
- 1:2010 年代初,Push 通知决定了 App 能否留住用户;
- 2:2010 年代中,Deep Link + 分享决定了 App 能否被生态流量分发;
- 3:2020 年代初,小组件 + Live Activity 决定了 App 在锁屏和主屏的存在感;
- 4:2020 年代中,AppFunctions / App Intents 决定了 App 能否在 AI Agent 生态里被「发现」和「调用」。
每一次都是「接入协议」的门槛,每一次都是「生态位竞争」的分水岭。
没接入协议的 App,正在被下一代交互范式抛下。
📎 参考资料
- AppFunctions 官方文档:developer.android.com/ai/appfunct...
- Build intelligent Android apps with AppFunctions(官方博客 Part 4):android-developers.googleblog.com/2026/07/bui...