すべてのプロダクト
Search
ドキュメントセンター

Cloud Phone:MobileAgentSDK for Android

最終更新日:May 22, 2026

MobileAgentSDK は、C++ MobileAgent SDK をラップし、Kotlin/Java フレンドリーなインターフェイスを提供する Android ライブラリです。WebSocket を介して MobileAgent サーバーに接続し、エージェントタスクの実行、管理、監視を行います。

SDKのダウンロード

MobileAgentSDK for Android

使用方法

AARのインポート

生成された AAR ファイルをプロジェクトにコピーし、build.gradle ファイルに次の依存関係を追加します。

dependencies {
    implementation(files('libs/sdk-release.aar'))
}

基本的な使用方法

オプション1:フローの使用 (推奨)

フローは、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", "Connected")
                            // タスクを実行します。
                            sdk.executeTask("Please help me open settings", 10)
                        }
                        MobileAgentState.DISCONNECTED -> {
                            Log.d("SDK", "Disconnected")
                        }
                        else -> {}
                    }
                }
                is MobileAgentEvent.TaskResult -> {
                    Log.d("SDK", "Task complete: ${event.resultText}")
                }
                is MobileAgentEvent.Stream -> {
                    Log.d("SDK", "Stream data: ${event.content}")
                }
                else -> {}
            }
        }
    }

    // 特定のタイプのイベントのみを収集することもできます。
    lifecycleScope.launch {
        sdk.events
            .filterIsInstance<MobileAgentEvent.StateChanged>()
            .collect { event ->
                Log.d("SDK", "State: ${event.state}")
            }
    }

    lifecycleScope.launch {
        sdk.events
            .filterIsInstance<MobileAgentEvent.TaskResult>()
            .collect { event ->
                Log.d("SDK", "Task result: ${event.resultText}")
            }
    }

    // サーバーに接続します。
    sdk.connect("wss://localhost:30005/ws")

    // use ブロックの最後に SDK は自動的に破棄されます。
}

オプション2:コールバックの使用

このアプローチは、コルーチンを使用しないプロジェクト向けです。

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", "Connected")
                    sdk.executeTask("Please help me open settings", 10)
                }
                MobileAgentState.DISCONNECTED -> {
                    Log.d("SDK", "Disconnected")
                }
                else -> {}
            }
        }

        override fun onTaskResult(
            taskId: String,
            result: String,
            resultText: String,
            success: Boolean,
            actualSteps: Int
        ) {
            Log.d("SDK", "Task complete: $resultText")
        }

        override fun onStream(
            taskId: String,
            step: Int,
            agent: String,
            contentType: String,
            content: String
        ) {
            Log.d("SDK", "Stream data: $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()
}

API

接続管理

connect(url: String)

WebSocket URL を使用してサーバーに接続します。

sdk.connect("wss://localhost:30005/ws")

connectWithTicket(ticket: String)

チケットを使用して接続します (サーバー側のサポートが必要です)。

sdk.connectWithTicket("your-ticket-here")

disconnect()

サーバーから切断します。

sdk.disconnect()

タスク管理

executeTask(task: String, maxSteps: Int): String

タスクを実行し、タスク ID を返します。

val taskId = sdk.executeTask("Please help me open the settings app", 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 -> // 切断済み
}

フロー API

SDK は、すべてのイベントタイプを発行する events フローを提供します。filterIsInstance を使用して、特定のイベントをフィルターできます。

// すべてのイベントを収集します。
sdk.events.collect { event ->
    when (event) {
        is MobileAgentEvent.StateChanged -> // 状態の変更を処理します。
        is MobileAgentEvent.TaskResult -> // タスク結果を処理します。
        is MobileAgentEvent.Stream -> // ストリームデータを処理します。
        // ...
    }
}

// 特定のタイプのイベントのみを収集します。
sdk.events
    .filterIsInstance<MobileAgentEvent.StateChanged>()
    .collect { event ->
        // 状態の変更のみを処理します。
    }

イベントタイプ:

イベントタイプ

説明

MobileAgentEvent.StateChanged

接続状態の変更

MobileAgentEvent.TodoWrite

ToDo リストの更新

MobileAgentEvent.TaskStart

タスクの開始

MobileAgentEvent.StepStart

ステップの開始

MobileAgentEvent.Stream

ストリームデータの受信

MobileAgentEvent.StepEnd

ステップの終了

MobileAgentEvent.TaskResult

タスク結果の受信

コールバック API

MobileAgentCallback インターフェイスは、以下のコールバックメソッドを提供します。すべてのメソッドにはデフォルトの空実装が用意されているため、必要なものだけをオーバーライドできます。

メソッド

説明

onStateChanged(state: MobileAgentState)

接続状態の変更

onTodoWrite(todos: Array<TodoItem>, count: Int)

ToDo リストの更新

onTaskStart(taskId: String, task: String, maxSteps: Int)

タスクの開始

onStepStart(taskId: String, step: Int)

ステップの開始

onStream(taskId, step, agent, contentType, content)

ストリームデータの受信

onStepEnd(taskId, step, status, summary)

ステップの終了

onTaskResult(taskId, result, resultText, success, actualSteps)

タスク結果の受信

onRawMessage(rawJson: String)

未解析の生JSONメッセージ

データクラス

MobileAgentState

接続状態を表す enum:

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> タグを使用して、エージェントの思考プロセスをラップします。このコンテンツは Stream イベントから抽出できます。

<conclusion> のフォーマット

エージェントは、XML タグを使用して出力内の思考プロセスをマークします。

<conclusion>This is the agent's thought process...</conclusion>

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("[Thought] ")
                    } 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's thought process:")
            conclusions.forEachIndexed { index, conclusion ->
                println("${index + 1}. $conclusion")
            }
        }
}

出力

[思考] ユーザーは設定アプリを開きたいようです。パッケージ名とアクティビティを見つける必要があります。
[思考] システムアプリリストを照会して、設定アプリを見つけました: com.android.settings/.Settings
[思考] これから設定アプリを起動します。

タスク完了: 設定アプリを正常に開きました。

フローとコールバックの併用

両方のアプローチを干渉することなく同時に使用できます。

MobileAgentSdk().use { sdk ->
    // フローを使用してメインロジックを処理します。
    lifecycleScope.launch {
        sdk.events
            .filterIsInstance<MobileAgentEvent.TaskResult>()
            .collect { event ->
                // タスク結果を処理します。
            }
    }

    // ログ記録にコールバックを使用します。
    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", "Failed to create SDK", e)
} catch (e: Exception) {
    Log.e("SDK", "Connection error", e)
}

SDKのステータスの確認

val sdk = MobileAgentSdk()

// SDK が破棄済みかどうかを確認します。
if (sdk.isDestroyed()) {
    Log.w("SDK", "The SDK has been destroyed.")
}

// SDK が有効かどうかを確認します。
sdk.checkNotDestroyed()  // 破棄済みの場合は例外をスローします。

ProGuardの設定

コードの難読化を有効にする場合は、SDK のネイティブメソッドを保持する必要があります。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")
}

軽量性を維持するため、SDK には AppCompat や Material などの UI フレームワークは含まれていません。