让 AI Agent 直接理解并操作运行中的 Android App,而不是隔着屏幕猜测。

项目Github地址

AppActuator 是一个面向开发与测试场景的 Android 工具库。你可以把少量业务方法显式开放给 AI Agent,让它通过 adb + Socket + JSON-RPC 2.0 调用方法、读取状态、等待事件。调试包获得完整能力,release 包默认替换为空实现,并在构建时检查是否剥离干净。

AI Agent / MCP Client
         │
         │  JSON-RPC 2.0 over adb forward
         ▼
┌──────────────── Android App ────────────────┐
│ AppActuator                                 │
│  方法注册表 · 事件总线 · 生命周期 · 鉴权加密 │
│         │                                   │
│         ▼                                   │
│  你显式开放的业务对象                        │
└─────────────────────────────────────────────┘

为什么用它

假设你想让 Agent 测试一局扫雷。只靠截图和坐标点击,它很难可靠地知道「这一格是否已打开」「还剩多少雷」「游戏何时结束」。AppActuator 让 Agent 在你划定的边界内直接调用 game.sweepCell、读取 game.getGameState,并等待 game.gameOver

你通常遇到的问题 AppActuator 的做法
坐标点击容易受 UI 改版影响 直接调用稳定的业务方法
截图只能推测内部状态 返回结构化 JSON 数据
轮询又慢又不可靠 App 主动发事件,Agent 按需等待
自建调试服务容易遗留在正式包 release 默认注入 noop,并自动审计依赖、Manifest 和 DEX
暴露范围难以控制 只有显式注解且注册的方法可以被调用

它适合调试诊断、业务级自动化测试、AI Coding 联调、教学 Demo 和内部工具。它不用于跨 App 系统自动化、代码注入、热更新或逆向分析,也不替代 UIAutomator/Appium;涉及真实用户界面的端到端验证时,两类工具可以配合使用。

5 分钟接入

前置条件:AGP 8.x+、JDK 17+、minSdk 30+。插件和 Android 库已发布到 Gradle Plugin Portal / Maven Central。

1. 配置仓库与插件

宿主工程的 settings.gradle.kts 需要包含:

pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}

然后在应用模块的 build.gradle.kts 中应用插件:

plugins {
    id("io.gitee.kuangthree.appactuator") version "0.2.0"
}

appActuator {
    auth = "disabled"       // disabled | optional | required
    autoStart = true        // App 进程启动时自动初始化
    keepAlive = false       // 默认不在后台保持信道
    requireShellUid = true  // 仅允许 adb shell 读取元数据
}

无需手动添加 library-apilibrary-fulllibrary-noop。插件会按构建变体注入正确实现:debug 默认使用 full,release 默认使用 noop。

2. 开放一个业务对象

import android.app.Application
import com.universe_st.appactuator.api.ActuatorMethod
import com.universe_st.appactuator.api.ActuatorTarget
import com.universe_st.appactuator.api.AppActuator
import com.universe_st.appactuator.api.ThreadMode

@ActuatorTarget(name = "game", description = "游戏控制器")
class GameController {
    private fun gameRunning(): Boolean = true

    @ActuatorMethod(
        name = "sweepCell",
        condition = "gameRunning",
        thread = ThreadMode.MAIN,
    )
    fun sweepCell(x: Int, y: Int): Map<String, Int> {
        val result = mapOf("x" to x, "y" to y)
        AppActuator.emit("game.boardChanged", result)
        return result
    }
}

class DemoApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        AppActuator.register(GameController())
    }
}

如果项目还没有自定义 Application,请在 AndroidManifest.xml 中声明:

<application android:name=".DemoApplication" ... />

这里有三个重要边界:

  • 只有 @ActuatorMethod 标记的方法会暴露;普通方法仍然不可见。
  • condition 指向同类中的无参 Boolean 方法,返回 false 时调用会被拒绝;条件方法本身不能再标记 @ActuatorMethod
  • ThreadMode.MAIN 用于 UI 操作,BACKGROUND 用于耗时任务,默认的 CALLER 适合快速、无 UI 依赖的方法。

3. 构建并确认服务

.\gradlew.bat :app:assembleDebug
.\gradlew.bat :app:installDebug

adb shell content query --uri content://com.example.app.actuator/metadata

com.example.app 换成应用的 applicationId。正常情况下会看到 status=running、动态端口、协议版本和鉴权档位。如果 App 被 force-stop,先显式启动它;如果设备执行过 adb root,默认的 shell UID 校验会拒绝查询,请先 adb unroot

4. 从 PC 调用

仓库内的 Python 客户端要求 Python 3.10+:

python -m pip install -e "client"
from appactuator import AppActuatorClient

client = AppActuatorClient(package="com.example.app")
try:
    client.connect()
    client.handshake()
    methods = client.list_methods()
    result = client.invoke("game", "sweepCell", {"x": 0, "y": 0})
    event = client.wait_event("game.gameOver", timeout_ms=60_000)
finally:
    client.close()

list_methods() 支持 targets(只看特定对象,如 client.list_methods(targets=["game.1"]),也接受类名前缀 ["game"] 匹配全部实例)与 no_desc(返回最小结构省 token)参数,详见 skill/app-actuator/SKILL.md

connect() 会自动完成元数据查询和 adb forward,随后由 handshake() 协商协议;close() 会同时清理连接与转发。多台设备同时连接时,请向客户端传入设备序列号。

让 AI Coding 工具直接使用

仓库提供两种 Agent 接入方式:

  • Agent Skill:位于仓库根目录的 skill/ 文件夹,适合能读取操作指引并运行命令的 Agent。
  • MCP Server:适合支持本地 stdio MCP 的 AI Coding 工具。安装命令为 python -m pip install -e "client[mcp]";启用鉴权时使用 client[all]

下面是兼容 mcpServers 格式的最小配置。路径应替换为本机仓库的绝对路径;如果客户端已安装到当前 Python 环境,可以移除 PYTHONPATH

{
  "mcpServers": {
    "appactuator": {
      "command": "python",
      "args": ["-m", "appactuator.mcp_server"],
      "env": {
        "PYTHONPATH": "C:/path/to/app-actuator/client",
        "APPACTUATOR_PACKAGE": "com.example.app",
        "APPACTUATOR_SERIAL": "emulator-5554"
      }
    }
  }
}

MCP Server 暴露固定的 7 个工具,避免宿主方法动态变化导致工具缓存失效:

工具 用途
actuator_connect 建立连接;重复调用安全
actuator_get_status 查看运行状态与鉴权档位
actuator_list_methods 获取实时方法清单、参数与不可用原因;支持 targets 过滤与 no_desc 最小结构
actuator_invoke 调用一个宿主方法
actuator_list_events 查看已声明或已触发的事件
actuator_wait_event 等待一次事件
actuator_close 关闭连接并清理 adb forward

Reasonix、OpenCode 等工具的完整配置示例和环境变量说明见 AI Coding MCP 接入文档

鉴权与正式包安全

内部调试包也建议使用 required 鉴权。先生成密钥对:

$env:PYTHONPATH = 'client'
python -m appactuator.genkey --out-dir keys

公钥随构建注入 App,私钥只保留在 PC:

.\gradlew.bat :app:assembleDebug `
  "-PappActuator.auth=required" `
  "-PappActuator.publicKeyFile=keys/appactuator_public.pem"

鉴权成功后,业务消息使用 AES-256-GCM 加密;握手使用 RSA 公钥体系。required 模式缺少有效公钥时服务不会启动。PowerShell 中的 -PappActuator.<key>=<value> 参数务必整体加引号。

release 安全不是一条使用建议,而是构建机制:

  1. 插件默认让 release 变体依赖 library-noop,其公开 API 行为固定为空操作。
  2. 完整实现使用的 Provider、Service 和 INTERNET 权限不会由 noop 引入。
  3. release 构建自动从依赖图、合并后的 Manifest 和 DEX 三个层面审计残留;发现完整实现即构建失败。

请勿用 -PappActuator.enabled=true 将完整实现带入生产 release。该开关只应服务于明确隔离的内部构建。

运行模型与重要限制

  • 服务只监听设备回环地址 127.0.0.1,PC 必须通过 adb 转发访问。
  • 单个 App 同时只接受一个客户端连接;同一连接内可以并发请求。
  • listMethodsinvoke 都会实时检查注册状态、生命周期与自定义条件,不依赖旧缓存。
  • waitEvent 只等待请求发出之后的事件,不补发历史事件;需要可靠恢复时应同时提供状态查询方法。
  • Kotlin 默认参数不受支持,调用方必须显式传入全部参数。
  • 同一 target 中的暴露方法不能重名;发现重名时整个 target 注册失败。
  • 单条消息、并发请求、事件等待与订阅均有资源上限,默认调用超时为 30 秒。
  • keepAlive=true 会引入 specialUse 前台服务及相应政策成本,只建议用于内部调试构建。
  • library-full 声明 INTERNET 权限以创建本地 Socket;剥离后的 noop 构建不包含该权限。

协议、安全与并发语义的权威定义在 需求规格,实现追踪和端到端验证记录在 合规矩阵

跑通扫雷 Demo

仓库自带一个 10×10 Compose 扫雷 Demo。它开放 newGamegetGameStategetCellsweepCelltoggleFlaggame.* 事件,是体验完整链路最快的入口。

.\gradlew.bat :demo:sweeper:assembleDebug
.\gradlew.bat :demo:sweeper:installDebug

adb shell content query --uri content://com.universe_st.appactuator.demo.sweeper.actuator/metadata

$env:PYTHONPATH = 'client'
python client/examples/sweeper_demo.py `
  --package com.universe_st.appactuator.demo.sweeper

需要鉴权时:

$env:PYTHONPATH = 'client'
python -m appactuator.genkey --out-dir keys
.\gradlew.bat :demo:sweeper:assembleDebug "-PappActuator.auth=required" "-PappActuator.publicKeyFile=keys/appactuator_public.pem"
.\gradlew.bat :demo:sweeper:installDebug
python client/examples/sweeper_demo.py --package com.universe_st.appactuator.demo.sweeper --private-key keys/appactuator_private.pem

项目结构

路径 职责
library-api/ 稳定公开 API:注解、门面、配置、错误码与 noop 兜底
library-full/ Provider、TCP Server、JSON-RPC、注册表、事件与加密鉴权
library-noop/ release 剥离构建使用的空 AAR
library-lifecycle/ 可选 AndroidX Lifecycle 适配
plugin/ 变体依赖注入、构建期配置与剥离审计
client/ Python 客户端、CLI、密钥工具与 MCP Server
demo/sweeper/ Compose 扫雷示例
skill/ Agent 操作指引

完整实现中的 core/crypto/ 不依赖 android.*,因此核心协议和加密逻辑可以直接在 JVM 上测试。公开错误码由 Android 与 Python 两端同步维护。

从源码构建

需要 JDK 17 和 Android SDK,并在 local.properties 中配置 sdk.dir

# Debug 构建:注入完整实现
.\gradlew.bat :demo:sweeper:assembleDebug

# Release 构建:注入 noop,并运行剥离审计
.\gradlew.bat :demo:sweeper:assembleRelease

# Android / Gradle 单元测试
.\gradlew.bat :library-full:testDebugUnitTest :library-lifecycle:testDebugUnitTest :demo:sweeper:testDebugUnitTest :plugin:test

# Python 客户端测试
$env:PYTHONPATH = 'client'
python -m unittest discover -s client/tests -v
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐