Release v6.11.0-beta.2 (#16916)

## Summary by Sourcery

通过将串口配置与命令执行分离,并对共享客户端实例的访问强制使用互斥锁保护,改进 ADB-lite 客户端管理。

改进内容:
- 添加锁定辅助工具,在执行 connect、shell、exec、push 和交互式 shell 命令之前,验证并保护对共享 ADB-lite
客户端的访问。
- 在 PlatformIO/AdbLiteIO 上引入可配置的 ADB
串口,并在客户端(重新)初始化时提供安全机制,同时在客户端创建失败时记录错误日志。
- 调整 ADB 释放逻辑,仅在存在 ADB-lite 客户端时才发出释放命令,从而减少不必要的操作。
- 连接 AdbController,将设备地址向下传递到平台 I/O 层,以在各类命令中实现 ADB-lite 客户端的一致使用。

<details>
<summary>Original summary in English</summary>

## Summary by Sourcery

Improve ADB-lite client management by separating serial configuration
from command execution and enforcing mutex-guarded access to the shared
client instance.

Enhancements:
- Add a locking helper to validate and guard access to the shared
ADB-lite client before executing connect, shell, exec, push, and
interactive shell commands.
- Introduce a configurable ADB serial on PlatformIO/AdbLiteIO with safe
client (re)initialization and error logging when client creation fails.
- Adjust ADB release logic to only issue release commands when an
ADB-lite client is present, reducing unnecessary operations.
- Wire AdbController to propagate the device address into the platform
I/O layer for consistent ADB-lite client usage across commands.

</details>
This commit is contained in:
uye
2026-05-29 01:42:01 +08:00
committed by GitHub
8 changed files with 141 additions and 61 deletions

View File

@@ -63,37 +63,38 @@ description: 根据提交记录、PR、diff、现有 CHANGELOG 与历史 tag 内
- patch 版详细区块只能写“自上一发布版本之后新增的变化”,不能把更早正式版已经出现过的条目整段复制到当前 patch 版本下面。
- 例如生成 v6.10.4 时v6.10.4 区块只能写 v6.10.3 之后的新变化v6.10.0、v6.10.1、v6.10.2、v6.10.3 的既有内容应保留在历史区块,而不是重新抄进 v6.10.4。
- 若非 patch 版本不同,则直接根据现有内容组织该版本及其历史区块。
### 5. patch 版本的 Highlights 复用规则
- patch 版本(例如 v6.10.4 相对于 v6.10.3如果没有用户可感知的重要新功能或重大变化必须直接复用其正式版父版本v6.10.3)的 Highlights 内容,不得自行重写或另起一套。
- patch 版本复用 Highlights 时,只改顶部版本号标题(例如 `## v6.10.3``## v6.10.4`Highlights 正文原样保留。
- 当 patch 版本确实包含用户可感知的重要新变化时(例如新增了重大功能、改变了核心交互),可以为 Highlights 追加新段落,但必须保留原有 Highlights 内容,新段落追加在末尾。
### 5. patch / 测试版的 Highlights 复用规则
### 6. patch 版本编辑的完整结构
- patch 版本(例如 v6.10.4 相对于 v6.10.3)和测试版(例如 v6.11.0-beta.2 相对于 v6.11.0-beta.1)如果没有用户可感知的重要新功能或重大变化,必须直接复用其父版本的 Highlights 内容,不得自行重写或另起一套。
- 复用 Highlights 时,只改顶部版本号标题和发版日期(例如 `## v6.11.0-beta.1 (2026-05-27)``## v6.11.0-beta.2 (2026-05-29)`Highlights 正文原样保留。
- 当 patch 版本或测试版确实包含用户可感知的重要新变化时(例如新增了重大功能、改变了核心交互),可以为 Highlights 追加新段落,但必须保留原有 Highlights 内容,新段落追加在末尾。
- 输出文件的结构必须严格遵循以下层次,不得把 patch 版本的详细内容插入到父版本的 Highlights 与详细内容之间:
1. 顶部:`## vX.Y.Z`patch 版本标题)
### 6. patch / 测试版编辑的完整结构
- 输出文件的结构必须严格遵循以下层次,不得把 patch 版本或测试版的详细内容插入到父版本的 Highlights 与详细内容之间:
1. 顶部:`## vX.Y.Z (YYYY-MM-DD)`patch / 测试版标题,含发版日期)
2. `### Highlights`(复用父版本内容,或在有必要时追加新段落)
3. 英文 Highlights 折叠块
4. `----`
5. `以下是详细内容:`
6. `<details open><summary><b>vX.Y.Z</b></summary>`(当前版本详细内容,默认展开)
7. `<details><summary><b>vX.Y.Z-1</b></summary>`(上一 patch 版本,默认收起)
6. `<details open><summary><b>vX.Y.Z (YYYY-MM-DD)</b></summary>`(当前版本详细内容,默认展开)
7. `<details><summary><b>vX.Y.Z-1 (YYYY-MM-DD)</b></summary>`(上一 patch 版本,默认收起)
8. 更早版本各自独立折叠块...
9. `<details><summary><b>vX.Y.0</b></summary>`(正式版,默认收起)
- 每个版本的详细内容各自放入独立的 `<details>` 折叠块,当前目标版本使用 `<details open>` 默认展开,其余默认收起。
9. `<details><summary><b>vX.Y.0 (YYYY-MM-DD)</b></summary>`(正式版,默认收起)
- 每个版本的详细内容各自放入独立的 `<details>` 折叠块,`<summary>` 内格式为 `<b>vX.Y.Z (YYYY-MM-DD)</b>`(版本号 + 发版日期),当前目标版本使用 `<details open>` 默认展开,其余默认收起。
- 折叠块内只保留详细内容(改进、修复等),不重复 Highlights不写 `## vX.Y.Z` 子标题(`<summary>` 已提供版本标识)。
### 5. Highlights 必须中英双语且先中后英
### 7. Highlights 必须中英双语且先中后英
- 输出顶部必须包含当前目标版本,例如 ## vX.Y.Z。
- 输出顶部必须包含当前目标版本和发版日期,例如 `## vX.Y.Z (2026-05-29)`
- 必须包含 ### Highlights。
- 中文 Highlights 直接展示,不折叠。
- 英文 Highlights 放入折叠块:`<details><summary><b>English</b></summary>` ... `</details>`
- 中文与英文都应按主题分段,标题简洁明确,正文面向最终用户,不是 commit 列表翻译。
- Highlights 只总结本次版本中最值得强调的变化,不要把所有条目机械搬进去。
### 6. 必须过滤的噪音项
### 8. 必须过滤的噪音项
- 删除或忽略纯 bot 自动生成的 changelog、update、release 条目。
- 删除显式的 Release 发布记录,例如 Release vX.Y.Z。
@@ -120,9 +121,9 @@ description: 根据提交记录、PR、diff、现有 CHANGELOG 与历史 tag 内
- 把同一功能拆成多条重复表述,例如同一个生息演算功能拆成多个相近新增或改进条目。
- 保留玩梗、口语化、半成品标题,例如不会现在还有人选沙中遗火吧、特意删的 PNS 怎么又给加回来了。
- 机械沿用 commit type 导致分类错误,例如把用户能感知的修复放进其他,把兼容性提升放进新增。
- patch 版本没有用户可感知的重要新变化,却自行重写了独立的 Highlights而非复用父版本内容。
- patch 版本或测试版没有用户可感知的重要新变化,却自行重写了独立的 Highlights而非复用父版本内容。
- 把 patch 版本的详细内容插入到父版本的 Highlights 与详细内容之间,破坏了文件结构。
- patch 版的历史区块中重复保留了 Highlights 和"以下是详细内容:"引导语,这些应只在顶部出现一次。
- patch / 测试版的历史区块中重复保留了 Highlights 和"以下是详细内容:"引导语,这些应只在顶部出现一次。
## Output Requirements
@@ -174,7 +175,7 @@ English summary paragraph.
以下是详细内容:
<details open>
<summary><b>vX.Y.Z</b></summary>
<summary><b>vX.Y.Z (YYYY-MM-DD)</b></summary>
### 改进 | Improved
@@ -191,7 +192,7 @@ English summary paragraph.
</details>
<details>
<summary><b>vX.Y.1</b></summary>
<summary><b>vX.Y.1 (YYYY-MM-DD)</b></summary>
### 改进 | Improved
@@ -204,7 +205,7 @@ English summary paragraph.
</details>
<details>
<summary><b>vX.Y.0</b></summary>
<summary><b>vX.Y.0 (YYYY-MM-DD)</b></summary>
### 新增 | New
@@ -230,7 +231,7 @@ English summary paragraph.
- 是否已经按模块正确分类、排序,并保持中文在前、英文在后?
- 是否已经输出完整 Markdown而不是说明文字或代码块
- 如果是 patch 版本且没有用户可感知的重要新变化,是否复用了父版本的 Highlights 而非自行重写?
- patch 版的详细内容是否紧跟在"以下是详细内容:"之后,而非插入到父版本的 Highlights 下方?
- patch / 测试版的详细内容是否紧跟在"以下是详细内容:"之后,而非插入到父版本的 Highlights 下方?
- 历史版本区块中是否只保留详细内容,没有重复 Highlights 和引导语?
- 英文 Highlights 是否放入 `<details>` 折叠块(中文不折叠)?
- 每个版本的详细内容是否各自放入独立的 `<details>` 折叠块?

View File

@@ -1,4 +1,4 @@
## v6.11.0-beta.1
## v6.11.0-beta.2
### Highlights
@@ -36,7 +36,18 @@ The settings page now features a search bar with keyword highlighting and sectio
以下是详细内容:
<details open>
<summary><b>v6.11.0-beta.1</b></summary>
<summary><b>v6.11.0-beta.2 (2026-05-29)</b></summary>
### 修复 | Fix
* 修复 adb-lite 在连接阶段未正确绑定当前设备,减少切换设备或并发执行命令时的连接异常 (#16853) @wangl-cc
* 修复快捷切换在开启基建退出提醒时无法退出基建的问题 @ABA2396
* 修复修复快捷切换在遇到 LoadingText 时无法跳出循环的问题 @ABA2396
</details>
<details>
<summary><b>v6.11.0-beta.1 (2026-05-29)</b></summary>
### 新增 | New

View File

@@ -6,6 +6,13 @@
"maskRange": [150, 255],
"postDelay": 500
},
"QuickSwitch@Confirm": {
"Doc": "小房子切换后如果弹出返回确认框,由 QuickSwitch 自己处理并回到调用方",
"template": "PopupConfirm.png",
"action": "ClickSelf",
"roi": [630, 400, 650, 160],
"next": ["QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"]
},
"QuickSwitch@ToHome": {
"Doc": "优先使用小房子快捷回到首页,成功后通过 #back 回到调用方",
"algorithm": "JustReturn",
@@ -16,13 +23,13 @@
"Doc": "小房子下拉框中的首页图标",
"template": "QuickSwitch/Home.png",
"roi": [11, 206, 164, 149],
"next": ["#self", "QuickSwitch@ToHome@Entry@LoadingText", "QuickSwitch@ToHome@Entry@LoadingIcon", "#back"]
"next": ["QuickSwitch@ToHome@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"]
},
"QuickSwitch@ToHome@Open": {
"baseTask": "QuickSwitch@Base",
"template": "QuickSwitch/Menu.png",
"roi": [197, 0, 144, 134],
"next": ["QuickSwitch@ToHome@Entry", "#self"]
"next": ["QuickSwitch@ToHome@Entry", "QuickSwitch@ToHome@Open"]
},
"QuickSwitch@ToTerminal": {
"Doc": "优先使用小房子快捷切到终端,成功后通过 #back 回到调用方",
@@ -34,18 +41,13 @@
"Doc": "小房子下拉框中的终端图标",
"template": "QuickSwitch/Terminal.png",
"roi": [450, 0, 159, 148],
"next": [
"#self",
"QuickSwitch@ToTerminal@Entry@LoadingText",
"QuickSwitch@ToTerminal@Entry@LoadingIcon",
"#back"
]
"next": ["QuickSwitch@ToTerminal@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"]
},
"QuickSwitch@ToTerminal@Open": {
"baseTask": "QuickSwitch@Base",
"template": "QuickSwitch/Menu.png",
"roi": [197, 0, 144, 134],
"next": ["QuickSwitch@ToTerminal@Entry", "#self"]
"next": ["QuickSwitch@ToTerminal@Entry", "QuickSwitch@ToTerminal@Open"]
},
"QuickSwitch@ToOperBox": {
"Doc": "优先使用小房子快捷切到干员页面,成功后通过 #back 回到调用方",
@@ -57,13 +59,13 @@
"Doc": "小房子下拉框中的干员图标",
"template": "QuickSwitch/OperBox.png",
"roi": [226, 204, 163, 154],
"next": ["#self", "QuickSwitch@ToOperBox@Entry@LoadingText", "QuickSwitch@ToOperBox@Entry@LoadingIcon", "#back"]
"next": ["QuickSwitch@ToOperBox@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"]
},
"QuickSwitch@ToOperBox@Open": {
"baseTask": "QuickSwitch@Base",
"template": "QuickSwitch/Menu.png",
"roi": [197, 0, 144, 134],
"next": ["QuickSwitch@ToOperBox@Entry", "#self"]
"next": ["QuickSwitch@ToOperBox@Entry", "QuickSwitch@ToOperBox@Open"]
},
"QuickSwitch@ToInfrast": {
"Doc": "优先使用小房子快捷切到基建页面,成功后通过 #back 回到调用方",
@@ -75,13 +77,13 @@
"Doc": "小房子下拉框中的基建图标",
"template": "QuickSwitch/Infrast.png",
"roi": [610, 0, 150, 147],
"next": ["#self", "QuickSwitch@ToInfrast@Entry@LoadingText", "QuickSwitch@ToInfrast@Entry@LoadingIcon", "#back"]
"next": ["QuickSwitch@ToInfrast@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"]
},
"QuickSwitch@ToInfrast@Open": {
"baseTask": "QuickSwitch@Base",
"template": "QuickSwitch/Menu.png",
"roi": [197, 0, 144, 134],
"next": ["QuickSwitch@ToInfrast@Entry", "#self"]
"next": ["QuickSwitch@ToInfrast@Entry", "QuickSwitch@ToInfrast@Open"]
},
"QuickSwitch@ToRecruit": {
"Doc": "优先使用小房子快捷切到公开招募页面,成功后通过 #back 回到调用方",
@@ -93,13 +95,13 @@
"Doc": "小房子下拉框中的公开招募图标",
"template": "QuickSwitch/Recruit.png",
"roi": [879, 207, 152, 152],
"next": ["#self", "QuickSwitch@ToRecruit@Entry@LoadingText", "QuickSwitch@ToRecruit@Entry@LoadingIcon", "#back"]
"next": ["QuickSwitch@ToRecruit@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"]
},
"QuickSwitch@ToRecruit@Open": {
"baseTask": "QuickSwitch@Base",
"template": "QuickSwitch/Menu.png",
"roi": [197, 0, 144, 134],
"next": ["QuickSwitch@ToRecruit@Entry", "#self"]
"next": ["QuickSwitch@ToRecruit@Entry", "QuickSwitch@ToRecruit@Open"]
},
"QuickSwitch@ToGacha": {
"Doc": "优先使用小房子快捷切到干员寻访页面,成功后通过 #back 回到调用方",
@@ -111,13 +113,13 @@
"Doc": "小房子下拉框中的干员寻访图标",
"template": "QuickSwitch/Gacha.png",
"roi": [1003, 208, 155, 149],
"next": ["#self", "QuickSwitch@ToGacha@Entry@LoadingText", "QuickSwitch@ToGacha@Entry@LoadingIcon", "#back"]
"next": ["QuickSwitch@ToGacha@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"]
},
"QuickSwitch@ToGacha@Open": {
"baseTask": "QuickSwitch@Base",
"template": "QuickSwitch/Menu.png",
"roi": [197, 0, 144, 134],
"next": ["QuickSwitch@ToGacha@Entry", "#self"]
"next": ["QuickSwitch@ToGacha@Entry", "QuickSwitch@ToGacha@Open"]
},
"QuickSwitch@ToMall": {
"Doc": "优先使用小房子快捷切到采购中心页面,成功后通过 #back 回到调用方",
@@ -129,12 +131,12 @@
"Doc": "小房子下拉框中的采购中心图标",
"template": "QuickSwitch/Mall.png",
"roi": [1117, 207, 163, 150],
"next": ["#self", "QuickSwitch@ToMall@Entry@LoadingText", "QuickSwitch@ToMall@Entry@LoadingIcon", "#back"]
"next": ["QuickSwitch@ToMall@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"]
},
"QuickSwitch@ToMall@Open": {
"baseTask": "QuickSwitch@Base",
"template": "QuickSwitch/Menu.png",
"roi": [197, 0, 144, 134],
"next": ["QuickSwitch@ToMall@Entry", "#self"]
"next": ["QuickSwitch@ToMall@Entry", "QuickSwitch@ToMall@Open"]
}
}

View File

@@ -2192,7 +2192,13 @@
},
"MallBegin": {
"algorithm": "JustReturn",
"next": ["Mall", "Mall#next", "MallBegin@QuickSwitch@ToMall#next", "Mall@ReturnButtons#next", "Mall@CloseAnnos#next"]
"next": [
"Mall",
"Mall#next",
"MallBegin@QuickSwitch@ToMall#next",
"Mall@ReturnButtons#next",
"Mall@CloseAnnos#next"
]
},
"Mall": {
"action": "DoNothing",

View File

@@ -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) {

View File

@@ -62,8 +62,15 @@ std::optional<int> 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<int> 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<int> 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<int> 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<adb::client> asst::AdbLiteIO::get_adb_client(std::string_view serial)
std::optional<std::unique_lock<std::mutex>> 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::IOHandler> asst::AdbLiteIO::interactive_shell(const std::string& cmd)
@@ -154,11 +200,17 @@ std::shared_ptr<asst::IOHandler> 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<IOHandlerAdbLite>(get_adb_client(match[1].str())->interactive_shell(command));
return std::make_shared<IOHandlerAdbLite>(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::IOHandler> 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<bool>(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)

View File

@@ -13,6 +13,7 @@
#include "Utils/Logger.hpp"
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
@@ -42,15 +43,17 @@ public:
virtual std::shared_ptr<IOHandler> 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<adb::client> get_adb_client(std::string_view serial);
std::optional<std::unique_lock<std::mutex>> 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<adb::client> m_adb_client = nullptr;
std::string m_adb_serial;
};

View File

@@ -4,6 +4,7 @@
#include <memory>
#include <optional>
#include <string>
#include <string_view>
namespace asst
{
@@ -33,6 +34,8 @@ public:
virtual std::shared_ptr<IOHandler> 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;