MobileAgentSDK 是一個 Android 庫,封裝了 C++ MobileAgent SDK,提供 Kotlin/Java 友好的介面。通過 WebSocket 串連到 MobileAgent 伺服器,實現智能體任務的執行、管理和監控。
SDK下載
使用方法
引入 AAR
將產生的 AAR 檔案複製到專案中,並在 build.gradle 中添加:
dependencies {
implementation(files('libs/sdk-release.aar'))
}基本使用
方式一:使用 Flow(推薦)
Flow 提供了響應式編程風格,更適合 Kotlin 協程:
import com.wuying.mobileagentsdk.MobileAgentSdk
import com.wuying.mobileagentsdk.MobileAgentEvent
import com.wuying.mobileagentsdk.MobileAgentState
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.collectLatest
// 使用 try-with-resources 自動管理生命週期
MobileAgentSdk().use { sdk ->
// 在協程中收集事件
lifecycleScope.launch {
sdk.events.collectLatest { event ->
when (event) {
is MobileAgentEvent.StateChanged -> {
when (event.state) {
MobileAgentState.CONNECTED -> {
Log.d("SDK", "已串連")
// 執行任務
sdk.executeTask("請幫我開啟設定", 10)
}
MobileAgentState.DISCONNECTED -> {
Log.d("SDK", "已斷開")
}
else -> {}
}
}
is MobileAgentEvent.TaskResult -> {
Log.d("SDK", "任務完成: ${event.resultText}")
}
is MobileAgentEvent.Stream -> {
Log.d("SDK", "流式資料: ${event.content}")
}
else -> {}
}
}
}
// 也可以只收集特定類型的事件
lifecycleScope.launch {
sdk.events
.filterIsInstance<MobileAgentEvent.StateChanged>()
.collect { event ->
Log.d("SDK", "狀態: ${event.state}")
}
}
lifecycleScope.launch {
sdk.events
.filterIsInstance<MobileAgentEvent.TaskResult>()
.collect { event ->
Log.d("SDK", "任務結果: ${event.resultText}")
}
}
// 串連到伺服器
sdk.connect("wss://localhost:30005/ws")
// SDK 會在 use 塊結束時自動銷毀
}方式二:使用傳統 Callback
適合不熟悉協程的情境:
import com.wuying.mobileagentsdk.MobileAgentSdk
import com.wuying.mobileagentsdk.MobileAgentCallback
import com.wuying.mobileagentsdk.MobileAgentState
MobileAgentSdk().use { sdk ->
// 設定回調
sdk.setCallback(object : MobileAgentCallback {
override fun onStateChanged(state: MobileAgentState) {
when (state) {
MobileAgentState.CONNECTED -> {
Log.d("SDK", "已串連")
sdk.executeTask("請幫我開啟設定", 10)
}
MobileAgentState.DISCONNECTED -> {
Log.d("SDK", "已斷開")
}
else -> {}
}
}
override fun onTaskResult(
taskId: String,
result: String,
resultText: String,
success: Boolean,
actualSteps: Int
) {
Log.d("SDK", "任務完成: $resultText")
}
override fun onStream(
taskId: String,
step: Int,
agent: String,
contentType: String,
content: String
) {
Log.d("SDK", "流式資料: $content")
}
// 其他回調根據需要實現
})
// 串連到伺服器
sdk.connect("wss://localhost:30005/ws")
}生命週期管理
SDK 實現了 AutoCloseable 介面,支援 try-with-resources 模式:
// 推薦:自動管理生命週期
MobileAgentSdk().use { sdk ->
sdk.connect("wss://localhost:30005/ws")
// 使用 SDK...
} // 自動調用 close() 釋放資源
// 或者手動管理
val sdk = MobileAgentSdk()
try {
sdk.connect("wss://localhost:30005/ws")
// 使用 SDK...
} finally {
sdk.close() // 或 sdk.destroy()
}API 參考
串連管理
connect(url: String)
通過 WebSocket URL 串連:
sdk.connect("wss://localhost:30005/ws")connectWithTicket(ticket: String)
通過 Ticket 串連(需要伺服器支援):
sdk.connectWithTicket("your-ticket-here")disconnect()
中斷連線:
sdk.disconnect()任務管理
executeTask(task: String, maxSteps: Int): String
執行任務,返回任務 ID:
val taskId = sdk.executeTask("請幫我開啟設定應用", 10)pauseTask(taskId: String)
暫停任務:
sdk.pauseTask(taskId)resumeTask(taskId: String)
恢複任務:
sdk.resumeTask(taskId)cancelTask(taskId: String)
取消任務:
sdk.cancelTask(taskId)狀態查詢
getState(): MobileAgentState
擷取當前串連狀態:
val state = sdk.getState()
when (state) {
MobileAgentState.CONNECTED -> // 已串連
MobileAgentState.CONNECTING -> // 串連中
MobileAgentState.DISCONNECTED -> // 已斷開
}Flow API
SDK 提供 events Flow,包含所有事件類型。可以根據需要使用 filterIsInstance 過濾特定事件:
// 收集所有事件
sdk.events.collect { event ->
when (event) {
is MobileAgentEvent.StateChanged -> // 處理狀態變化
is MobileAgentEvent.TaskResult -> // 處理任務結果
is MobileAgentEvent.Stream -> // 處理流式資料
// ...
}
}
// 只收集特定類型的事件
sdk.events
.filterIsInstance<MobileAgentEvent.StateChanged>()
.collect { event ->
// 只處理狀態變化
}事件類型:
事件類型 | 說明 |
| 串連狀態變化 |
| Todo 列表更新 |
| 任務開始 |
| 步驟開始 |
| 流式資料 |
| 步驟結束 |
| 任務結果 |
回調介面
MobileAgentCallback 提供以下回調方法(所有方法都有空實現,可選擇性重寫):
方法 | 說明 |
| 串連狀態變化 |
| Todo 列表更新 |
| 任務開始 |
| 步驟開始 |
| 流式資料 |
| 步驟結束 |
| 任務結果 |
| 原始 JSON 訊息(未解析) |
資料類
MobileAgentState
串連狀態枚舉:
enum class MobileAgentState {
DISCONNECTED, // 未串連
CONNECTING, // 串連中
CONNECTED // 已串連
}TodoItem
Todo 清單項目:
data class TodoItem(
val id: String,
val title: String,
val status: String,
val details: String
)MobileAgentEvent
事件密封類,所有事件類型:
sealed class MobileAgentEvent {
data class StateChanged(val state: MobileAgentState)
data class TodoWrite(val todos: Array<TodoItem>, val count: Int)
data class TaskStart(val taskId: String, val task: String, val maxSteps: Int)
data class StepStart(val taskId: String, val step: Int)
data class Stream(val taskId: String, val step: Int, val agent: String, val contentType: String, val content: String)
data class StepEnd(val taskId: String, val step: Int, val status: String, val summary: String)
data class TaskResult(val taskId: String, val result: String, val resultText: String, val success: Boolean, val actualSteps: Int)
}進階用法
解析"思考"內容
MobileAgent 在流式輸出中會使用 <conclusion> 標籤包裹 Agent 的思考過程。可以從 Stream 事件中提取這些內容。
思考內容的格式
Agent 在輸出時會使用 XML 標籤標記思考內容:
<conclusion>這裡是 Agent 的思考過程...</conclusion>Kotlin 實現樣本
以下代碼示範如何在 Kotlin 中即時解析"思考"內容:
import com.wuying.mobileagentsdk.MobileAgentSdk
import com.wuying.mobileagentsdk.MobileAgentEvent
class ConclusionParser {
private val conclusionBuffer = StringBuilder()
private var inConclusion = false
/**
* 處理流式資料,提取 conclusion 內容
*/
fun processStream(content: String): String? {
val result = StringBuilder()
for (char in content) {
if (!inConclusion) {
// 不在 conclusion 內,檢查是否開始出現 <conclusion>
conclusionBuffer.append(char)
// 檢查是否出現 <conclusion>
if (conclusionBuffer.length >= 12) {
if (conclusionBuffer.endsWith("<conclusion>")) {
inConclusion = true
conclusionBuffer.clear()
result.append("[思考] ")
} else if (conclusionBuffer.length > 12) {
// 保留最後 11 個字元
conclusionBuffer.delete(0, conclusionBuffer.length - 11)
}
}
} else {
// 在 conclusion 內,檢查是否結束
conclusionBuffer.append(char)
// 檢查是否出現 </conclusion>
if (conclusionBuffer.length >= 13) {
if (conclusionBuffer.endsWith("</conclusion>")) {
// 找到結束標籤
inConclusion = false
val conclusionContent = conclusionBuffer.toString()
.dropLast(13) // 移除 </conclusion>
.trim()
if (conclusionContent.isNotEmpty()) {
result.append(conclusionContent).append("\n")
}
conclusionBuffer.clear()
continue
} else if (conclusionBuffer.length > 13) {
// 保留最後 12 個字元
conclusionBuffer.delete(0, conclusionBuffer.length - 12)
}
}
// 如果不是結束標籤的一部分,立即輸出
if (!couldBeEndTag(conclusionBuffer.toString())) {
result.append(char)
}
}
}
return if (result.isNotEmpty()) result.toString() else null
}
/**
* 檢查緩衝區末尾是否可能是結束標籤的首碼
*/
private fun couldBeEndTag(buffer: String): Boolean {
val prefixes = listOf(
"<", "</", "</c", "</co", "</con", "</conc",
"</concl", "</conclu", "</conclus", "</conclusi",
"</conclusio", "</conclusion"
)
return prefixes.any { buffer.endsWith(it) }
}
/**
* 重設解析器狀態
*/
fun reset() {
conclusionBuffer.clear()
inConclusion = false
}
}
// 使用樣本
MobileAgentSdk().use { sdk ->
val parser = ConclusionParser()
lifecycleScope.launch {
sdk.events
.filterIsInstance<MobileAgentEvent.Stream>()
.collect { event ->
// 解析並輸出
val output = parser.processStream(event.content)
if (output != null) {
print(output)
}
}
}
sdk.connect("wss://localhost:30005/ws")
}簡化版本(完整接收後解析)
如果不需要即時資料流式輸出,可以等待完整內容後再解析:
/**
* 從完整內容中提取所有 conclusion 內容
*/
fun extractConclusions(content: String): List<String> {
val conclusions = mutableListOf<String>()
val regex = "<conclusion>(.*?)</conclusion>".toRegex(RegexOption.DOT_MATCHES_ALL)
for (match in regex.findAll(content)) {
val conclusion = match.groupValues[1].trim()
if (conclusion.isNotEmpty()) {
conclusions.add(conclusion)
}
}
return conclusions
}
// 使用樣本
lifecycleScope.launch {
sdk.events
.filterIsInstance<MobileAgentEvent.TaskResult>()
.collect { event ->
// 從任務結果中提取思考內容
val conclusions = extractConclusions(event.resultText)
println("Agent 的思考過程:")
conclusions.forEachIndexed { index, conclusion ->
println("${index + 1}. $conclusion")
}
}
}輸出樣本
[思考] 使用者想要開啟設定應用,我需要先找到設定應用的包名和 Activity。
[思考] 通過查詢系統應用列表,我找到了設定應用:com.android.settings/.Settings
[思考] 現在我將啟動設定應用。
任務完成:已成功開啟設定應用同時使用 Flow 和 Callback
兩種方式可以同時使用,互不干擾:
MobileAgentSdk().use { sdk ->
// 使用 Flow 處理主要邏輯
lifecycleScope.launch {
sdk.events
.filterIsInstance<MobileAgentEvent.TaskResult>()
.collect { event ->
// 處理任務結果
}
}
// 使用 Callback 記錄日誌
sdk.setCallback(object : MobileAgentCallback {
override fun onRawMessage(rawJson: String) {
// 記錄原始訊息用於調試
Log.d("SDK_RAW", rawJson)
}
})
sdk.connect("wss://localhost:30005/ws")
}錯誤處理
try {
MobileAgentSdk().use { sdk ->
sdk.connect("wss://localhost:30005/ws")
}
} catch (e: IllegalStateException) {
Log.e("SDK", "SDK 建立失敗", e)
} catch (e: Exception) {
Log.e("SDK", "串連錯誤", e)
}檢查 SDK 狀態
val sdk = MobileAgentSdk()
// 檢查是否已銷毀
if (sdk.isDestroyed()) {
Log.w("SDK", "SDK 已被銷毀")
}
// 檢查是否有效
sdk.checkNotDestroyed() // 如果已銷毀會拋出異常ProGuard 配置
如果啟用了代碼混淆,需要保留 SDK 的 native 方法。SDK 已包含 proguard-rules.pro 檔案,通常不需要額外配置。
如需手動設定:
# MobileAgentSDK
-keep class com.wuying.mobileagentsdk.** { *; }
-keepclassmembers class com.wuying.mobileagentsdk.MobileAgentSdk {
private native <methods>;
}依賴
SDK 的運行時依賴非常精簡:
dependencies {
// 唯一的運行時依賴
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
}不包含任何 使用者介面架構(AppCompat、Material 等),保持輕量。