Android 16 App Functions 深度解析:从语义索引到意图路由的端侧 AI 实践
以下是润色后的文章正文:
前几天产品丢过来一个需求:用户在相册里选中一张合照,系统自动判断”这张图适合发给谁”。不是弹出 share sheet 让用户选,而是直接理解语义——「把合照发给照片里的人」。
Android 的 Intent 系统做了十几年,能力边界很清楚:App 声明自己能处理的 Action 类型,系统做字符串匹配后拉起。但只要需求触及”理解用户说什么”这一层,这套机制就不够用了。Android 16 的 App Functions 框架要解决的正是这个问题——用语义索引替代字符串匹配,把跨应用操作从”手动选择”推到”意图路由”。
语义索引:App 能力的向量化表达
传统 Intent Filter 的匹配逻辑是 action + data + category 三元组,本质是关键字过滤。App Functions 换了思路:App 用结构化 Schema 声明自己能执行的函数,系统对描述文本做语义编码,建立向量索引。
注册方式如下:
val function = FunctionDeclaration.Builder()
.setName("share_to_contact")
.setDescription("通过通讯工具将照片或文件分享给通讯录中的联系人")
.addParameter("recipient", Schema.String, "接收者姓名")
.addParameter("mediaUri", Schema.Uri, "待分享的媒体文件")
.setExecutionCallback { args, context ->
val recipient = args["recipient"] as String
val uri = (args["mediaUri"] as Uri)
shareToContact(recipient, listOf(uri))
}
.build()
appFunctionManager.registerFunction(function)
setDescription 里的自然语言描述是整个机制的核心。系统将这段文本送入端侧文本编码模型,生成语义向量存入本地索引。用户说出”把照片发给老王”时,系统同样将指令向量化,在索引中做最近邻检索。
这种方式比关键字匹配的召回率高得多。用户说”传给老王”、“转发给老王”、甚至”老王帮我看看这张图”,都能命中同一个 share_to_contact Function——语义向量捕捉的是意图,不是字面。
索引在 App 安装完成后异步构建,App 升级时触发增量更新,用户无感知。数据全程在本地处理,不依赖云端。
意图路由:不止匹配,还要编排
语义索引找到候选 Function 只是第一步。完整的意图路由需要解决三个环节:匹配、消歧、调度。
Android 16 用**函数执行分数(Execution Score)**做候选排序,综合以下维度:
- 语义相似度:用户指令与 Function 描述的向量余弦距离
- 调用历史:同一用户过去处理类似指令时倾向哪个 App
- 上下文权重:前台 App 的 Function 获得加权——用户正在用的 App 最可能相关,这符合直觉
- 参数完备性:指令中能提取出的参数是否覆盖 Function 的必需参数
打分后,系统选择 top-1 直接执行(需用户授权过的场景),或推送 top-3 让用户确认。
这套路由对 App 开发者完全透明——你不需要关心自己排第几、被谁挤掉。只管把 Function 注册进去,系统接管后续的匹配和调度。
端侧 AI Agent 的两阶段协同
语义索引用向量计算解决了粗筛,但真实场景里指令往往带歧义。比如「把昨天的周报发给老板」——「周报」是哪个文件?「老板」是谁?这类实体消歧不是向量检索能搞定的。
Android 16 采用两阶段流水线设计:
第一阶段(粗筛):从全量 Function 索引中检索 top-3 候选,纯向量计算,毫秒级完成。
第二阶段(精排 + 消歧):端侧模型(Gemini Nano 或用户部署的 LiteRT 模型)仅对 top-3 候选做深度推理,解析实体、补全参数:
val resolutionContext = IntentResolutionContext.Builder()
.setUserUtterance("把昨天的周报发给老板")
.setCandidateFunctions(top3Matches)
.build()
val resolved = intentResolver.resolve(resolutionContext)
// resolved.entities: {"周报" -> "Q4_周报_v3.docx", "老板" -> "张总"}
// resolved.bestMatch -> share_to_contact, recipient=张总
两阶段设计的价值在于控制推理成本。让 Agent 在全量几百个 Function 上跑推理,延迟会到秒级,用户没法接受。先粗筛缩到 3 个,Agent 的推理任务从 O(n) 降到 O(1)。
实测数据:第一阶段 5-15ms(向量检索),第二阶段 200-500ms(模型推理),整体端到端延迟在可接受范围。
工程落地的几个关键点
集成过程中踩过几个坑,提前规划能省不少时间:
描述文本的写法直接决定匹配精度。setDescription("发消息") 和 setDescription("通过即时通讯应用向指定联系人发送文本或图片消息,支持群聊和单聊") 的召回率能差 3-5 倍。描述要覆盖用户可能的表达方式,但别写成关键词堆砌——语义模型对自然语言敏感,堆砌反而稀释向量方向。
参数 Schema 要预留容错空间。Agent 的实体解析不是 100% 准确,偶尔会把”发三张照片”里的”三张”解析成人名。ExecutionCallback 里必须做类型校验和降级兜底。
Function 数量宁缺毋滥。实测一个 App 注册 5 个核心 Function 的匹配成功率,远超注册 30 个冷门 Function 的场景。索引中的噪声 Function 会拉低整体 Precision,建议只注册用户高频使用的操作。
调试阶段用 adb shell dumpsys app_functions 可以导出当前设备所有已注册 Function 及其语义向量状态,排查匹配问题时比翻文档高效得多。
App Functions 把 Android 的跨应用交互从「声明式匹配」推到了「语义理解」这一层。它不取代 Intent,而是在 Intent 之上叠加一层 AI 驱动的意图路由——面向的场景不是”打开哪个 App”,而是”理解用户想做什么,然后调度合适的 App 去做”。