修改注释及代码重构

This commit is contained in:
zyj
2026-03-10 13:41:18 +08:00
parent 8a7521faff
commit 1be2f4021f
15 changed files with 411 additions and 365 deletions

View File

@@ -12,8 +12,12 @@ import (
"time"
)
// mu 用于保护 ADB 连接的互斥锁,避免并发连接冲突
var mu sync.Mutex
// DialADB 建立到 ADB 服务器的 TCP 连接
// addr 格式为 "host:port",例如 "127.0.0.1:5037"
// timeout 为连接超时时间
func DialADB(addr string, timeout time.Duration) (net.Conn, error) {
mu.Lock()
defer mu.Unlock()
@@ -21,6 +25,8 @@ func DialADB(addr string, timeout time.Duration) (net.Conn, error) {
return d.Dial("tcp", addr)
}
// WriteAdbCmd 向 ADB 连接发送命令
// 协议格式4 位十六进制长度前缀 + 命令内容
func WriteAdbCmd(conn net.Conn, cmd string) error {
header := fmt.Sprintf("%04x", len(cmd))
conn.SetWriteDeadline(time.Now().Add(2 * time.Second))
@@ -28,6 +34,8 @@ func WriteAdbCmd(conn net.Conn, cmd string) error {
return err
}
// readN 从连接中精确读取 n 个字节
// 会持续读取直到收集到足够的字节数,或超时/出错
func readN(conn net.Conn, n int, timeout time.Duration) ([]byte, error) {
buf := make([]byte, n)
total := 0
@@ -43,6 +51,7 @@ func readN(conn net.Conn, n int, timeout time.Duration) ([]byte, error) {
return buf, nil
}
// ReadStatus 读取 ADB 协议的 4 字节状态码(如 "OKAY" 或 "FAIL"
func ReadStatus(conn net.Conn) (string, error) {
b, err := readN(conn, 4, 3*time.Second)
if err != nil {
@@ -51,6 +60,8 @@ func ReadStatus(conn net.Conn) (string, error) {
return string(b), nil
}
// ReadLenFrame 读取一个带长度前缀的数据帧
// 先读取 4 字节十六进制长度前缀,再读取相应长度的数据
func ReadLenFrame(conn net.Conn) ([]byte, error) {
hdr, err := readN(conn, 4, 3*time.Second)
if err != nil {
@@ -66,7 +77,8 @@ func ReadLenFrame(conn net.Conn) ([]byte, error) {
return readN(conn, int(l), 10*time.Second)
}
// transportTo: 指示 adb server 将后续请求路由到指定 serial
// TransportTo 指示 ADB 服务器将后续请求路由到指定设备
// serial 为设备序列号,例如 "emulator-5556"
func TransportTo(conn net.Conn, serial string) error {
if err := WriteAdbCmd(conn, "host:transport:"+serial); err != nil {
return err
@@ -77,15 +89,17 @@ func TransportTo(conn net.Conn, serial string) error {
}
if status == "FAIL" {
msg, _ := ReadLenFrame(conn)
return fmt.Errorf("transport FAIL: %s", string(msg))
return fmt.Errorf("传输失败: %s", string(msg))
}
if status != "OKAY" {
return fmt.Errorf("unexpected transport status: %s", status)
return fmt.Errorf("意外的传输状态: %s", status)
}
conn.SetWriteDeadline(time.Time{})
return nil
}
// ExecShell 在设备上执行 Shell 命令并返回输出结果
// shellCmd 为要执行的 Shell 命令字符串
func ExecShell(conn net.Conn, shellCmd string) ([]byte, error) {
if err := WriteAdbCmd(conn, "shell:"+shellCmd); err != nil {
return nil, err
@@ -96,7 +110,7 @@ func ExecShell(conn net.Conn, shellCmd string) ([]byte, error) {
}
if st != "OKAY" {
msg, _ := ReadLenFrame(conn)
return nil, fmt.Errorf("shell FAIL: %s", string(msg))
return nil, fmt.Errorf("Shell 执行失败: %s", string(msg))
}
var buf bytes.Buffer
@@ -120,7 +134,10 @@ func ExecShell(conn net.Conn, shellCmd string) ([]byte, error) {
return buf.Bytes(), nil
}
// readResponse reads a 4-byte response like OKAY/FAIL and returns it and optional message (for FAIL)
// ReadResponse 读取 ADB 协议响应
// 返回状态码("OKAY"/"FAIL"/其他)、消息内容和错误
// 支持小端 uint32 和 ASCII 十六进制两种长度编码格式
// debug 为 true 时会将调试信息输出到 stderr
func ReadResponse(conn net.Conn, debug bool) (string, []byte, error) {
stb, err := readN(conn, 4, 10*time.Second)
if err != nil {
@@ -128,97 +145,92 @@ func ReadResponse(conn net.Conn, debug bool) (string, []byte, error) {
}
st := string(stb)
if debug {
fmt.Fprintf(os.Stderr, "ReadResponseFixed: status raw hex=%x ascii=%q\n", stb, st)
fmt.Fprintf(os.Stderr, "ReadResponse: 状态原始 hex=%x ascii=%q\n", stb, st)
}
if st == "OKAY" {
return st, nil, nil
}
if st == "FAIL" {
// read next 4 bytes (may be little-endian uint32 length or ASCII hex)
// 读取后续 4 字节(可能是小端 uint32 长度或 ASCII 十六进制长度)
hdr, err := readN(conn, 4, 10*time.Second)
if err != nil {
if debug {
fmt.Fprintf(os.Stderr, "ReadResponseFixed: no length header after FAIL: %v\n", err)
fmt.Fprintf(os.Stderr, "ReadResponse: FAIL 后无长度头: %v\n", err)
}
return st, nil, nil
}
if debug {
fmt.Fprintf(os.Stderr, "ReadResponseFixed: length header raw hex=%x ascii=%q\n", hdr, string(hdr))
fmt.Fprintf(os.Stderr, "ReadResponse: 长度头原始 hex=%x ascii=%q\n", hdr, string(hdr))
}
// try little-endian uint32 first
// 优先尝试小端 uint32 解析
l := int(binary.LittleEndian.Uint32(hdr))
if l > 0 {
msg, err := readN(conn, l, 10*time.Second)
if err != nil {
if debug {
fmt.Fprintf(os.Stderr, "ReadResponseFixed: failed to read %d bytes message: %v\n", l, err)
fmt.Fprintf(os.Stderr, "ReadResponse: 读取 %d 字节消息失败: %v\n", l, err)
}
return st, nil, nil
}
if debug {
fmt.Fprintf(os.Stderr, "ReadResponseFixed: message hex=%x ascii=%q\n", msg, string(msg))
fmt.Fprintf(os.Stderr, "ReadResponse: 消息 hex=%x ascii=%q\n", msg, string(msg))
}
return st, msg, nil
}
// fallback: try ASCII-hex parse (backwards compatibility)
// 回退:尝试 ASCII 十六进制解析(向后兼容)
if n, perr := strconv.ParseInt(string(hdr), 16, 32); perr == nil && n > 0 {
msg, err := readN(conn, int(n), 10*time.Second)
if err != nil {
if debug {
fmt.Fprintf(os.Stderr, "ReadResponseFixed: failed to read ascii-hex message of len %d: %v\n", n, err)
fmt.Fprintf(os.Stderr, "ReadResponse: 读取 ASCII 十六进制消息(长度 %d失败: %v\n", n, err)
}
return st, nil, nil
}
if debug {
fmt.Fprintf(os.Stderr, "ReadResponseFixed: ascii-hex message hex=%x ascii=%q\n", msg, string(msg))
fmt.Fprintf(os.Stderr, "ReadResponse: ASCII 十六进制消息 hex=%x ascii=%q\n", msg, string(msg))
}
return st, msg, nil
}
// neither produced a message
// 两种方式都未解析出消息
return st, nil, nil
}
// unexpected token
// 非预期的状态码
return st, nil, nil
}
// LaunchUiautomator connects to adb server, transports to device, starts uiautomator2,
// and returns an io.Reader for streaming logs.
// LaunchUiautomator 连接 ADB 服务器,路由到指定设备,启动 UIAutomator2 服务
// 启动后持续将服务日志输出到标准输出
// addr 为 ADB 服务器地址serial 为设备序列号
func LaunchUiautomator(addr, serial string) {
conn, err := DialADB(addr, 15*time.Second)
if err != nil {
fmt.Println("dial error:", err)
fmt.Println("连接失败:", err)
return
}
// transport
// adbSend(conn, "host:transport:"+serial)
// resp, _ := adbReadResponse(conn)
// fmt.Println("transport resp:", resp)
TransportTo(conn, serial)
// shell
cmd := "shell:CLASSPATH=/data/local/tmp/u2.jar app_process / com.wetest.uia2.Main"
adbSend(conn, cmd)
resp, _ := adbReadResponse(conn)
fmt.Println("shell resp:", resp)
// 输出流
// 路由到目标设备
if err := TransportTo(conn, serial); err != nil {
fmt.Println("设备路由失败:", err)
return
}
// 启动 UIAutomator2 服务
cmd := "shell:CLASSPATH=/data/local/tmp/u2.jar app_process / com.wetest.uia2.Main"
if err := WriteAdbCmd(conn, cmd); err != nil {
fmt.Println("发送命令失败:", err)
return
}
status, err := ReadStatus(conn)
if err != nil {
fmt.Println("读取状态失败:", err)
return
}
fmt.Println("UIAutomator2 启动状态:", status)
// 持续输出 UIAutomator2 服务日志
io.Copy(os.Stdout, conn)
}
func adbSend(conn net.Conn, cmd string) error {
length := fmt.Sprintf("%04x", len(cmd))
_, err := conn.Write([]byte(length + cmd))
return err
}
func adbReadResponse(conn net.Conn) (string, error) {
buf := make([]byte, 4)
_, err := conn.Read(buf)
if err != nil {
return "", err
}
return string(buf), nil
}

View File

@@ -10,23 +10,25 @@ import (
"time"
)
// DeviceInfo 包含从 adb server 列表和设备端 getprop 收集到的信息
// DeviceInfo 包含从 ADB 服务器设备列表和设备端 getprop 收集到的设备信息
type DeviceInfo struct {
Serial string
State string
Product string
Model string
Device string
TransportID string
Props map[string]string
Serial string // 设备序列号
State string // 设备状态device/offline/unauthorized
Product string // 产品名称
Model string // 设备型号
Device string // 设备代号
TransportID string // ADB 传输 ID
Props map[string]string // 额外属性
}
// AdbDevice 表示一个已建立连接的 ADB 设备
type AdbDevice struct {
Connect net.Conn
Serial string
Connect net.Conn // 到设备的 TCP 连接
Serial string // 设备序列号
}
// ListDevicesRaw: 请求 host:devices 并返回原始 payload
// ListDevicesRaw 向 ADB 服务器请求设备列表并返回原始文本
// addr 为 ADB 服务器地址timeout 为超时时间
func ListDevicesRaw(addr string, timeout time.Duration) (string, error) {
conn, err := DialADB(addr, timeout)
if err != nil {
@@ -43,17 +45,17 @@ func ListDevicesRaw(addr string, timeout time.Duration) (string, error) {
}
if status == "FAIL" {
msg, _ := ReadLenFrame(conn)
return "", fmt.Errorf("adb FAIL: %s", string(msg))
return "", fmt.Errorf("ADB 返回失败: %s", string(msg))
}
if status != "OKAY" {
return "", fmt.Errorf("unexpected status: %s", status)
return "", fmt.Errorf("意外的状态码: %s", status)
}
var parts []string
for {
data, err := ReadLenFrame(conn)
if err != nil {
// treat short read timeout as finish
// 超时视为读取结束
if ne, ok := err.(net.Error); ok && ne.Timeout() {
break
}
@@ -71,7 +73,8 @@ func ListDevicesRaw(addr string, timeout time.Duration) (string, error) {
return strings.Join(parts, ""), nil
}
// ParseDevicesPayload: 解析 host:devices 返回的 payload提取可能的 product/model/device/transport_id
// ParseDevicesPayload 解析 host:devices-l 命令返回的文本
// 提取设备的序列号、状态、product/model/device/transport_id 等信息
func ParseDevicesPayload(payload string) []DeviceInfo {
out := []DeviceInfo{}
lines := strings.Split(payload, "\n")
@@ -80,7 +83,7 @@ func ParseDevicesPayload(payload string) []DeviceInfo {
if ln == "" || strings.HasPrefix(ln, "List of devices attached") {
continue
}
// adb -l 格式通常serial <state> key:val key:val ...
// adb -l 格式serial <state> key:val key:val ...
fields := strings.Fields(ln)
if len(fields) < 2 {
continue
@@ -90,7 +93,7 @@ func ParseDevicesPayload(payload string) []DeviceInfo {
State: fields[1],
Props: map[string]string{},
}
// parse remaining key:val pairs
// 解析剩余的 key:val 键值对
for _, kv := range fields[2:] {
if strings.Contains(kv, ":") {
parts := strings.SplitN(kv, ":", 2)
@@ -106,7 +109,7 @@ func ParseDevicesPayload(payload string) []DeviceInfo {
case "transport_id":
dev.TransportID = v
default:
// store any extra short fields into Props under prefixed key
// 将未知字段存入 Props添加 "short." 前缀
dev.Props["short."+k] = v
}
}
@@ -116,26 +119,24 @@ func ParseDevicesPayload(payload string) []DeviceInfo {
return out
}
// parseGetprop parses getprop output "key]: [value" lines into map
// parseGetprop 解析 getprop 命令的输出
// 输出格式为 "[key]: [value]",解析为 map
func parseGetprop(raw []byte) map[string]string {
m := map[string]string{}
sc := bufio.NewScanner(strings.NewReader(string(raw)))
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
// getprop lines are like: [ro.build.version.release]: [10]
if line == "" {
continue
}
// find first ']:' separator
// safe parse: extract between first '[' and first ']: [' pattern
// simpler: split by "]: [" into two parts after trimming surrounding brackets
// getprop 输出格式:[ro.build.version.release]: [10]
parts := strings.SplitN(line, "]: [", 2)
if len(parts) == 2 {
k := strings.TrimPrefix(parts[0], "[")
v := strings.TrimSuffix(parts[1], "]")
m[k] = v
} else {
// fallback: try split by ": "
// 回退:尝试按 ": " 分割
kv := strings.SplitN(line, ": ", 2)
if len(kv) == 2 {
m[strings.Trim(kv[0], "[]")] = strings.Trim(kv[1], "[]")
@@ -145,6 +146,8 @@ func parseGetprop(raw []byte) map[string]string {
return m
}
// ParseDevicesMap 将设备列表文本解析为 serial → product 的映射
// 同时返回原始行列表
func ParseDevicesMap(payload string) (map[string]string, []string) {
m := make(map[string]string)
lines := strings.Split(payload, "\n")
@@ -170,7 +173,8 @@ func ParseDevicesMap(payload string) (map[string]string, []string) {
return m, lines
}
// Find device serial by product value and return serial (first match)
// FindSerialByProduct 根据产品名称查找设备序列号
// 优先从设备列表中匹配,如果列表中没有 product 字段,则回退到逐设备查询 getprop
func FindSerialByProduct(addr, targetProduct string) (string, error) {
payload, err := ListDevicesRaw(addr, 3*time.Second)
if err != nil {
@@ -182,9 +186,8 @@ func FindSerialByProduct(addr, targetProduct string) (string, error) {
return serial, nil
}
}
// fallback: if no product fields in devices-l, try per-device getprop
payloadBasic, _ := ListDevicesRaw(addr, 3*time.Second) // reuse; could be host:devices if preferred
lines := strings.Split(payloadBasic, "\n")
// 回退:逐设备查询 getprop ro.product.model
lines := strings.Split(payload, "\n")
for _, ln := range lines {
ln = strings.TrimSpace(ln)
if ln == "" || strings.HasPrefix(ln, "List of devices attached") {
@@ -195,12 +198,10 @@ func FindSerialByProduct(addr, targetProduct string) (string, error) {
continue
}
serial := fields[0]
// query getprop ro.product.model for this serial
conn, err := DialADB(addr, 2*time.Second)
if err != nil {
continue
}
// ensure close
defer conn.Close()
if err := TransportTo(conn, serial); err != nil {
continue
@@ -212,91 +213,49 @@ func FindSerialByProduct(addr, targetProduct string) (string, error) {
}
}
}
return "", fmt.Errorf("no device with product=%s found", targetProduct)
return "", fmt.Errorf("未找到产品名为 %s 的设备", targetProduct)
}
// 安装APK到设备
// InstallApkOnDevice 安装 APK 到设备
// addr 为 ADB 服务器地址serial 为设备序列号
// remoteTmp 为 APK 在设备上的临时路径
// pmArgs 为 pm install 的额外参数(如 "-r" 表示覆盖安装),默认为 "-r"
// debug 为 true 时输出调试信息
func InstallApkOnDevice(addr, serial string, remoteTmp string, pmArgs string, debug bool) (string, error) {
conn, err := DialADB(addr, 15*time.Second)
if err != nil {
fmt.Println("dial error:", err)
fmt.Println("连接失败:", err)
return "", err
}
defer conn.Close()
TransportTo(conn, serial)
// 1. 准备远端临时路径
if pmArgs == "" {
pmArgs = "-r"
}
// 执行 shell: pm install ...
// 使用 exec shell非 interactive通过 "shell:<cmd>"
// 执行 pm install 命令
installCmd := "pm install " + pmArgs + " " + remoteTmp
if debug {
fmt.Printf("[DEBUG] running shell command: %s\n", installCmd)
fmt.Printf("[调试] 执行命令: %s\n", installCmd)
}
outBuf, err := ExecShell(conn, installCmd)
if err != nil {
log.Println("[ERROR] ", err)
log.Println("[错误]", err)
return "", err
}
outStr := string(outBuf)
if debug {
fmt.Printf("[DEBUG] pm install output:\n%s\n", outStr)
fmt.Printf("[调试] pm install 输出:\n%s\n", outStr)
}
// 安装完成后删除临时文件
ExecShell(conn, "rm -f "+remoteTmp)
// 5. 根据 pm 输出判断成功pm install 成功通常包含 "Success"
if containsSuccess(outStr) {
// 根据输出判断是否安装成功
if strings.Contains(strings.ToLower(outStr), "success") {
return outStr, nil
}
return outStr, fmt.Errorf("install failed: %s", outStr)
return outStr, fmt.Errorf("安装失败: %s", outStr)
}
func containsSuccess(s string) bool {
// 简单判断:忽略大小写包含 "success"
return (len(s) > 0) && (stringContainsFold(s, "success"))
}
func stringContainsFold(s, sub string) bool {
// 不依赖 strings 包的 ToLower 性能差别,这里直接用标准方法
return (len(s) >= len(sub)) && (IndexFold(s, sub) >= 0)
}
func IndexFold(s, sub string) int {
// 直接使用 strings 包实现(为了清晰,这里直接调用)
return indexFoldUsingStrings(s, sub)
}
func indexFoldUsingStrings(s, sub string) int {
// 实际工程里直接用 strings.Contains(strings.ToLower(s), strings.ToLower(sub))
// 但为了最小示例,这里直接实现:
// 换成标准库实现:
//
// 注意:下面两行才是简洁实现
//
// import "strings"
// return strings.Index(strings.ToLower(s), strings.ToLower(sub))
//
// 这里我们直接调用:
return stringsIndexFold(s, sub)
}
func stringsIndexFold(s, sub string) int {
// 调用标准库
// 把此函数简单实现为:
// strings.Index(strings.ToLower(s), strings.ToLower(sub))
// 以便示例完整可运行
// 这里需要引入 strings 包
// 为了保持示例简洁,我在文件顶部添加下面两行导入:
//
// "strings"
//
// 然后直接实现:
return stringsIndex(stringsToLower(s), stringsToLower(sub))
}
func stringsToLower(s string) string { return strings.ToLower(s) }
func stringsIndex(a, b string) int { return strings.Index(a, b) }

21
adb/doc.go Normal file
View File

@@ -0,0 +1,21 @@
// Package adb 实现了 Android Debug Bridge (ADB) 协议的核心功能。
//
// 本包提供了纯 Go 实现的 ADB 客户端,支持以下功能:
// - 设备发现与连接管理
// - Shell 命令执行
// - 文件同步传输(推送文件到设备)
// - APK 安装
// - UIAutomator2 服务启动
//
// 使用示例:
//
// // 连接 ADB 服务器
// conn, err := adb.DialADB("127.0.0.1:5037", 15*time.Second)
// defer conn.Close()
//
// // 路由到指定设备
// adb.TransportTo(conn, "emulator-5556")
//
// // 执行 Shell 命令
// output, err := adb.ExecShell(conn, "getprop ro.product.model")
package adb

View File

@@ -11,12 +11,16 @@ import (
"time"
)
// maxChunk 定义单次数据块传输的最大字节数64KB
const maxChunk = 64 * 1024
// Sync 封装了 ADB 文件同步协议的操作
type Sync struct {
Conn net.Conn
Conn net.Conn // 已建立的 ADB 连接
}
// InitSync 创建一个文件同步操作实例
// conn 必须是已经通过 TransportTo 路由到目标设备的连接
func InitSync(conn net.Conn) *Sync {
return &Sync{
Conn: conn,
@@ -25,11 +29,12 @@ func InitSync(conn net.Conn) *Sync {
// SyncPushFile 将本地文件推送到设备
// localPath: 本地文件路径
// remotePath: 设备目标路径
// mode: 文件权限 (如 0644)
// debug: 是否打印调试信息
// remotePath: 设备目标路径
// mode: 文件权限如 0644
// debug: 是否输出调试信息
// 返回写入的字节数和错误信息
func (s *Sync) SyncPushFile(localPath, remotePath string, mode int, debug bool) (int64, error) {
// 打开文件
// 打开本地文件
f, err := os.Open(localPath)
if err != nil {
return 0, err
@@ -37,14 +42,19 @@ func (s *Sync) SyncPushFile(localPath, remotePath string, mode int, debug bool)
defer f.Close()
fi, _ := f.Stat()
if debug {
fmt.Printf("[DEBUG] local filesize=%d\n", fi.Size())
fmt.Printf("[调试] 本地文件大小=%d\n", fi.Size())
}
s.StartSync()
// 构造 SEND payload
// 初始化同步模式
if err := s.StartSync(); err != nil {
return 0, err
}
// 构造 SEND 请求remotePath + "," + 文件权限
modeStr := strconv.Itoa(syscall.S_IFREG | mode)
sendPayload := []byte(remotePath + "," + modeStr)
// 写入 "SEND" + 长度 + payload
// 写入 "SEND" 命令头 + 数据长度 + 请求内容
hdr := make([]byte, 8)
copy(hdr[:4], []byte("SEND"))
binary.LittleEndian.PutUint32(hdr[4:], uint32(len(sendPayload)))
@@ -55,19 +65,19 @@ func (s *Sync) SyncPushFile(localPath, remotePath string, mode int, debug bool)
return 0, err
}
if debug {
fmt.Printf("[DEBUG] Wrote SEND payload len=%d path=%s mode=%s\n", len(sendPayload), remotePath, modeStr)
fmt.Printf("[调试] 发送 SEND 请求: 长度=%d 路径=%s 权限=%s\n", len(sendPayload), remotePath, modeStr)
}
// 写入 DATA
// 分块写入文件数据(DATA 命令)
var total int64
buf := make([]byte, maxChunk)
for {
n, rerr := f.Read(buf)
if n > 0 {
hdr := make([]byte, 8)
copy(hdr[:4], []byte("DATA"))
binary.LittleEndian.PutUint32(hdr[4:], uint32(n))
if _, err := s.Conn.Write(hdr); err != nil {
dataHdr := make([]byte, 8)
copy(dataHdr[:4], []byte("DATA"))
binary.LittleEndian.PutUint32(dataHdr[4:], uint32(n))
if _, err := s.Conn.Write(dataHdr); err != nil {
return total, err
}
if _, err := s.Conn.Write(buf[:n]); err != nil {
@@ -83,7 +93,7 @@ func (s *Sync) SyncPushFile(localPath, remotePath string, mode int, debug bool)
}
}
// 发送 DONE
// 发送 DONE 命令,携带文件修改时间戳
done := make([]byte, 8)
copy(done[:4], []byte("DONE"))
mtime := uint32(fi.ModTime().Unix())
@@ -92,7 +102,7 @@ func (s *Sync) SyncPushFile(localPath, remotePath string, mode int, debug bool)
return total, err
}
if debug {
fmt.Println("[DEBUG] Wrote DONE, waiting response")
fmt.Println("[调试] 发送 DONE等待响应")
}
// 读取最终响应
@@ -102,13 +112,15 @@ func (s *Sync) SyncPushFile(localPath, remotePath string, mode int, debug bool)
}
if resp != "OKAY" {
if len(msg) > 0 {
return total, fmt.Errorf("sync failed: %s", string(msg))
return total, fmt.Errorf("同步失败: %s", string(msg))
}
return total, fmt.Errorf("sync failed: %s", resp)
return total, fmt.Errorf("同步失败: %s", resp)
}
return total, nil
}
// StartSync 启动 ADB 同步模式
// 发送 "sync:" 命令并等待 "OKAY" 响应
func (s *Sync) StartSync() error {
if err := WriteAdbCmd(s.Conn, "sync:"); err != nil {
return err
@@ -120,13 +132,15 @@ func (s *Sync) StartSync() error {
if string(tok) != "OKAY" {
_, msg, _ := ReadSyncStatus(s.Conn)
if len(msg) > 0 {
return fmt.Errorf("sync open failed: %s", string(msg))
return fmt.Errorf("同步模式启动失败: %s", string(msg))
}
return fmt.Errorf("sync open failed: %q", string(tok))
return fmt.Errorf("同步模式启动失败: %q", string(tok))
}
return nil
}
// ReadSyncStatus 读取同步协议的状态响应
// 返回状态码("OKAY"/"FAIL"/其他)、失败时的消息内容和错误
func ReadSyncStatus(r io.Reader) (string, string, error) {
hdr := make([]byte, 4)
if _, err := io.ReadFull(r, hdr); err != nil {
@@ -137,6 +151,7 @@ func ReadSyncStatus(r io.Reader) (string, string, error) {
case "OKAY":
return "OKAY", "", nil
case "FAIL":
// 读取 4 字节小端长度 + 对应长度的错误消息
lenBuf := make([]byte, 4)
if _, err := io.ReadFull(r, lenBuf); err != nil {
return "FAIL", "", err
@@ -148,7 +163,7 @@ func ReadSyncStatus(r io.Reader) (string, string, error) {
}
return "FAIL", string(msg), nil
default:
// 非预期状态,直接返回原始字符串,便于上层报错
// 非预期状态,直接返回原始字符串,便于上层排查
return status, "", nil
}
}