From b2fa9a1c3e5bfa1aca0eeef7703d15adb5ff7966 Mon Sep 17 00:00:00 2001 From: Loong <40141251+wangl-cc@users.noreply.github.com> Date: Thu, 28 May 2026 17:46:30 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=98=BE=E5=BC=8F=E5=A3=B0=E6=98=8E=20a?= =?UTF-8?q?db-lite=20client=20=E5=AF=B9=E5=BA=94=E7=9A=84=20serial=20(#168?= =?UTF-8?q?53)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概述 - 在 `AdbController::connect` 确定目标 address/serial 后,通过 `PlatformIO::set_adb_serial` 显式声明当前 adb 设备。 - NativeIO 默认忽略该调用;AdbLiteIO 使用该 serial 初始化或切换内部 adb-lite client。 - adb-lite 的 `shell`、`exec-out`、`push` 和交互式 shell 只使用已声明的 client,不再从命令路径里隐式初始化 client。 - AdbLiteIO 内部集中校验 client/serial,并用 mutex 覆盖 `m_adb_client` / `m_adb_serial` 以及对 adb-lite client 的调用,避免 `call_command` 与 `interactive_shell` 并发访问同一状态。 ## 背景 #16850 已修复 `adb connect` 被跳过后 adb-lite client 未初始化的问题,并补充了内部锁保护。这个 PR 是后续调整:把 client 初始化入口从命令执行路径挪到连接生命周期中,让状态声明更明确。 ## 修复方式 连接阶段已经知道当前 address/serial,所以在设置 connect/release 命令后调用: ```cpp m_platform_io->set_adb_serial(address); ``` 对 NativeIO 来说这是 no-op;对 AdbLiteIO 来说,这是初始化或切换 `adb::client` 的入口。 后续 adb-lite 命令路径通过 `lock_adb_client(serial)` 统一校验命令中的 `-s ` 与当前 client 对应的 serial 一致,并在同一把锁仍然持有时调用 adb-lite client。这样避免在 `interactive_shell` 中隐式写入 client/serial 状态,也避免把 `m_adb_client` 暴露给无锁调用。 `set_adb_serial` 也改为先创建临时 `adb::client`,创建成功后再提交 `m_adb_serial` / `m_adb_client` 状态;如果创建失败则记录错误并清空 adb-lite client 状态,避免 serial 已切换但 client 仍指向旧对象的不一致状态。 ## 验证 - `git diff --check --cached` - `cmake --build /private/tmp/maa-build-8599-core --parallel 8` - `cmake --install /private/tmp/maa-build-8599-core --config RelWithDebInfo` - 本机 `maa` CLI + Android 35 arm64 AVD: - `maa run connect-only -p adb-lite-test --no-summary -vvv` - 日志显示 `Loading MaaCore from: /private/tmp/maa-test-data-8599/lib` - 日志显示 `Setting adb lite enabled to true` - 日志到达 `Connected` - 日志到达 `FastestWayToScreencap Encode` - 未出现 `adb client not initialized` - 未出现 `failed to create adb-lite client` - 未出现 `fallback to NativeIO` --- src/MaaCore/Controller/AdbController.cpp | 1 + src/MaaCore/Controller/Platform/AdbLiteIO.cpp | 87 +++++++++++++++---- src/MaaCore/Controller/Platform/AdbLiteIO.h | 7 +- src/MaaCore/Controller/Platform/PlatformIO.h | 3 + 4 files changed, 79 insertions(+), 19 deletions(-) diff --git a/src/MaaCore/Controller/AdbController.cpp b/src/MaaCore/Controller/AdbController.cpp index 6e3745ee14..dba7e5b259 100644 --- a/src/MaaCore/Controller/AdbController.cpp +++ b/src/MaaCore/Controller/AdbController.cpp @@ -915,6 +915,7 @@ bool asst::AdbController::connect(const std::string& adb_path, const std::string // 设置配置 connect、release 命令,即使这里不连接,后续也会需要用到 m_adb.connect = m_conn_ctx.replace_cmd(adb_cfg.connect); m_adb.release = m_conn_ctx.replace_cmd(adb_cfg.release); + m_platform_io->set_adb_serial(address); if (need_connect) { // 如果不包含 `:` 且需要连接,connect 命令也不会成功 if (address.find(':') == std::string::npos) { diff --git a/src/MaaCore/Controller/Platform/AdbLiteIO.cpp b/src/MaaCore/Controller/Platform/AdbLiteIO.cpp index fe7e89ca07..08eaa6959d 100644 --- a/src/MaaCore/Controller/Platform/AdbLiteIO.cpp +++ b/src/MaaCore/Controller/Platform/AdbLiteIO.cpp @@ -62,8 +62,15 @@ std::optional asst::AdbLiteIO::call_command( // adb connect if (boost::regex_match(cmd, match, connect_regex)) { + const std::string serial = match[1].str(); + auto lock = lock_adb_client(serial); + if (!lock) { + ret = std::nullopt; + goto ret_exit; + } + try { - pipe_data = get_adb_client(match[1].str())->connect(); + pipe_data = m_adb_client->connect(); ret = 0; goto ret_exit; } @@ -77,11 +84,18 @@ std::optional asst::AdbLiteIO::call_command( // adb shell if (boost::regex_match(cmd, match, shell_regex)) { + const std::string serial = match[1].str(); std::string command = match[2].str(); remove_quotes(command); + auto lock = lock_adb_client(serial); + if (!lock) { + ret = std::nullopt; + goto ret_exit; + } + try { - pipe_data = get_adb_client(match[1].str())->shell(command); + pipe_data = m_adb_client->shell(command); ret = 0; goto ret_exit; } @@ -94,11 +108,18 @@ std::optional asst::AdbLiteIO::call_command( // adb exec-out if (boost::regex_match(cmd, match, exec_regex)) { + const std::string serial = match[1].str(); std::string command = match[2].str(); remove_quotes(command); + auto lock = lock_adb_client(serial); + if (!lock) { + ret = std::nullopt; + goto ret_exit; + } + try { - pipe_data = get_adb_client(match[1].str())->exec(command); + pipe_data = m_adb_client->exec(command); ret = 0; goto ret_exit; } @@ -111,8 +132,15 @@ std::optional asst::AdbLiteIO::call_command( // adb push if (boost::regex_match(cmd, match, push_regex)) { + const std::string serial = match[1].str(); + auto lock = lock_adb_client(serial); + if (!lock) { + ret = std::nullopt; + goto ret_exit; + } + try { - get_adb_client(match[1].str())->push(match[2].str(), match[3].str(), 0644); + m_adb_client->push(match[2].str(), match[3].str(), 0644); ret = 0; goto ret_exit; } @@ -135,17 +163,35 @@ ret_exit: return ret; } -std::shared_ptr asst::AdbLiteIO::get_adb_client(std::string_view serial) +std::optional> asst::AdbLiteIO::lock_adb_client(std::string_view serial) +{ + std::unique_lock lock(m_adb_client_mutex); + if (!m_adb_client || m_adb_serial != serial) { + Log.error("adb client not initialized for serial:", std::string(serial), "current:", m_adb_serial); + return std::nullopt; + } + + return std::move(lock); +} + +void asst::AdbLiteIO::set_adb_serial(std::string_view serial) { std::lock_guard lock(m_adb_client_mutex); const std::string serial_str(serial); - if (!m_adb_client || m_adb_serial != serial_str) { + if (m_adb_client && m_adb_serial == serial_str) { + return; + } + + try { auto adb_client = adb::client::create(serial_str); m_adb_serial = serial_str; m_adb_client = std::move(adb_client); } - - return m_adb_client; + catch (const std::exception& e) { + Log.error("failed to create adb-lite client for serial:", serial_str, e.what()); + m_adb_serial.clear(); + m_adb_client.reset(); + } } std::shared_ptr asst::AdbLiteIO::interactive_shell(const std::string& cmd) @@ -154,11 +200,17 @@ std::shared_ptr asst::AdbLiteIO::interactive_shell(const std::s boost::smatch match; if (boost::regex_match(cmd, match, shell_regex)) { + const std::string serial = match[1].str(); std::string command = match[2].str(); remove_quotes(command); + auto lock = lock_adb_client(serial); + if (!lock) { + return nullptr; + } + try { - return std::make_shared(get_adb_client(match[1].str())->interactive_shell(command)); + return std::make_shared(m_adb_client->interactive_shell(command)); } catch (const std::exception& e) { Log.error("adb shell failed:", e.what()); @@ -173,18 +225,19 @@ std::shared_ptr asst::AdbLiteIO::interactive_shell(const std::s void asst::AdbLiteIO::release_adb(const std::string& adb_release, int64_t timeout) { - // 只在读取 m_adb_client 时持锁,避免 call_command 内部调用 get_adb_client 时死锁 + bool has_adb_client = false; { std::lock_guard lock(m_adb_client_mutex); - if (!m_adb_client) { - return; - } + has_adb_client = static_cast(m_adb_client); } - std::string pipe_data; - std::string sock_data; - auto start_time = std::chrono::steady_clock::now(); - call_command(adb_release, false, pipe_data, sock_data, timeout, start_time); + if (has_adb_client) { + std::string pipe_data; + std::string sock_data; + auto start_time = std::chrono::steady_clock::now(); + + call_command(adb_release, false, pipe_data, sock_data, timeout, start_time); + } } bool asst::AdbLiteIO::remove_quotes(std::string& data) diff --git a/src/MaaCore/Controller/Platform/AdbLiteIO.h b/src/MaaCore/Controller/Platform/AdbLiteIO.h index 1c6d2c3979..7da811d87a 100644 --- a/src/MaaCore/Controller/Platform/AdbLiteIO.h +++ b/src/MaaCore/Controller/Platform/AdbLiteIO.h @@ -13,6 +13,7 @@ #include "Utils/Logger.hpp" #include +#include #include #include @@ -42,15 +43,17 @@ public: virtual std::shared_ptr interactive_shell(const std::string& cmd) override; + virtual void set_adb_serial(std::string_view serial) override; + virtual void release_adb(const std::string& adb_release, int64_t timeout = 20000) override; private: - std::shared_ptr get_adb_client(std::string_view serial); + std::optional> lock_adb_client(std::string_view serial); static bool remove_quotes(std::string& data); // 保护 m_adb_client / m_adb_serial,防止 call_command 与 interactive_shell 并发访问 - mutable std::mutex m_adb_client_mutex; + std::mutex m_adb_client_mutex; std::shared_ptr m_adb_client = nullptr; std::string m_adb_serial; }; diff --git a/src/MaaCore/Controller/Platform/PlatformIO.h b/src/MaaCore/Controller/Platform/PlatformIO.h index f0f67c4476..112bfd18b5 100644 --- a/src/MaaCore/Controller/Platform/PlatformIO.h +++ b/src/MaaCore/Controller/Platform/PlatformIO.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace asst { @@ -33,6 +34,8 @@ public: virtual std::shared_ptr interactive_shell(const std::string& cmd) = 0; + virtual void set_adb_serial(std::string_view) {} + virtual void release_adb(const std::string& adb_release, int64_t timeout = 20000) = 0; bool m_support_socket = false;