十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android 16 AppFunctions 上手实战:把你的 App 变成设备端 MCP 服务器

Android 16 AppFunctions 上手实战:把你的 App 变成设备端 MCP 服务器 摘要MCP 火了大半年但大多数人做的都是服务端工具。Google 在 I/O 2026 放出的 AppFunctionsAndroid MCP把这条路搬到了设备上——你的 App 可以像 MCP server 一样向 Gemini 等智能体暴露函数而且全部本地执行、不走网络。本文用官方 API 从零实现一个笔记 App 的 AppFunction讲清楚它和标准 MCP 的架构差异、测试方法以及我踩过的 6 个坑。1. 背景与痛点如果你跟我一样这半年被 MCP 折腾得不轻应该能理解这种感觉服务端 MCP server 确实强但每次调工具都要走一遍网络往返而且它完全够不到你手机里的数据。我做过一个 MCP server 去查日历部署在云上手机端 agent 调它要经过「手机 → 云 → 日历 API → 云 → 手机」一整条链路。延迟能接受但有个本质问题它碰不到本机应用的状态。你的笔记、相册、任务清单全躺在 App 的沙盒里服务端 MCP 只能干瞪眼。Google 在 2026 年 5 月的 I/O 大会上给出了自己的答案AppFunctions官方别名就叫 Android MCP。核心思路一句话让 Android App 像一个「设备端 MCP server」把自己暴露成一个个可被智能体发现和调用的函数。这篇文章写给三类人已经玩过 MCP、想搞清楚「设备端」和「服务端」到底差在哪的开发者Android 开发者想提前给 App 接上 agent 生态Gemini 的私测预览 2026 年 5 月已经开放给可信测试者了好奇下一代人机交互长什么样的技术爱好者读完你会得到AppFunctions 的完整架构拆解、一份可跑通的笔记 App 示例代码、adb 实测命令以及 6 个真实的坑。先给结论这玩意儿现在还很不成熟但方向是对的。如果你正在做 Android App现在花一个周末摸一遍等生态成熟的时候你就已经在前排了。2. 技术原理AppFunctions 到底是什么2.1 一句话定位AppFunctions 是 Android 16API 36的平台 API 一套Jetpack 库androidx.appfunctions。它让你的 App 像 MCP server 一样向外提供「函数」调用方agent App、Gemini 这类助理可以发现并执行这些函数。关键机制提供方给函数方法加AppFunction注解注解处理器自动生成 schema 元数据注册进系统调用方持有EXECUTE_APP_FUNCTIONS权限注意这是个 protected 权限后面踩坑细说用AppFunctionManager发现和执行传输基于 AIDL 的 IPC进程隔离不是随便哪个 App 都能调你的函数flowchartLRsubgraph设备端A[用户自然语言指令]-- B[Gemini / Agent App]B-- C[AppFunctionManager]C-- D[系统 AppFunctions 注册表]D-- E[你的 Appbr/AppFunction 函数]E-- F[本地数据/服务]endG[云上 MCP Server]-.网络往返.-H[远程工具]B-.可选.-G2.2 和标准 MCP 的对比这是很多人第一眼会问的问题都是给 agent 提供工具AppFunctions 和 MCP 有什么区别对比维度标准 MCP ServerAndroid AppFunctions运行位置云端/服务器设备本地网络依赖每次调用都要网络往返无本地 IPC平台绑定平台无关Android 16 专属数据可达性只能访问服务端数据直接访问 App 本地状态安全模型服务端鉴权AIDL IPC EXECUTE_APP_FUNCTIONS权限开发者工作量搭服务、部署、维护加注解 一个 Service 入口类生态状态成熟工具海量实验性私测预览阶段官方 FAQ 里原话是「AppFunctions 是 Android 独有的内置操作系统级钩子可在本地执行标准 MCP 服务器依赖云执行和网络往返。」说人话就是MCP 解决的是「agent 怎么连服务器」AppFunctions 解决的是「agent 怎么用你手机里的 App」。还有个有趣的类比Chrome 有个 WebMCPEPP项目思路也是把浏览器能力暴露给 agent。Google 这边是铁了心要把「设备即工具」这条路铺开。目前最典型的落地案例是三星Galaxy S26 系列上Gemini 已经能通过 AppFunctions 调起 Samsung Gallery。用户直接说「帮我找我家猫的照片」Gemini 识别意图、触发对应的查询函数、把相册里的照片直接呈现在对话里全程不用离开 Gemini 应用还能把结果接着发消息给朋友。这个场景官方演示用的是语音输入多模态链路已经跑通了。日历、笔记、任务这三类应用的能力Gemini 在多家厂商的设备上也已经能用 AppFunctions 自动化了。也就是说这条路不是 Google 画饼头部厂商已经在真机上演过了。剩下的问题是生态什么时候铺开——而铺开之前就是开发者提前卡位的窗口。2.3 版本和依赖以我写这篇文章时的最新稳定 alpha 版本为准平台要求Android 16API 36或更高Jetpack 库androidx.appfunctions:appfunctions:1.0.0-alpha10编译器androidx.appfunctions:appfunctions-compiler:1.0.0-alpha10走 KSP可选androidx.appfunctions:appfunctions-service封装 Service 入口的脚手架注意这是 alpha 版本API 一直在变。官方甚至专门出了个 migration skill 帮你从 alpha09 迁到 alpha10 的AppFunctionServiceEntryPoint架构——后面踩坑部分细说。3. 环境准备写之前先确认三件事# 1. 确认 Android Studio 版本需要支持 API 36 的 SDK# 2. 确认设备的系统版本adbshellgetpropro.build.version.sdk# 应该输出 36 或更高低于 36 直接放弃# 3. 确认 AppFunctions 系统服务存在adbshellcmdapp_functionhelp# 能打出帮助页就说明系统服务在报 Cant find service: app_function 就是设备太老或 ROM 不支持我用的环境Android Studio Otter2026.1 Kotlin 2.1 KSP 2.1 Gradle 8.14测试设备是 Pixel 9 ProAndroid 16 QPR2API 36。然后给模块的build.gradle.kts加依赖dependencies{implementation(androidx.appfunctions:appfunctions:1.0.0-alpha10)// 项目里有 Kotlin 源码就必须加 KSP 编译器ksp(androidx.appfunctions:appfunctions-compiler:1.0.0-alpha10)}如果编译器报「找不到 KSP 插件」先确认根项目build.gradle.kts里挂了com.google.devtools.ksp插件版本和 Kotlin 对齐。4. 实战实现给笔记 App 加一个 AppFunction我拿一个简化版笔记 App 演示。目标暴露两个函数给智能体——listNotes列出笔记和createNote新建笔记。4.1 定义序列化参数和返回值AppFunctions 的参数和返回值必须是可序列化的 data class用AppFunctionSerializable标注AppFunctionSerializable(isDescribedByKDoctrue)dataclassNote(/** 笔记的唯一 ID */valid:String,/** 笔记标题 */valtitle:String,/** 笔记正文 */valcontent:String,/** 创建时间戳毫秒 */valcreatedAt:Long,)AppFunctionSerializable(isDescribedByKDoctrue)dataclassCreateNoteParams(/** 新笔记的标题 */valtitle:String,/** 新笔记的正文 */valcontent:String,)4.2 实现函数逻辑核心逻辑类方法加AppFunction注解classNotesAppFunctions(privatevalrepository:NotesRepository,){/*** 列出当前用户的所有笔记。** param appFunctionContext 由 AppFunctions 运行时提供的执行上下文* return 笔记列表按创建时间倒序*/AppFunction(isDescribedByKDoctrue)suspendfunlistNotes(appFunctionContext:AppFunctionContext,):ListNoterepository.getAllNotes().sortedByDescending{it.createdAt}.map{Note(it.id,it.title,it.content,it.createdAt)}/*** 创建一条新笔记。** param appFunctionContext 由 AppFunctions 运行时提供的执行上下文* param params 包含标题和正文* return 创建成功后的完整笔记对象*/AppFunction(isDescribedByKDoctrue)suspendfuncreateNote(appFunctionContext:AppFunctionContext,params:CreateNoteParams,):Note{valnoterepository.create(idUUID.randomUUID().toString(),titleparams.title,contentparams.content,)returnNote(note.id,note.title,note.content,note.createdAt)}}isDescribedByKDoc true这个参数千万别省。编译器会把 KDoc 折进生成的 schema 元数据里agent 靠这个理解参数语义。我一开始没写 KDoc结果 agent 把title当成「要搜索的关键词」用了折腾半天才发现是描述缺失。4.3 Service 入口类AppFunctions 是无前台执行的——App 不在前台系统也要能找到并实例化你的函数。所以需要一个静态入口 Service用AppFunctionServiceEntryPoint标注AppFunctionServiceEntryPoint(serviceNameNotesAppFunctionService,appFunctionXmlFileNamenotes_app_function_service,)abstractclassNotesAppFunctionService:AppFunctionService(){overridefuncreateAppFunctionInstance(appFunctionContext:AppFunctionContext,):AppFunction{valrepository(applicationContextasNotesApplication).repositoryreturnNotesAppFunctions(repository)}}appFunctionXmlFileName对应res/xml/notes_app_function_service.xml声明这个 Service 暴露哪些函数?xml version1.0 encodingutf-8?app-function-servicexmlns:androidhttp://schemas.android.com/apk/res/androidandroid:name.NotesAppFunctionServiceandroid:exportedtrueapp-functionandroid:namelistNotes/app-functionandroid:namecreateNote//app-function-service最后别忘了在AndroidManifest.xml注册 Serviceserviceandroid:name.NotesAppFunctionServiceandroid:exportedtrueandroid:permissionandroid.permission.EXECUTE_APP_FUNCTIONSintent-filteractionandroid:nameandroid.app.appfunctions.action.EXECUTE_APP_FUNCTIONS//intent-filter/service4.4 调用方Agent App 怎么调如果你写的是调用方agent 端核心 API 是AppFunctionManagerclassNotesAgent(privatevalappFunctionManager:AppFunctionManager,){suspendfunlistNotesFromNotesApp():ListNote{// 先确认目标 App 的某个函数是否可用valenabledappFunctionManager.isAppFunctionEnabled(packageNamecom.example.notes,functionIdNotesAppFunctionsIds.listNotes,// 编译器生成的 Ids 类)if(!enabled)returnemptyList()valrequestExecuteAppFunctionRequest.Builder(appFunctionIdNotesAppFunctionsIds.listNotes,parametersAppFunctionSerializableParams.Builder().setSerializable(EmptyParams()).build(),).build()valresponseappFunctionManager.executeAppFunction(request)returnresponse.result.getSerializable(Note::class.java)}}注意NotesAppFunctionsIds这个类每个带AppFunction的类编译器都会生成一个以Ids结尾的类里面是每个函数的常量 ID。别自己拼字符串直接用它。4.5 动态启停函数重要不是所有函数都适合一直开着。比如「需要登录才能用的函数」「需要特定账号状态的函数」如果默认就暴露给智能体agent 可能在你没登录时就尝试调用返回一堆错误。官方推荐的两步流程第 1 步默认停用。把AppFunction注解的isEnabled参数设为falseAppFunction(isEnabledfalse,isDescribedByKDoctrue)suspendfunsyncNotes(appFunctionContext:AppFunctionContext,):SyncResult{...}第 2 步运行时动态启用。用编译器生成的Ids常量和AppFunctionManagerCompat在合适的时机打开classNotesAppFunctionsController(privatevalappFunctionManager:AppFunctionManagerCompat,){funonUserLoggedIn(){appFunctionManager.setAppFunctionEnabled(appFunctionIdNotesAppFunctionsIds.syncNotes,enabledtrue,)}funonUserLoggedOut(){appFunctionManager.setAppFunctionEnabled(appFunctionIdNotesAppFunctionsIds.syncNotes,enabledfalse,)}}这样智能体在任何时刻看到的函数集合都是「用户当前真正能用」的集合。这比把所有函数一股脑暴露出去安全得多——agent 会尝试调用它能发现的一切别给它发现用不了的东西。5. 效果验证装到设备上之后用 adb 验证注册和执行。这一步比写代码还重要——能跑通 adb就说明你的函数已经被系统索引了。# 1. 列出设备上所有已注册的 AppFunctions过滤你自己的包名adbshellcmdapp_functionlist-app-functions|grep-A10com.example.notes# 2. 直接命令行执行 listNotesadbshellcmdapp_functionexecute-app-function\--packagecom.example.notes\--functioncom.example.notes.NotesAppFunctionService#listNotes\--parameters{}# 3. 执行 createNote传入参数adbshellcmdapp_functionexecute-app-function\--packagecom.example.notes\--functioncom.example.notes.NotesAppFunctionService#createNote\--parameters{createNoteParams: {title: 买牛奶, content: 记得买脱脂的}}我实测listNotes的输出节选AppFunctionId: com.example.notes.NotesAppFunctions#listNotes Display name: listNotes Description: 列出当前用户的所有笔记。 ... Result: OK函数执行返回OK说明整条链路通了。如果想跑端到端Google 还提供了一个官方AppFunctions testing agent示例 AppGitHub 上android/appfunctions仓库装到设备上就能用自然语言指挥 agent 调你的函数。测试时还有两个更细的命令值得记下来# 按包名过滤看某个 App 暴露了哪些函数adbshellcmdapp_functionlist-app-functions--packagecom.example.notes# 调试阶段想临时停用某个函数验证动态启停逻辑adbshellcmdapp_functionset-app-function-enabled\--packagecom.example.notes\--functioncom.example.notes.NotesAppFunctions#syncNotes\--enabledfalse另外如果你习惯在 JVM 层跑单元测试Jetpack 提供了AppFunctionTestRule配合 Robolectric 环境可以脱离真机验证AppFunctionManager和AppFunction逻辑。不过 Google 官方建议优先做真机系统级验证——adb 这套命令就是为此准备的比模拟环境靠谱。6. 踩坑记录六个坑按杀伤力排序。坑 1EXECUTE_APP_FUNCTIONS是 protected 权限普通 App 拿不到这是最大的坑。文档写「调用方必须有EXECUTE_APP_FUNCTIONS权限」但这个权限的 protection level 是 privileged默认只授予系统应用。在 Android 16 r4 及更早的 ROM 上你写个普通 agent App 去调AppFunctionManager会直接SecurityException。解法测试阶段用官方 testing agent它通过特殊渠道拿到权限或者自己刷带权限的 system image。生产环境想当调用方得等 Google 放开别跟系统权限死磕。坑 2execute-app-function强制要求--parameters我第一次执行listNotes没带--parameters直接报参数缺失。这个函数明明一个参数都没有。试了半天发现必须传{}占位。社区里也有人遇到同样的问题。记住没参数也要传--parameters {}。坑 3alpha09 → alpha10 换了 Service 架构alpha09 时代的写法AppFunctionService基类 手动处理在 alpha10 里被AppFunctionServiceEntryPoint注解架构取代了。如果你照着老教程写编译能过但运行期找不到入口。官方出了 migration 文档迁移要点就是本文 4.3 节那套注解 抽象 Service 类 createAppFunctionInstance工厂方法。坑 4isDescribedByKDoc true但没写 KDoc等于没开这个我前面提过。编译器只认 KDoc 里的param描述你光把参数开成 true 但方法上没写文档块schema 里就是空的。agent 拿到空描述只能瞎猜参数语义表现就是「答非所问」。要么写完整 KDoc要么别开这个开关。坑 5Schema 解析失败查 aflags如果你list-app-functions能看到函数但执行时 schema 解析报错检查设备上的 feature flagadbshellaflagslist|grepenable_app_functions_schema_parser部分 ROM 上这个 flag 默认没开导致系统解析不了你打包进 APK 的app_functions.xml/app_functions_v2.xml。验证元数据是否真的打包进去了unzip-lapp/build/outputs/apk/debug/app-debug.apk|grepapp_functions坑 6Gemini 集成是私测预览你的 App 不一定能被系统 agent 访问这是最容易被忽略的「预期管理」坑。AppFunctions API 本身现在就能用、能测但「Gemini 真的来调你的函数」目前只对可信测试者开放。官方 FAQ 原话为了评估质量只有有限数量的应用和系统级智能体可以访问整个流水线。所以别期待装上 App 明天 Gemini 就能用你的笔记——先把 adb 链路跑通申请 EAP 等通知。7. 总结与展望AppFunctions 把 MCP 的思想从云端搬到了设备上解决了服务端工具「够不到本地数据」的根本问题。当前状态API 可用、生态未熟——Android 16 限定、alpha 版本 API 还在变、Gemini 集成私测中但方向已经很清楚未来 agent 操作你的手机靠的不再是「模拟点击」这种脆弱的 UI 自动化而是 App 主动暴露的结构化函数。几个值得关注的时间点Android 17 会把这个能力铺到更多设备官方博客已预告Samsung Gallery × Gemini 已经在 Galaxy S26 上跑通了「帮我找猫的照片」这种多模态案例如果你在做 Android App现在动手的窗口期正好——等生态成熟了你已经踩完坑了我的建议是三步走先按本文把 adb 链路跑通再申请 EAP 抢占端到端体验最后盯着androidx.appfunctions的 release note 随时跟版本。最后留个开放问题你觉得「设备端 MCP」和「云端 MCP」是互补关系还是替代关系你的 App 会优先暴露哪些函数给 agent评论区聊聊你的想法——我很好奇大家的第一反应是兴奋还是焦虑。
返回列表