搜狐技术产品

Android 开发者的 AI 时代新武器:AppFunctions 实战指南

作为 Android Jetpack 库家族的新成员,AppFunctions 配合全新的 Android 平台 API,旨在将应用的部分功能转化为 AI Agent 可以发现和执行的工具。

技术定位:AppFunctions 是 Android 原生的 Model Context Protocol(MCP)等价物——采用相同的理念,但工具位于应用内部,在设备端本地运行。

一、 目标

核心目标:建立单一的、OS 级别的、与 Agent 无关的接口,使应用只需声明一次其能力,任何平台信任的调用者均可发现并执行。

前置要求:
- compileSdk = 37
- targetSdk = 36 或更高
- 运行 Android 16 或更高版本的设备

二、技术背景

2.1 现有方案的局限性

在 AppFunctions 出现之前,应用与 AI 助手的交互主要存在以下模式:

方案
厂商
特点
局限性
App Actions
Google
暴露内置意图给 Google Assistant
助手专属、预定义词汇表
App Intents / SiriKit
Apple
类似机制供 Siri 调用
仅限苹果生态
Bixby Capsules
Samsung
三星专属方案
平台锁定

上述方案的共同特征是助手专属且不具备可移植性。第三方应用需要为每个助手单独实现集成,增加了开发和维护成本。

2.2 Model Context Protocol(MCP)简介

MCP 是由 Anthropic 于 2024 年底发布的开放标准,现已被多家 AI 产品采用。其核心模型如下:

•MCP 服务器:暴露工具列表的进程,每个工具包含名称、自然语言描述和 JSON Schema 参数定义
•MCP 客户端:通常为 AI Agent,连接服务器后查询可用工具并决定调用策略
•通信协议:JSON-RPC,通过 stdio(本地)或 HTTP(远程)传输

MCP 的挑战:传统 MCP 服务器运行于应用外部,缺乏对应用状态的特权访问。在移动端,通过 API 暴露应用内部状态或在应用进程内运行 MCP 服务器均存在实际困难。

AppFunctions 的设计目标:填补上述空白,提供设备端的 MCP 等价实现。

三、AppFunctions 架构设计

3.1 核心特性

AppFunctions 具备以下三项核心特性:

A. 本地执行
- 无网络往返,无需维护服务器
- Agent 直接调用应用并读取当前状态

B. OS 级索引
- 无需运行时注册
- 注解处理器生成 schema 至 APK(assets/app_function_v2.xml)
- OS 在安装时读取并维护注册表

C. 文档即契约
- @AppFunction(isDescribedByKDoc = true) 将 KDoc 编码至元数据
- 文档质量直接影响 Agent 的调用决策

3.2 架构层次

Image

四、实现指南

4.1 依赖配置

步骤 1:版本目录配置(gradle/libs.versions.toml)

[versions]
appfunctions = "1.0.0-alpha09"

[libraries]
androidx-appfunctions          = { module = "androidx.appfunctions:appfunctions",          version.ref = "appfunctions" }
androidx-appfunctions-service  = { module = "androidx.appfunctions:appfunctions-service",  version.ref = "appfunctions" }
androidx-appfunctions-compiler = { module = "androidx.appfunctions:appfunctions-compiler", version.ref = "appfunctions" }

步骤 2:模块构建配置(app/build.gradle.kts)

ksp {
    arg("appfunctions:aggregateAppFunctions", "true")
}

dependencies {
    implementation(libs.androidx.appfunctions)
    implementation(libs.androidx.appfunctions.service)
    ksp(libs.androidx.appfunctions.compiler)
}

配置说明:
- ksp(libs.androidx.appfunctions.compiler) 注册 KSP 处理器
- aggregateAppFunctions 标志聚合项目内所有 @AppFunction 至统一 schema
- 多模块项目中,仅应用模块设置此标志,库模块仅需编译器依赖

4.2 Manifest 声明

OS 在安装时需获取两项信息:AppFunction 元数据位置、功能执行时的服务绑定目标。

<application ...>
    <property
        android:name="android.app.appfunctions.app_metadata"
        android:resource="___HL_0___/app_metadata" />

    <service
        android:name="androidx.appfunctions.service.PlatformAppFunctionService"
        android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
        android:exported="true"
        tools:targetApi="36">
        <intent-filter>
            <action android:name="android.app.appfunctions.AppFunctionService" />
        </intent-filter>
    </service>
</application>

权限说明:
- BIND_APP_FUNCTION_SERVICE:仅限平台绑定
- EXECUTE_APP_FUNCTIONS:调用者权限,非应用所需

4.3 应用元数据配置

创建 res/xml/app_metadata.xml:

<?xml version="1.0" encoding="utf-8"?>
<AppFunctionAppMetadata
    xmlns:appfn="http://schemas.android.com/apk/androidx.appfunctions"
    appfn:description="本应用支持用户查看和管理笑话内容,包括标记收藏和清除收藏列表。" />

属性说明:

属性
格式
用途
appfn:description
string
LLM 面向描述,运行时决策依据
appfn:displayDescription
reference|string
人类可读描述,可本地化

复杂场景配置示例:

<AppFunctionAppMetadata
    xmlns:appfn="http://schemas.android.com/apk/androidx.appfunctions"
    appfn:description="本应用支持用户查看和管理笑话及收藏。
        操作模式:
        - 调用 'getFavorites' 获取有效笑话 ID 后再调用 'setFavorite'
        约束条件:
        - 'clearFavorites' 操作不可逆,调用前需确认用户意图"

    appfn:displayDescription="___HL_0___/app_function_user_description" />

4.4 数据层扩展

AppFunction 应作为普通业务逻辑的薄壳,而非重复实现。首先通过现有架构层添加业务能力:

DAO 层:

@Query("UPDATE joke SET isFavorite = 0")
suspendfun clearAllFavorites()

数据源层:

interface LocalJokesDataSource {
suspendfun clearAllFavorites()
}

class LocalJokesDataSourceImpl : LocalJokesDataSource {
overridesuspendfun clearAllFavorites() {
        database.clearAllFavorites()
    }
}

仓库层:

interface JokesRepository {
suspendfun clearAllFavorites(): Result<Unit>
}

class JokesRepositoryImpl : JokesRepository {
overridesuspendfun clearAllFavorites(): Result<Unit> {
return safeCall {
            localDataSource.clearAllFavorites()
        }
    }
}

4.5 AppFunction 实现

基础功能示例:

class JokesAppFunctions {

@AppFunction(isDescribedByKDoc = true)
suspendfun clearFavorites(context: AppFunctionContext): String {
val repository = AppModule.jokesRepository
val result = repository.clearAllFavorites()
returnif (result.isSuccess) {
"所有收藏笑话已成功清除"
        } else {
"清除收藏失败:${result.exceptionOrNull()?.message}"
        }
    }
}

实现要点:

要素
说明
isDescribedByKDoc = true
KDoc 编码至元数据,供 Agent 决策参考
AppFunctionContext
首参数,系统传入,用于访问系统服务和识别调用者
返回类型
可为 String 或 @AppFunctionSerializable 注解的数据类
suspend
必需,AppFunction 默认在主线程执行

4.6 结构化数据与类型化参数

可序列化数据类:

@AppFunctionSerializable(isDescribedByKDoc = true)
dataclass AppFunctionJoke(
val id: Int,
val question: String,
val answer: String,
)

fun Joke.toAppFunctionJoke(): AppFunctionJoke = AppFunctionJoke(
    id = id, question = question, answer = answer,
)

带参数功能:

@AppFunction(isDescribedByKDoc = true)
suspendfun setFavorite(
    context: AppFunctionContext,
    jokeId: Int,
    isFavorite: Boolean,
): String = withContext(Dispatchers.IO) {
val joke = AppModule.jokesRepository.getJokeById(jokeId).getOrNull()
        ?: throw AppFunctionElementNotFoundException("未找到 ID 为 $jokeId 的笑话")

val result = AppModule.jokesRepository.setFavorite(jokeId, isFavorite)
if (result.isSuccess) {
if (isFavorite) "笑话 ${joke.id} 已添加至收藏"else"笑话 ${joke.id} 已取消收藏"
    } else {
"更新笑话 ${joke.id} 失败:${result.exceptionOrNull()?.message}"
    }
}

错误处理:
- 通过抛出 AppFunctionException 子类报告失败
- 库内置异常类型:无效参数、元素未找到、权限不足等

支持的数据类型:

类别
类型
基本类型
Int
, Long, Float, Double, Boolean
基本类型数组
IntArray
, LongArray, FloatArray, DoubleArray, BooleanArray
原生类型
String
, PendingIntent, Uri, LocalTime, LocalDate, LocalDateTime, Instant
复合类型
@AppFunctionSerializable
 对象,上述类型的 List

4.7 工厂配置

OS 负责实例化 AppFunctions 类,需在 Application 中声明工厂:

class MyApplication : Application(), AppFunctionConfiguration.Provider {

overridefun onCreate() {
super.onCreate()
        AppModule.initialize(applicationContext)
    }

overrideval appFunctionConfiguration: AppFunctionConfiguration
get() = AppFunctionConfiguration.Builder()
            .addEnclosingClassFactory(JokesAppFunctions::class.java) { JokesAppFunctions() }
            .build()
}

Manifest 注册:

<application
    android:name=".MyApplication"
    ...>

4.8 代码组织结构

AppFunction 属于入站入口点(驱动适配器),与 Compose UI 同级,应置于功能模块的独立包中:

features/jokes/
├─ data/          ← 驱动适配器:Room、Ktor、DTO、映射器
├─ domain/        ← 领域模型、仓库接口(无 Android 依赖)
├─ presentation/  ← Compose 界面 + ViewModels(面向人类用户)
└─ appfunctions/  ← AppFunctions、DTO、映射器(面向 AI Agent)

架构原则:appfunctions/ 之于 Agent 如同 presentation/ 之于人类用户——相同的架构等级,不同的交付对象。

五、测试与验证

5.1 adb 命令测试

列出已注册功能:

adb shell cmd app_function list-app-functions | grep -A 10"[应用包名]"

执行功能:

adb shell cmd app_function execute-app-function \
  --package [应用包名] \
  --function [完整类名]#[方法名] \
  --parameters '{"参数名":值}'

示例命令:

# 清除收藏
adb shell cmd app_function execute-app-function \
  --package eu.anifantakis.networkapp \
  --function eu.anifantakis.networkapp.jokes.features.jokes.appfunctions.JokesAppFunctions#clearFavorites \
  --parameters '{}'

# 获取收藏列表
adb shell cmd app_function execute-app-function \
  --package eu.anifantakis.networkapp \
  --function eu.anifantakis.networkapp.jokes.features.jokes.appfunctions.JokesAppFunctions#getFavorites \
  --parameters '{}'

# 设置收藏状态
adb shell "cmd app_function execute-app-function \
  --package eu.anifantakis.networkapp \
  --function eu.anifantakis.networkapp.jokes.features.jokes.appfunctions.JokesAppFunctions___HL_5___
  --parameters '{\"
jokeId\":179,\"isFavorite\":true}'"

5.2 环境要求

镜像类型
cmd app_function 支持
说明
Google APIs / Google Play
✅ 完整支持
推荐使用
纯 AOSP
❌ 仅 dumpsys
API 可用,adb 命令缺失

验证方法:

adb shell getprop ro.build.fingerprint
# sdk_gphone... 表示支持 cmd
# sdk_phone.../test-keys 表示不支持 cmd

六、最佳实践

6.1 幂等性设计

对于破坏性操作,建议采用以下模式确保幂等性:

策略
实现方式
绝对设置操作
使用 SET status = 'active' 而非 SET value = value + 1
客户端请求 ID
接受调用者提供的唯一键,检测并忽略重复调用
统一返回格式
无操作和实际更改返回相同成功响应

6.2 安全建议

•读取操作:可自由暴露
•写入操作:保守暴露,选择最小有效表面
•权限假设:EXECUTE_APP_FUNCTIONS 当前为特权权限,但功能层应作为最后防线

6.3 文档规范

KDoc 质量直接影响 Agent 的调用决策:

•清晰描述操作涉及的变更和不变内容
•说明操作是否可安全重试、是否可撤销
•参数命名应自描述(如 daysOlderThan: Int 优于 filter: String)
•使用普通引号引用功能名(如 "setFavorite"),避免 KDoc 链接语法

七、相关资源

7.1 官方文档

•AppFunctions 概览[2]
•将 AppFunctions API 添加到你的应用[3]
•androidx.appfunctions Jetpack 发布说明[4]

7.2 示例代码

•起始代码分支[5]
•完成代码分支[6]
•添加 AppFunctions 的 Commit[7]
•AppFunction 聊天应用演示[8]

7.3 参考标准

•Model Context Protocol 规范[9]

八、版本状态说明

当前版本:AppFunctions 1.0.0-alpha09

已知限制:
- Gemini 集成处于私人预览阶段,仅对可信测试者开放
- EXECUTE_APP_FUNCTIONS 权限在 Android 16 发布版本上为特权权限
- 完整 Agent 集成需等待后续版本

开发者可用功能:
- 构建、注册和执行 AppFunctions(通过 adb)
- 自定义宿主应用调用(需 userdebug 构建或 root 设备)

本文档基于 AppFunctions 1.0.0-alpha09 版本编写,后续版本可能有变更。请以官方文档为准。

参考链接

[1] ProAndroidDev: https://proandroiddev.com/appfunctions-making-your-android-app-discoverable-by-ai-agents-fbfbeddf8103

[2] AppFunctions 概览: https://developer.android.com/ai/appfunctions

[3] 将 AppFunctions API 添加到你的应用: https://developer.android.com/ai/appfunctions/add-appfunctions

[4] androidx.appfunctions Jetpack 发布说明: https://developer.android.com/jetpack/androidx/releases/appfunctions

[5] 起始代码分支: https://github.com/ioannisa/AppFunctionsDemo/

[6] 完成代码分支: https://github.com/ioannisa/AppFunctionsDemo/tree/02-Finished-Code

[7] 添加 AppFunctions 的 Commit: https://github.com/ioannisa/AppFunctionsDemo/commit/dafd9cdb96755b456605bc758ccfd4e6575434d3

[8] AppFunction 聊天应用演示: https://github.com/android/appfunctions

[9] Model Context Protocol 规范: https://modelcontextprotocol.io/