Android 跨应用能力调用新范式:ACI 架构被控端开发实战指南

2 阅读8分钟

1. 引言:从应用到平台,Android 跨应用调用的新思路

在 Android 应用生态中,应用之间的通信与能力共享一直是一个复杂且充满挑战的领域。传统的 Intent、ContentProvider 等方式虽然强大,但在面对 AI Agent 等新兴场景时,往往显得不够灵活和安全。今天,我们要介绍的 ACI(Agent Capability Interface,智能体能力接口) 架构,正是为了解决这一痛点而生。

ACI 是一套运行在同一台 Android 设备内无需 Root、基于 AIDL Binder 的本地跨应用调用框架。它由开源项目 ZorvAI 提供,旨在通过标准化的能力发现和调用协议,大幅降低跨应用集成的成本,同时提供 Token 认证、权限控制和高危能力确认等安全保障。

开源地址github.com/Quor-a/Zorv…

本文将聚焦于 ACI 架构中的被控端(Controlled) 开发,手把手带你从零开始,构建一个可以被其他应用安全调用的能力提供方。

2. 核心概念与角色

在深入代码之前,我们先来理解 ACI 架构中的几个核心角色。它们共同构成了一个清晰、解耦的调用模型。

2.1 角色定义

角色职责关键类
控制端(Controller)发起能力调用的应用,如 Zorv AI 主应用QuroAidlAciManager + aci-coreIAidlAciService
被控端(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_readfile_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}")
                }
            }
        })
    }
}

关键步骤回顾

  1. 注册能力:在 onCreateCapabilities 中声明 file_read / file_write,并设置 requireUserConfirm = true,确保敏感操作透明可控。
  2. 处理请求:在 onCall 中按能力 ID 分发到对应的处理函数,读取/写入文件并返回结构化响应。
  3. 参数校验:处理前校验路径与内容参数,防止空值或越权路径。
  4. 控制端调用:通过 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 安全性

  1. 永远不要在受控端定义权限:权限定义权归控制端
  2. 高危能力必须设置 requireUserConfirm = true
  3. 验证所有输入参数:防止注入攻击
  4. 定期轮换 Token:避免长期有效 Token
  5. 记录所有审计日志:便于安全审计

6.2 性能优化

  1. 使用异步调用:避免阻塞主线程
  2. 缓存能力列表:避免频繁查询
  3. 限制响应大小:避免传输大量数据
  4. 使用连接池:复用 AIDL 连接

6.3 错误处理

  1. 返回详细的错误信息:便于调试
  2. 使用标准错误码:保持一致性
  3. 实现重试机制:处理临时故障
  4. 提供降级方案:服务不可用时的备选

7. 总结与展望

ACI 架构为 Android 跨应用能力调用提供了一种全新的、标准化的解决方案。通过本文的实战指南,你应该已经掌握了如何构建一个安全、高效的 ACI 被控端。这不仅为 AI Agent 提供了强大的工具调用能力,也为应用间的深度协作打开了新的可能。

ZorvAI 项目仍在快速迭代中,未来还将支持更多传输方式、更丰富的安全策略和更完善的开发工具链。欢迎 Star 并参与到项目中来,共同推动 Android 应用生态的智能化演进。

项目地址github.com/Quor-a/Zorv…

相关文档


本文基于 ZorvAI v1.0.69 编写,最后更新于 2026年8月30日。