让你的 App 成为 AI 的一环

各位 Android 吴彦祖们,你们好!

在移动开发领域,我们可能正在见证自 App Store 诞生以来,最重要的一次转变------AI 时代。

现在的我们,已经正式进入 AI 浪潮中,传统的"搜索框"正在被"聊天气泡"取代。

越来越多的用户或者开发者,打开手机的第一个应用已经变成了豆包、千问等 AI 软件。而开发者们打开的第一个代码软件,变成了 Claude/Cursor/Codex 等。

在过去十年里,App 开发一直在优化 SEO 和 Deep Link,希望 App 能被搜索引擎、App Store、甚至是微信中的一个分享链接发现并顺利打开。

但是,我们已经来到了一个用户不再主动打开 App,而是直接让 AI "订机票"或"总结我的笔记"的世界里,旧工具正在逐渐失效。

我们即将面临的问题是,如果 AI 无法"看见"你的应用内部能力,那么你的应用就等于不存在。

那么,怎样才能让你应用的功能被 AI 发现?

答案就是 Android AppFunctions

名字解释

文章后续会根据行文以及上下文,会混用 "AI"、"智能体"、"Agent" 这三个单词,读者不用特意区分其含义,在本文中,它们都指代具有 AI 功能的软件。

什么是 AppFunctions

Android AppFunctions 允许应用把数据和功能共享给 AI Agent 与智能助手。它是一座新的桥梁,让应用的核心逻辑走出 UI,进入智能体的世界。

它让开发者能够创建可自描述的函数,供智能体类应用通过自然语言发现并执行。这为 Android 应用提供了一种设备端方案,类似于后端通过 MCP 云服务器声明能力。

Google 正在为 AI Agent 和智能助手开发一套 UI 自动化框架,使它们能够在用户已安装的应用上智能执行通用任务,同时保证过程对用户透明且可控。

当然,实现 AppFunctions 将来不会是对于 App 的强制要求。如果应用希望提供一种结构化程度更高、控制力更强、与 AI 深度融合的通信方式,AppFunctions 将会是一个不错的选择。

当前,Samsung S26 Ultra 和 Google Pixel 10 这些设备已经支持这样的能力,这些设备的用户可以通过 Gemini 应用访问已安装应用暴露出的 AppFunctions

如何集成 AppFunctions

AppFunctions 是 Android 16 SDK 的一部分,同时也通过 Jetpack 库提供,因此可以较为平滑地集成到应用中,而不必担心兼容性问题。

它使用了类似 AIDL 的进程间通信框架,因此具备相应的安全机制,并不是任意应用都可以调用另一个应用的函数。

Jetpack 库内置了注解处理器,可以简化类型安全的 schema 模型与函数创建流程。你只需要声明这个函数做什么、需要什么、返回什么。

随后,注解处理器会生成一份契约,让 Agent 应用理解如何调用这个应用函数,包括所需参数及其类型。

这个过程和配置了 MCP 之后,LLM Agent 知道如何调用函数的方式很相似。

截至目前,AppFunctions 的最新版本是 v1.0.0-alpha08,从版本号可以看出,当前还是处于 alpha 阶段, 所以如果你是在较久之后读到这篇文章,它应该会有不少变化。

好,我们马上开始!

应用需要在项目中添加以下依赖。

kotlin 复制代码
dependencies {
  val appfunversion = "1.0.0-alpha08"

  implementation("androidx.appfunctions:appfunctions-service:$appfunversion")
  implementation("androidx.appfunctions:appfunctions:$appfunversion")
  ksp("androidx.appfunctions:appfunctions-compiler:$appfunversion")
}

// Configure KSP
ksp { 
  arg("appfunctions:aggregateAppFunctions", "true") 
  arg("appfunctions:generateMetadataFromSchema", "false") 
}

在添加完依赖之后,正式开始我们的编码工作.

我们以一个特别简单的笔记 App 为例子,大致实现增删改查四个功能(CRUD Boys)。

首先,你需要定义一个对外的、暴露给 Agent 使用的的笔记模型。

Kotlin 复制代码
@AppFunctionSerializable(isDescribedByKDoc = true) // 注意这里需要使用 AppFunctionSerializable 注解
data class DemoNote(
    /** 笔记唯一标识 */
    val id: String,
    /** 标题 */
    val title: String,
    /** 正文 */
    val content: String,
)

接下来创建一个新的类 DemoAppFunctions,来实现我们的增删改查功能。

kotlin 复制代码
class DemoAppFunctions(
    private val repository: NoteRepository,
) {

    /**
     * 列出全部笔记。
     *
     * @param appFunctionContext AppFunction 上下文。
     */
    @AppFunction(isDescribedByKDoc = true)
    suspend fun listNotes(appFunctionContext: AppFunctionContext): List<DemoNote> =
        repository.getAll()

    /**
     * 新建笔记。
     *
     * @param appFunctionContext AppFunction 上下文。
     * @param title 标题。
     * @param content 正文。
     */
    @AppFunction(isDescribedByKDoc = true)
    suspend fun createNote(
        appFunctionContext: AppFunctionContext,
        title: String,
        content: String,
    ): DemoNote = repository.create(title, content)

    /**
     * 编辑笔记;仅更新非 null 的字段。
     *
     * @param appFunctionContext AppFunction 上下文。
     * @param noteId 目标笔记 id。
     * @param title 新标题,null 表示不改。
     * @param content 新正文,null 表示不改。
     */
    @AppFunction(isDescribedByKDoc = true)
    suspend fun editNote(
        appFunctionContext: AppFunctionContext,
        noteId: String,
        title: String?,
        content: String?,
    ): DemoNote? = repository.update(noteId, title, content)

    /**
     * 删除笔记。
     *
     * @param appFunctionContext AppFunction 上下文。
     * @param noteId 目标笔记 id。
     * @return 是否删除成功。
     */
    @AppFunction(isDescribedByKDoc = true)
    suspend fun deleteNote(
        appFunctionContext: AppFunctionContext,
        noteId: String,
    ): Boolean = repository.delete(noteId)
}
  • 每一个 Agent 暴露的功能(函数),使用注解 AppFunction 进行修饰。
  • 函数的第一个参数,必须是 AppFunctionContext

可以看到,这套定义方式非常清晰。

上述代码中还有个 NoteRepository,这个其实就是个简单的笔记存储类,实现逻辑比较简单,各位可以按照项目复杂度使用数据库、网络、SharedPreference 等去实现即可,这里不做展开讲解。

这里面,繁重的工作都由注解处理器来完成。

@AppFunction(isDescribedByKDoc = true) 注解会生成结构化 schema,从 KDoc 中提取函数说明、每个参数的细节,以及返回模型的描述信息。

当然,我们还需要编写一点额外的代码,用来向应用注册我们的能力:

kotlin 复制代码
class DemoApp : Application(), AppFunctionConfiguration.Provider {

    private val noteRepository by lazy { NoteRepository(this) }
    private val demoAppFunctions by lazy { DemoAppFunctions(noteRepository) }

    override val appFunctionConfiguration: AppFunctionConfiguration
        get() = AppFunctionConfiguration.Builder()
            .addEnclosingClassFactory(DemoAppFunctions::class.java) { demoAppFunctions }
            .build()
}

别忘了在你的 Manifest 中声明哦!

xml 复制代码
<application
    android:name=".MlkitchenApplication"
    //...
    >

就是这样!这就是在应用中实现 AppFunctions 的方式,简单吧。

如何测试

遗憾的是,目前 Gemini 应用还不能直接调用这些函数。

如果你看了上面的支持机型,可能你也会觉得目前还用不了。

不过,我们可以借助 ADB 命令工具来测试集成效果。

请去确保你创建一个 SDK 36 版本及以上的虚拟机,真实设备也可以(我测试发现至少需要 36.1,不过这个可能是我的手法问题)。

现在,我们打开调试,敲出我们的第一行命令:

1. 列出功能

sh 复制代码
# Lists all the app functions exposed by all apps installed on the device.
adb -s emulator-5554 shell cmd app_function list-app-functions

-s emulator-5554 并不是必须的,这个 ADB 参数只是帮助我选择正确的设备而已。

上面这行参数会列出当前设备所有的 app functions,输出结果如下:

这里只是整个结果的一部分,实际上结果很长!

通过它,你可以查看所有函数的完整细节,包括它们的 ID、描述、参数类型及说明、类型、可空性,以及返回类型模型的同类信息。

2. 执行功能

执行功能的命令如下:

sh 复制代码
adb shell cmd app_function execute-app-function \
    --package <PACKAGE_NAME> \
    --function <FUNCTION_ID> \
    --parameters <PARAMS_IN_JSON>

假设我想执行一个简单的读取操作,也就是获取应用中的笔记列表:

sh 复制代码
adb -s emulator-5554 shell cmd app_function execute-app-function --package com.xetom.mlkitchen --function com.xetom.mlkitchen.appfunctions.DemoApp
Functions#listNotes --parameters {}

目前参数是强制传入的,如果我们定义的函数并不需要任何参数(除了 AppFunctionContext),那么直接给 {}

如果你是 windows 平台,上面的命令在 powershell 中运行会有问题,因为我不会处理引号,所以请在 cmd 环境运行。如果有开发者知道如何处理引号问题,希望分享一下

为了测试 AppFunctions,我写了个简单的测试界面用于测试三种功能,所以在运行上述代码之前,我已经增加了数据。

在有数据的情况下,上面的命令会返回 JSON 输出,并且与我们在应用里定义的契约模型保持一致:

json 复制代码
{
  "androidAppfunctionsReturnValue": [
    {
      "id": [
        "995826f0-7c91-40cf-a75c-d0a58593dc5d"
      ],
      "title": [
        "ideas"
      ],
      "content": [
        "your ideas are important"
      ]
    }
  ]
}

如果我们想创建一条笔记,对应命令可以这样写:

bash 复制代码
adb -s emulator-5554 shell cmd app_function execute-app-function --package com.xetom.mlkitchen --function com.xetom.mlkitchen.appfunctions.DemoAppFunctions#createNote --parameters '{\"title\":\"apple\", \"content\":\"a kind of fruits\"}'

你可能会碰到 parameters 各种格式问题,如果你不熟悉各平台的 sh 命令,只能自己尝试了。

执行这条命令后,笔记就会在应用中被创建出来,并可能像下面这样显示。

此时,你再 list

补充:Agent 应用

如果你想尝试做一个基于 AppFunctions 的 Agent 应用,目前可能还面临着不少困难:

  1. API 未正式发布且不稳定 :目前相关的 AppFunctions API 仍处于 alpha 阶段(如 v1.0.0-alpha08),频繁的变动使得长期维护一个 Agent 变得非常吃力,接口可能随时会调整。
  2. 系统级集成门槛高AppFunctions 并非简单的第三方库调用,它深度依赖于系统的支持(如 AppSearch 以及底层的应用间通信机制)。只有在 Android 16 等新系统及 OEM 厂商底层适配的情况下才能完美运行。
  3. 调用方受限与安全性考量:并不是任意一个第三方 App 都可以轻松调用其他应用的函数。出于隐私与安全考虑,系统对跨应用的数据访问与执行权限把控极严,通常只有系统级助手(如 Gemini 或内置的语音助手)才有充足的权限去顺畅调度这些暴露的函数。

官网地址

一点想法

你对这个新能力怎么看?

我真的很期待它在应用中的广泛落地。随着语音支持和 Agent 的不断进步,它可能会成为未来 Android 开发趋势中的一项亮眼能力。

你可以想象它这样被使用:

"帮我在饿了么上再点一次我上次点的炒饭。"

"帮我对比一下所有外卖平台的饺子,给我点一份性价比最高的。"

"帮我订一辆到公司的专车。"

诸如此类......

但是,回到国内的现实环境中,我们可能也会有一些担忧:

在国内相对封闭的移动生态中,各大厂的数据与功能往往是相互隔绝的"信息孤岛"。

AppFunctions 的初衷是开放应用能力供外部 Agent 调度,但这显然触及了各大 App 的核心利益。大厂们通常更希望将用户、流量和数据锁定在自家的生态体系内,而不会轻易把核心的预订、搜索、下单等能力以结构化的形式拱手让给系统级的 Agent。

这就意味着,想要在国内跑通一个跨应用协同的"闭环"极其困难。哪怕系统底层提供了足够完善的接口,如果应用端不配合暴露功能,这套美好的愿景也只能停留在纸面上。在打破这种商业壁垒之前,Agent 也许只能在一些效率工具、笔记或个人独立开发者的小型应用中发挥作用。

相关推荐
空中海2 小时前
7.3 优化实践
android·flutter
Lsk_Smion3 小时前
Sability安卓(三)_基础开发知识扫盲,开学XML......
android·java·android studio·安卓
三少爷的鞋3 小时前
Android 慢性病之拒绝"带病"上线:为什么 ANR 是必须根除的代码 HP?
android
草莓熊Lotso3 小时前
Linux 线程深度剖析:线程 ID 本质、地址空间布局与 pthread 源码全解
android·linux·运维·服务器·数据库·c++
私人珍藏库3 小时前
【Android】Shizuku升级版-Stellar-提高软件权限
android·app·工具·软件·多功能
白毛大侠3 小时前
# MySQL InnoDB 隔离级别与 MVCC 完全解析
android·数据库·mysql
冬奇Lab14 小时前
MediaPlayer 播放器架构:NuPlayer 的 Source/Decoder/Renderer 三驾马车
android·音视频开发·源码阅读
炸炸鱼.15 小时前
Python 操作 MySQL 数据库
android·数据库·python·adb
用户416596736935517 小时前
nextlib 项目架构与深度技术指南 (Architecture & Technical Master Guide)
android