向应用添加 AppFunctions API

本指南介绍了如何将 AppFunctions API 集成到 Android 应用中、实现函数逻辑,以及验证集成是否正常运行。

版本兼容性

此实现要求将项目 compileSdk 设置为 API 级别 36 或更高级别。

您的应用无需验证是否支持 AppFunctions;AppFunctions Jetpack 库会自动处理此问题。 如果支持该功能,AppFunctionManager 会返回一个实例;否则,会返回 null。

依赖项

将所需的库依赖项添加到模块的 build.gradle.kts(或 build.gradle)文件中,并在顶级应用模块中配置 KSP 插件,如下所示:

dependencies {
  implementation("androidx.appfunctions:appfunctions:1.0.0-alpha10")
  // If this project uses any Kotlin source, use Kotlin Symbol Processing (KSP)
  // See Add the KSP plugin to your project
  ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha10")
}

实现 AppFunctions 逻辑

如需为 Android 应用实现 AppFunction,请创建一个实现特定 AppFunctions 逻辑的类。这包括为参数和响应创建可序列化的数据类,然后在函数方法中提供核心逻辑。

以下代码展示了在 TODO 应用中创建任务的示例实现,包括使用代码库定义自定义参数和响应类型以及主要函数逻辑。

@RequiresApi(36)
@AndroidEntryPoint
@AppFunctionServiceEntryPoint(
    serviceName = "TaskAppFunctionService",
    appFunctionXmlFileName = "task_app_function_service",
)
abstract class BaseTaskAppFunctionService : AppFunctionService() {
    @Inject internal lateinit var taskRepository: TaskRepository

    /**
     * Creates a task based on [createTaskParams].
     *
     * @param createTaskParams The parameter to describe how to create the task.
     */
    @AppFunction(isDescribedByKDoc = true)
    suspend fun createTask(
        createTaskParams: CreateTaskParams,
    ): Task = withContext(Dispatchers.IO) {
        // Developers can use predefined exceptions to let the agent know
        // why it failed.
        if (createTaskParams.title == null && createTaskParams.content == null) {
            throw AppFunctionInvalidArgumentException("Title or content should be non-null")
        }

        val id = taskRepository.createTask(
            createTaskParams.title,
            createTaskParams.content
        )

        return@withContext taskRepository
            .getTask(id)
            ?.toTask()
            ?: throw AppFunctionElementNotFoundException("Task not found for ID = $id")
    }

    // Maps internal TaskEntity
    private fun TaskEntity.toTask() = Task(id = id, title = title, content = description)
}

代码要点

  • 默认情况下,AppFunction 实现在 Android 界面线程中运行。因此,长时间运行的操作应执行以下操作:
    • 将 AppFunction 声明为挂起函数。
    • 当操作可能会阻塞线程时,切换到合适的协程调度器。
  • isDescribedByKDoc 设置为 true 时,函数说明或可序列化说明会编码为 AppFunctionMetadata 的一部分,以帮助代理了解如何使用应用的 AppFunction。

在清单中声明 AppFunction 服务

在模块清单(例如 src/main/AndroidManifest.xml)中注册 KSP 生成的服务声明和 app_metadata 属性。 KSP 编译器会生成扩展抽象入口点类的具体服务类 (TaskAppFunctionService),以及 assets/ 目录中的相应 XML 架构。

<service
    android:name="com.example.snippets.ai.TaskAppFunctionService"
    android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
    android:exported="true"
    tools:targetApi="36">
    <property
        android:name="android.app.appfunctions.schema"
        android:value="app_functions_schema.xsd" />
    <property
        android:name="android.app.appfunctions.v2"
        android:value="task_app_function_service.xml" />
    <intent-filter>
        <action android:name="android.app.appfunctions.AppFunctionService" />
    </intent-filter>
</service>
<property
    android:name="android.app.appfunctions.app_metadata"
    android:resource="@xml/app_metadata" />

可选:在运行时切换 AppFunction 可用性

在对 AppFunctions 进行门控时,使用 AppFunctionManager API 显式启用或停用函数。当应用的某些功能不向所有用户开放时,门控功能会非常有用。通过动态启用或停用 AppFunctions,智能系统可以准确了解用户在任何给定时间可使用的功能。

如需安全地限制需要特定账号状态的 AppFunction,请按以下两步流程操作:

第 1 步:默认停用该功能

为防止在验证功能标志之前访问函数,请将 @AppFunction 注解的 isEnabled 参数设置为 false

@AppFunction(isEnabled = false, isDescribedByKDoc = true)
suspend fun createTask(
    createTaskParams: CreateTaskParams,
): Task = TODO()

第 2 步:在运行时动态启用函数

对于每个 AppFunction 类,编译器都会生成一个包含函数 ID 常量(使用 Ids 后缀)的相应类。您可以将这些生成的 ID 常量与 AppFunctionManagerCompat 中的 setAppFunctionEnabled 方法搭配使用,以便在运行时更改函数的启用状态。

suspend fun onFeatureEnabled(context: Context) {
    try {
        AppFunctionManager.getInstance(context)
            ?.setAppFunctionEnabled(
                BaseTaskAppFunctionServiceIds.CREATE_TASK_ID,
                AppFunctionManager.APP_FUNCTION_STATE_ENABLED,
            )
    } catch (e: Exception) {
        // Handle exception: AppFunctions indexation may not be fully completed
        // upon initial app startup.
    }
}

suspend fun onFeatureDisabled(context: Context) {
    try {
        AppFunctionManager.getInstance(context)
            ?.setAppFunctionEnabled(
                BaseTaskAppFunctionServiceIds.CREATE_TASK_ID,
                AppFunctionManager.APP_FUNCTION_STATE_DISABLED,
            )
    } catch (e: Exception) {
        // Handle exception
    }
}

考虑要提供的功能类型

安全性始终至关重要。在选择将应用的哪些功能作为 AppFunction 提供时,请务必记住,系统代理可能会在服务器上处理用户查询,以利用高级 LLM 功能。

为提供出色的用户体验,同时避免泄露敏感信息,我们建议您遵循以下准则:

  • 可从自然语言中获益的功能:提供用户在对话中比通过手动界面导航更容易表达的任务。
  • 缩小访问范围:创建 AppFunction,仅授予代理访问满足用户特定请求所需的数据和操作的权限。
  • 非敏感信息:仅分享并非高度个人化或机密的数据,或者用户在操作上下文中明确同意分享的数据。
  • 针对任何破坏性操作进行明确确认:对于执行破坏性操作(例如删除数据)的函数,请务必格外谨慎。虽然代理可能会调用这些操作,但您的应用应包含自己的确认步骤,并使用清晰明确的语言说明意图。添加多个确认步骤也有助于真正确保用户了解他们需要做什么。

验证 AppFunction 集成

如需验证您是否已正确集成 AppFunctions,可以使用 adb shell cmd app_function

使用 adb shell cmd app_function list-app-functions | grep --after-context 10 $myPackageName 查看应用提供的 AppFunctions 的详细信息。

您还可以使用 AppFunction 的显式标识符 ("$enclosingClassName#$methodName") 直接从命令行执行该函数:

adb shell "cmd app_function execute-app-function \
  --package com.example.android.appfunctions \
  --function 'com.example.android.appfunctions.BaseTaskAppFunctionService#createTask' \
  --parameters '{\"createTaskParams\": {\"title\": \"Buy milk\", \"content\": \"From grocery store\"}}'"

如需体验 Android MCP 的实际效果并验证端到端工作流,而无需任何提示,请在设备上安装并运行 AppFunctions 测试代理 Android 应用。

如果您使用基于对话的助理(例如 Android Studio 中的 Gemini)验证集成,请使用 AppFunctions 开发技能,或提供如下提示:

Execute `adb shell cmd app_function` to learn how the tool works, then act as a
chat agent aiming to invoke AppFunctions to fulfil user prompts for this app.
Rely on the AppFunction description as instructions.

从较低的 API 版本迁移

在版本 1.0.0-alpha10 中,AppFunctions 引入了编译时 @AppFunctionServiceEntryPoint 架构,该架构可整合库依赖项并替换旧版配置提供程序 (AppFunctionConfiguration.Provider)。

如果您的应用目前使用的是旧版 AppFunctions(例如 1.0.0-alpha09),您可以在 AI IDE(例如 Android Studio 中的 Gemini)中使用 AppFunctions 代理技能来自动执行迁移。该技能包含专门的迁移规则,可引导代理整合 build 依赖项、创建所需的 @AppFunctionServiceEntryPoint 服务封装容器、分离上下文参数并更新清单声明。

如需使用 AI 智能体启动自动化迁移,请使用以下提示:

Use the AppFunctions migration skill to upgrade my app's AppFunctions implementation from 1.0.0-alpha09 to the 1.0.0-alpha10 @AppFunctionServiceEntryPoint architecture.