Files
go-mobile-uiautomator/libs/jsonrpc.go
2026-03-10 15:49:36 +08:00

176 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package libs
import (
"context"
"encoding/json"
"fmt"
"log"
"strings"
)
// ---------- JSON-RPC 请求/响应结构 ----------
// JsonRpcRequest JSON-RPC 2.0 请求
type JsonRpcRequest struct {
JsonRpc string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params interface{} `json:"params"`
}
// JsonRpcResponse JSON-RPC 2.0 响应
type JsonRpcResponse struct {
JsonRpc string `json:"jsonrpc"`
ID int `json:"id"`
Result *json.RawMessage `json:"result,omitempty"`
Error *JsonRpcError `json:"error,omitempty"`
}
// JsonRpcError JSON-RPC 错误对象
type JsonRpcError struct {
Code int `json:"code"`
Message string `json:"message"`
Data string `json:"data,omitempty"` // Java 堆栈信息
}
// ---------- JSON-RPC 调用函数 ----------
// JsonRpcCall 向 UIAutomator2 服务发送 JSON-RPC 调用
// dev 实现 AdbDevice 接口devicePort 为设备端服务端口
// method 为 RPC 方法名params 为参数
// timeout 为请求超时debug 为 true 时输出调试信息
//
// 返回值为 JSON 原始字节(由调用者根据需要解析)
//
// 可能返回的错误类型:
// - *UiObjectNotFoundError: UI 元素未找到
// - *UiAutomationNotConnectedError: UIAutomation 服务断开
// - *RPCStackOverflowError: Java 端栈溢出
// - *RPCUnknownError: 未知 RPC 错误
// - *RPCInvalidError: 无效的 RPC 响应
func JsonRpcCall(ctx context.Context, dev AdbDevice, devicePort int, method string, params interface{}, timeout float64, debug bool) (json.RawMessage, error) {
// 构造 JSON-RPC 请求体
payload := map[string]interface{}{
"jsonrpc": "2.0",
"id": 1,
"method": method,
"params": params,
}
// 通过 HTTP 发送到 /jsonrpc/0 端点
resp, err := HttpRequest(ctx, dev, devicePort, "POST", "/jsonrpc/0", payload, timeout, debug)
if err != nil {
return nil, err
}
// 解析响应
var rpcResp JsonRpcResponse
if err := json.Unmarshal(resp.Content, &rpcResp); err != nil {
return nil, &RPCInvalidError{Message: fmt.Sprintf("JSON 解析失败: %v", err)}
}
// 处理 RPC 错误
if rpcResp.Error != nil {
return nil, handleRpcError(rpcResp.Error, resp.Text(), params)
}
// 确保有结果字段
if rpcResp.Result == nil {
return nil, &RPCInvalidError{Message: "响应中缺少 result 字段"}
}
return *rpcResp.Result, nil
}
// handleRpcError 根据 JSON-RPC 错误内容映射到具体的 Go 错误类型
func handleRpcError(rpcErr *JsonRpcError, rawText string, params interface{}) error {
code := rpcErr.Code
message := rpcErr.Message
data := rpcErr.Data
if debug := false; debug {
log.Printf("JSON-RPC 错误: code=%d message=%s", code, message)
}
// UIAutomation 未连接
if strings.Contains(rawText, "UiAutomation not connected") {
return &UiAutomationNotConnectedError{Message: "UiAutomation not connected"}
}
if strings.Contains(message, "android.os.DeadObjectException") {
return &UiAutomationNotConnectedError{Message: "android.os.DeadObjectException"}
}
if strings.Contains(message, "android.os.DeadSystemRuntimeException") {
return &UiAutomationNotConnectedError{Message: "android.os.DeadSystemRuntimeException"}
}
// UI 元素未找到
if strings.Contains(message, "uiautomator.UiObjectNotFoundException") {
return &UiObjectNotFoundError{
Code: code,
Message: message,
Params: params,
}
}
// 栈溢出
if strings.Contains(message, "java.lang.StackOverflowError") {
truncated := data
if len(data) > 2000 {
truncated = data[:1000] + "..." + data[len(data)-1000:]
}
return &RPCStackOverflowError{
RPCError: RPCError{
Code: code,
Message: fmt.Sprintf("StackOverflowError: %s", message),
Data: truncated,
Params: params,
},
}
}
// 未知 RPC 错误
return &RPCUnknownError{
RPCError: RPCError{
Code: code,
Message: fmt.Sprintf("未知 RPC 错误: %d %s", code, message),
Data: data,
Params: params,
},
}
}
// ---------- JSON-RPC 动态调用包装器 ----------
// JsonRpcWrapper 提供动态方法名的 JSON-RPC 调用
// 通过记录方法名并在 Call 时发送请求,实现类似 Python 的动态属性访问
type JsonRpcWrapper struct {
// caller 为实际发送 JSON-RPC 请求的函数
caller func(method string, params interface{}, timeout float64) (json.RawMessage, error)
}
// NewJsonRpcWrapper 创建 JSON-RPC 动态调用包装器
func NewJsonRpcWrapper(caller func(method string, params interface{}, timeout float64) (json.RawMessage, error)) *JsonRpcWrapper {
return &JsonRpcWrapper{caller: caller}
}
// Call 发送 JSON-RPC 调用
// method 为 RPC 方法名(如 "click"、"setText"
// params 为方法参数,通常为 []interface{} 或 map[string]interface{}
// timeout 为超时时间0 使用默认值
func (w *JsonRpcWrapper) Call(method string, params interface{}, timeout ...float64) (json.RawMessage, error) {
t := HTTPTimeout
if len(timeout) > 0 && timeout[0] > 0 {
t = timeout[0]
}
return w.caller(method, params, t)
}
// CallResult 发送 JSON-RPC 调用并将结果解析到指定结构体
func (w *JsonRpcWrapper) CallResult(result interface{}, method string, params interface{}, timeout ...float64) error {
raw, err := w.Call(method, params, timeout...)
if err != nil {
return err
}
return json.Unmarshal(raw, result)
}