1. 引言:从应用到平台,Android 跨应用调用的新思路
在 Android 应用生态中,应用之间的通信与能力共享一直是一个复杂且充满挑战的领域。传统的 Intent、ContentProvider 等方式虽然强大,但在面对 AI Agent 等新兴场景时,往往显得不够灵活和安全。今天,我们要介绍的 ACI(Agent Capability Interface,智能体能力接口) 架构,正是为了解决这一痛点而生。
ACI 是一套运行在同一台 Android 设备内、无需 Root、基于 AIDL Binder 的本地跨应用调用框架。它由开源项目 ZorvAI 提供,旨在通过标准化的能力发现和调用协议,大幅降低跨应用集成的成本,同时提供 Token 认证、权限控制和高危能力确认等安全保障。
本文将聚焦于 ACI 架构中的被控端(Controlled) 开发,手把手带你从零开始,构建一个可以被其他应用安全调用的能力提供方。
2. 核心概念与角色
在深入代码之前,我们先来理解 ACI 架构中的几个核心角色。它们共同构成了一个清晰、解耦的调用模型。
2.1 角色定义
| 角色 | 职责 | 关键类 |
|---|---|---|
| 控制端(Controller) | 发起能力调用的应用,如 Zorv AI 主应用 | QuroAidlAciManager + aci-core 的 IAidlAciService 桩 |
| 被控端(Controlled) | 暴露能力供其他应用调用的应用 | BaseAidlAciService / Capability / AidlAciRequest / AidlAciResponse |
| 传输层(Transport) | 控制端与被控端之间的通信方式 | AIDL / HTTP / MCP |
| Token | 用于身份认证的令牌 | AndroidKeyStore AES/GCM |
下图展示了控制端、被控端、传输层与 Token 认证之间的调用关系与数据流向:
flowchart TD
A["控制端(Controller)"] -->|"1. 生成 Token"| T["Token 认证"]
A -->|"2. 发起能力调用请求"| B["传输层(AIDL / HTTP / MCP)"]
T -->|"3. 校验 Token"| B
B -->|"4. 转发请求"| C["被控端(Controlled)"]
C -->|"5. 返回响应"| B
B -->|"6. 回传结果"| A
整体来看,控制端先通过 Token 完成身份认证,再经由传输层将能力调用请求转发给被控端;被控端处理完毕后,响应数据沿同一路径返回控制端,从而形成一次完整的跨应用能力调用闭环。
2.2 核心数据结构
被控端开发的核心是理解请求与响应的数据封装。以下是两个最基础的数据类。
AidlAciRequest(请求)
public class AidlAciRequest implements Parcelable {
public String token; // 认证令牌
public String capability; // 能力名称
public Bundle params; // 参数键值对
public long timestamp; // 请求时间戳
public String sourcePackage; // 调用方包名
}
AidlAciResponse(响应)
public class AidlAciResponse implements Parcelable {
public boolean success; // 是否成功
public Bundle data; // 返回数据
public int errorCode; // 错误码
public String errorMessage; // 错误信息
}
Capability(能力定义)
public class Capability {
public String id; // 能力 ID(如 "exec")
public String name; // 显示名称
public String description; // 描述
public boolean requireUserConfirm; // 是否需要用户确认
public Bundle metadata; // 元数据
}
3. 被控端开发实战
理论铺垫完毕,现在进入实战环节。我们将一步步构建一个完整的被控端应用。
3.1 环境准备
首先,你需要从 ZorvAI 的 GitHub Release 页面下载最新的 aidl-aci-core-release.aar 文件。
方式 A:从 Release 下载
# 从 GitHub Release 下载最新的 aidl-aci-core-release.aar
wget https://github.com/Quor-a/ZorvAI/releases/download/v1.0.69/aidl-aci-core-release.aar
方式 B:Gradle 依赖
// app/build.gradle.kts
dependencies {
implementation(files("libs/aidl-aci-core-release.aar"))
}
3.2 添加依赖
在你的被控端模块的 build.gradle.kts 中添加:
dependencies {
implementation(files("libs/aidl-aci-core-release.aar"))
implementation("androidx.annotation:annotation:1.7.1")
}
3.3 声明权限与清单
在 AndroidManifest.xml 中,你需要声明使用控制端定义的权限,并注册你的 ACI 服务。注意:不要自己定义权限,权限定义权归控制端所有。
<manifest ...>
<!-- 引用控制端定义的权限(不要自己定义!) -->
<uses-permission android:name="ai.aci.permission.CALL" />
<uses-permission android:name="ai.aci.permission.DISCOVER" />
<uses-permission android:name="ai.aci.permission.CALL_DANGEROUS" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- 剥离库可能带入的权限定义(避免同名异签冲突) -->
<permission android:name="ai.aci.permission.CALL" tools:node="remove" />
<permission android:name="ai.aci.permission.DISCOVER" tools:node="remove" />
<permission android:name="ai.aci.permission.CALL_DANGEROUS" tools:node="remove" />
<!-- 声明查询(Android 11+ 包可见性) -->
<queries>
<intent>
<action android:name="ai.aci.intent.BIND" />
</intent>
</queries>
<application ...>
<!-- 注册 ACI 服务 -->
<service
android:name=".MyAciService"
android:exported="true"
android:permission="ai.aci.permission.CALL">
<intent-filter>
<action android:name="ai.aci.intent.BIND" />
</intent-filter>
</service>
</application>
</manifest>
3.4 实现被控端服务
接下来,创建你的 ACI 服务类,继承 BaseAidlAciService。这是整个被控端的核心。
package com.example.myapp.aci
import ai.aidl.aci.core.BaseAidlAciService
import ai.aidl.aci.core.Capability
import ai.aidl.aci.core.AidlAciRequest
import ai.aidl.aci.core.AidlAciResponse
import android.os.Bundle
import android.util.Log
class MyAciService : BaseAidlAciService() {
override fun onCreateCapabilities(capabilities: MutableList<Capability>) {
// 在这里注册你的能力
capabilities.add(Capability(
id = "greet",
name = "打招呼",
description = "向用户发送一条问候信息",
requireUserConfirm = false
))
capabilities.add(Capability(
id = "get_device_info",
name = "获取设备信息",
description = "返回设备型号和系统版本",
requireUserConfirm = false
))
}
override fun onCall(request: AidlAciRequest): AidlAciResponse {
return when (request.capability) {
"greet" -> handleGreet(request)
"get_device_info" -> handleDeviceInfo(request)
else -> errorResponse("未知能力: ${request.capability}")
}
}
private fun handleGreet(request: AidlAciRequest): AidlAciResponse {
val name = request.params?.getString("name") ?: "陌生人"
return successResponse(Bundle().apply {
putString("message", "你好,$name!欢迎使用 ACI 能力。")
})
}
private fun handleDeviceInfo(request: AidlAciRequest): AidlAciResponse {
return successResponse(Bundle().apply {
putString("model", android.os.Build.MODEL)
putString("sdk", android.os.Build.VERSION.SDK_INT.toString())
})
}
private fun successResponse(data: Bundle): AidlAciResponse {
return AidlAciResponse().apply {
success = true
this.data = data
}
}
private fun errorResponse(message: String): AidlAciResponse {
return AidlAciResponse().apply {
success = false
errorMessage = message
errorCode = -1
}
}
}
3.5 处理高危能力
对于涉及用户隐私或系统敏感操作的能力,你必须设置 requireUserConfirm = true。这样,控制端在调用这些能力时,会强制弹出用户确认对话框,确保操作透明可控。
override fun onCreateCapabilities(capabilities: MutableList<Capability>) {
// ... 其他能力
// 高危能力:删除文件
capabilities.add(Capability(
id = "delete_file",
name = "删除文件",
description = "删除指定路径的文件",
requireUserConfirm = true // 必须用户确认
))
}
3.6 端到端实战:实现「文件读写」能力
下面我们综合前面所学,实现一个完整的「文件读写」能力。该能力涉及用户文件系统,属于敏感操作,因此必须设置 requireUserConfirm = true。
第一步:注册能力并处理请求
在被控端服务中注册 file_read 与 file_write 两个能力,并在 onCall 中分发处理:
package com.example.myapp.aci
import ai.aidl.aci.core.BaseAidlAciService
import ai.aidl.aci.core.Capability
import ai.aidl.aci.core.AidlAciRequest
import ai.aidl.aci.core.AidlAciResponse
import android.os.Bundle
import android.util.Log
import java.io.File
class FileAciService : BaseAidlAciService() {
override fun onCreateCapabilities(capabilities: MutableList<Capability>) {
// 注册文件读取能力(高危:涉及用户文件,必须用户确认)
capabilities.add(Capability(
id = "file_read",
name = "读取文件",
description = "读取指定路径的文本文件内容",
requireUserConfirm = true // 高危能力,强制用户确认
))
// 注册文件写入能力(高危:可能覆盖用户数据,必须用户确认)
capabilities.add(Capability(
id = "file_write",
name = "写入文件",
description = "向指定路径写入文本内容",
requireUserConfirm = true // 高危能力,强制用户确认
))
}
override fun onCall(request: AidlAciRequest): AidlAciResponse {
return when (request.capability) {
"file_read" -> handleFileRead(request)
"file_write" -> handleFileWrite(request)
else -> errorResponse("未知能力: ${request.capability}")
}
}
// 处理文件读取请求
private fun handleFileRead(request: AidlAciRequest): AidlAciResponse {
// 1. 从参数中取出目标文件路径
val path = request.params?.getString("path")
if (path.isNullOrBlank()) {
return errorResponse("缺少文件路径参数")
}
// 2. 校验路径合法性,防止越权访问
val file = File(path)
if (!file.exists() || !file.isFile) {
return errorResponse("文件不存在: $path")
}
// 3. 读取文件内容并返回
return try {
val content = file.readText()
successResponse(Bundle().apply {
putString("path", path)
putString("content", content)
putLong("size", file.length())
})
} catch (e: Exception) {
Log.e("FileAciService", "读取文件失败", e)
errorResponse("读取文件失败: ${e.message}")
}
}
// 处理文件写入请求
private fun handleFileWrite(request: AidlAciRequest): AidlAciResponse {
// 1. 从参数中取出目标路径与写入内容
val path = request.params?.getString("path")
val content = request.params?.getString("content")
if (path.isNullOrBlank() || content == null) {
return errorResponse("缺少路径或内容参数")
}
// 2. 写入文件(父目录不存在时自动创建)
return try {
val file = File(path)
file.parentFile?.mkdirs()
file.writeText(content)
successResponse(Bundle().apply {
putString("path", path)
putBoolean("written", true)
})
} catch (e: Exception) {
Log.e("FileAciService", "写入文件失败", e)
errorResponse("写入文件失败: ${e.message}")
}
}
private fun successResponse(data: Bundle): AidlAciResponse {
return AidlAciResponse().apply {
success = true
this.data = data
}
}
private fun errorResponse(message: String): AidlAciResponse {
return AidlAciResponse().apply {
success = false
errorMessage = message
errorCode = -1
}
}
}
第二步:控制端调用「文件读写」能力
控制端通过 AidlAciManager 发现并绑定服务后,即可发起调用。由于该能力设置了 requireUserConfirm = true,控制端调用时会自动弹出用户确认对话框:
class FileController {
private lateinit var aciManager: AidlAciManager
fun init(context: Context) {
aciManager = AidlAciManager.getInstance(context)
}
// 读取文件
fun readFile(packageName: String, path: String) {
val request = AidlAciRequest().apply {
capability = "file_read"
params = Bundle().apply {
putString("path", path)
}
}
// 高危能力:调用时会自动弹出用户确认对话框
aciManager.callAsync(request, object : IAidlAciCallback.Stub() {
override fun onResult(response: AidlAciResponse) {
if (response.success) {
val content = response.data?.getString("content")
Log.d("FileController", "读取成功: $content")
} else {
Log.e("FileController", "读取失败: ${response.errorMessage}")
}
}
})
}
// 写入文件
fun writeFile(packageName: String, path: String, content: String) {
val request = AidlAciRequest().apply {
capability = "file_write"
params = Bundle().apply {
putString("path", path)
putString("content", content)
}
}
// 高危能力:调用时会自动弹出用户确认对话框
aciManager.callAsync(request, object : IAidlAciCallback.Stub() {
override fun onResult(response: AidlAciResponse) {
if (response.success) {
Log.d("FileController", "写入成功: ${response.data?.getString("path")}")
} else {
Log.e("FileController", "写入失败: ${response.errorMessage}")
}
}
})
}
}
关键步骤回顾:
- 注册能力:在
onCreateCapabilities中声明file_read/file_write,并设置requireUserConfirm = true,确保敏感操作透明可控。 - 处理请求:在
onCall中按能力 ID 分发到对应的处理函数,读取/写入文件并返回结构化响应。 - 参数校验:处理前校验路径与内容参数,防止空值或越权路径。
- 控制端调用:通过
AidlAciManager.callAsync异步发起调用,用户确认后由被控端执行并回传结果。
4. 控制端调用示例
为了验证你的被控端是否工作正常,我们来看一下控制端是如何发起调用的。
4.1 发现并绑定服务
class MyController {
private lateinit var aciManager: AidlAciManager
fun init(context: Context) {
aciManager = AidlAciManager.getInstance(context)
}
fun connectToControlledApp(packageName: String) {
// 发现服务
val services = aciManager.discover(packageName)
if (services.isNotEmpty()) {
// 绑定到第一个服务
aciManager.bind(services[0])
// 获取能力列表
val capabilities = aciManager.getCapabilities()
Log.d("Controller", "发现 ${capabilities.size} 个能力")
}
}
fun callCapability(capability: String, params: Bundle) {
val request = AidlAciRequest().apply {
this.capability = capability
this.params = params
}
// 同步调用
val response = aciManager.call(request)
if (response.success) {
Log.d("Controller", "调用成功: ${response.data}")
} else {
Log.e("Controller", "调用失败: ${response.errorMessage}")
}
}
}
4.2 异步调用
对于耗时操作,建议使用异步调用,避免阻塞主线程。
fun callCapabilityAsync(
capability: String,
params: Bundle,
callback: (AidlAciResponse) -> Unit
) {
val request = AidlAciRequest().apply {
this.capability = capability
this.params = params
}
// 异步调用
aciManager.callAsync(request, object : IAidlAciCallback.Stub() {
override fun onResult(response: AidlAciResponse) {
callback(response)
}
})
}
5. 调试与故障排除
开发过程中难免会遇到问题。这里整理了几个常见问题及排查思路。
5.1 服务无法绑定
// 检查清单
val checks = listOf(
"AndroidManifest.xml 中是否声明了 <service>",
"intent-filter 是否包含 'ai.aci.intent.BIND'",
"android:permission 是否正确",
"被控端应用是否已安装并运行"
)
// 调试代码
val intent = Intent("ai.aci.intent.BIND")
intent.setPackage("com.example.myapp")
val resolveInfos = packageManager.queryIntentServices(intent, 0)
if (resolveInfos.isEmpty()) {
Log.e("ACI", "未找到 ACI 服务,请检查 AndroidManifest.xml")
}
5.2 Token 验证失败
// 检查 Token 生成和验证逻辑
val token = AidlAciTokenGenerator.generate(
packageName = "com.example.myapp",
timestamp = System.currentTimeMillis()
)
// 验证 Token
val isValid = AidlAciTokenVerifier.verify(
token = token,
expectedPackage = "com.example.myapp",
maxAgeMs = 5 * 60 * 1000
)
Log.d("Token", "Token 验证结果: $isValid")
5.3 权限不足
// 检查权限声明
val permissions = listOf(
"ai.aci.permission.CALL",
"ai.aci.permission.DISCOVER",
"ai.aci.permission.CALL_DANGEROUS"
)
for (permission in permissions) {
val granted = context.checkSelfPermission(permission)
Log.d("Permission", "$permission: ${if (granted == GRANTED) "已授权" else "未授权"}")
}
6. 最佳实践
最后,分享一些在开发 ACI 被控端时的最佳实践,帮助你构建更安全、更高效的服务。
6.1 安全性
- 永远不要在受控端定义权限:权限定义权归控制端
- 高危能力必须设置
requireUserConfirm = true - 验证所有输入参数:防止注入攻击
- 定期轮换 Token:避免长期有效 Token
- 记录所有审计日志:便于安全审计
6.2 性能优化
- 使用异步调用:避免阻塞主线程
- 缓存能力列表:避免频繁查询
- 限制响应大小:避免传输大量数据
- 使用连接池:复用 AIDL 连接
6.3 错误处理
- 返回详细的错误信息:便于调试
- 使用标准错误码:保持一致性
- 实现重试机制:处理临时故障
- 提供降级方案:服务不可用时的备选
7. 总结与展望
ACI 架构为 Android 跨应用能力调用提供了一种全新的、标准化的解决方案。通过本文的实战指南,你应该已经掌握了如何构建一个安全、高效的 ACI 被控端。这不仅为 AI Agent 提供了强大的工具调用能力,也为应用间的深度协作打开了新的可能。
ZorvAI 项目仍在快速迭代中,未来还将支持更多传输方式、更丰富的安全策略和更完善的开发工具链。欢迎 Star 并参与到项目中来,共同推动 Android 应用生态的智能化演进。
相关文档:
本文基于 ZorvAI v1.0.69 编写,最后更新于 2026年8月30日。