一、目标与整体思路
- 目标:为 Android 和 Windows 两端建立一套稳定、可重复的“构建 + 运行 + 调试”工具链,减少手工步骤,避免把调试兼容逻辑写死在业务代码里。
- 约束:
- 客户端代码尽量只承载业务逻辑和必要的跨平台抽象,不为了解决构建/调试问题去做“路径探测”等脏活。
- 所有与平台路径、可执行文件位置相关的逻辑尽量收敛到脚本层。
- 当前结果:
- Android:有
build_android.bat和run_android.bat分别负责“产物构建”和“真机调试运行”,自动生成 gomobile AAR 并跑起 Flutter。 - Windows:有
build_windows.bat和run_windows.bat分别负责 Release 构建与 Debug 调试,自动准备内嵌核心 exe 并跑起 Flutter 桌面端。
- Android:有
二、Android 侧:build / run 脚本与环境修复
1. build_android.bat:构建 AAR 与 APK
- 路径:
scripts/bat/build_android.bat。 - 主要职责:
- 设置 Android 构建相关环境变量:
ANDROID_HOME指向本机 SDK。ANDROID_NDK_HOME指向 NDK。PATH注入 Android Studio 自带的 JBR,避免本机 JDK 版本不一致。
- 在
core目录下执行gomobile bind:- 目标:生成
e2eepan-mobile.aar并输出到client/android/app/libs。
- 目标:生成
- 在
client目录下执行flutter build apk:- 产出标准 Release APK。
- 设置 Android 构建相关环境变量:
- 遇到的典型问题:gomobile 依赖缺失。
- 报错内容集中在找不到
golang.org/x/mobile/bind。 - 根因是 Go 环境中没有拉取对应模块,且没有在有
go.mod的模块目录下做一次go get。 - 修复方案:
- 在任意目录安装 gomobile 命令并初始化:
go install golang.org/x/mobile/cmd/gomobile@latestgomobile init
- 在
core模块下拉取 bind 依赖,使之写入core/go.mod:cd corego get golang.org/x/mobile/bind@latest
- 再执行
build_android.bat,gomobile bind和flutter build apk即可顺利完成。
- 在任意目录安装 gomobile 命令并初始化:
- 报错内容集中在找不到
2. run_android.bat:一键真机调试
- 路径:
scripts/bat/run_android.bat。 - 主要职责:
- 与 build 脚本一样,在
core下先执行一次gomobile bind:- 确保 AAR 是最新的。
- 然后进入
client,执行flutter run:- 在连接的 Android 设备上直接运行 debug 版客户端。
- 与 build 脚本一样,在
- 效果验证:
- 运行脚本后,logcat 中可以看到 Go 内核通过 gomobile 启动的日志,类似:
GoLog: mobile core http server starting on http://127.0.0.1:<port>。
- 同时可以看到大量 MIUI 输入事件日志,证明 Flutter 客户端正常响应操作。
- 运行脚本后,logcat 中可以看到 Go 内核通过 gomobile 启动的日志,类似:
- 经验:
- 将 gomobile AAR 生成放在 run 脚本前半段,可以保证每次真机调试时,native 核心和客户端 Dart 代码始终是配套版本。
- Android 端所有内嵌核心启动逻辑依旧仅存在于 Kotlin + gomobile 层,Flutter 只是发 MethodChannel 请求,工具链细节全部藏在脚本里。
三、Windows 侧:Debug 与 Release 的脚本分工
1. run_windows.bat:Debug 模式调试 + 自动内核
- 路径:
scripts/bat/run_windows.bat。 - 目标:在 Windows 上体验“和 Android 类似的内嵌模式”,即:
flutter run -d windows启动调试版客户端。- 客户端在桌面端同样自动拉起 Go 核心进程。
- 最终实现方式:
- 在
core目录下:- 预先设置核心输出目录:
..\client\build\windows\x64\runner\Debug。
- 若目录不存在,先
mkdir。 - 直接将 Go 核心编译到该目录下:
go build -o "<Debug runner 目录>\e2eepan-core.exe" ./cmd/server。
- 预先设置核心输出目录:
- 然后进入
client,简单执行:flutter run -d windows。
- 在
- 关键设计点:
- 客户端代码始终只按照“自己 exe 同目录下有
e2eepan-core.exe”这个约定工作,不再有任何“向上爬目录找 exe”之类的调试兼容逻辑。 - Debug 模式下 exe 的实际路径由 Flutter 和 CMake 决定,脚本只负责在那个目录里准备好核心 exe。
- 客户端代码始终只按照“自己 exe 同目录下有
- 踩过的坑:
- 第一版实现是客户端在运行时向上爬多级目录寻找
e2eepan-core.exe,虽然能解决问题,但把调试路径细节带进了 AppState,违反了“客户端只负责业务”的原则。 - 后来改为:客户端恢复到只看当前目录,脚本负责把核心放到 Debug runner 目录,这样业务代码保持纯粹,平台适配集中在脚本。
- 第一版实现是客户端在运行时向上爬多级目录寻找
2. build_windows.bat:Release 构建 + Release 内核
- 路径:
scripts/bat/build_windows.bat。 - 目标:产出一个可以直接双击运行、并能自动启动内嵌核心的 Release 包。
- 最终实现方式:
- 在
core目录下:- 预先设置 Release runner 目录:
..\client\build\windows\x64\runner\Release。
- 若目录不存在,先
mkdir。 - 直接将 Go 核心编译到该目录:
go build -o "<Release runner 目录>\e2eepan-core.exe" ./cmd/server。
- 预先设置 Release runner 目录:
- 然后进入
client,执行:flutter build windows。
- 在
- 关键调整:
- 早期版本是先把核心编译到
client\e2eepan-core.exe,再用copy复制到 Release 目录,并且错误输出被重定向到了nul,导致即使 copy 失败也看不到提示,最终 Release 目录缺少内核。 - 现在改为“直接编译到 Release 目录”,整个流程更简单、更不容易出错。
- 早期版本是先把核心编译到
- 结果:
- Release 目录内容包括:
e2eepan_client.exee2eepan-core.exe- 各种 Flutter 运行必需的 dll 和 data 目录。
- 双击
e2eepan_client.exe即可运行完整的“桌面客户端 + 内嵌 Go 核心”。
- Release 目录内容包括:
四、客户端代码与工具链的边界划分
1. 客户端的职责
- 在 Flutter 层:
- 维护
core_mode(external/embedded),并在 Windows 和 Android 上默认使用内嵌模式。 - 在内嵌模式下:
- Android:通过
MethodChannel调NativeCoreService.startEmbeddedCore。 - Windows:在桌面端直接通过
Process.start启动e2eepan-core.exe,路径仅假设“和当前 exe 在同一目录”。
- Android:通过
- 提供若干调试入口(如 Debug 页里的“重启内核 / 查看健康状态”等),帮助验证内核状态与 S3 配置。
- 维护
2. 工具链脚本的职责
- Android:
- 负责准备 gomobile AAR,保证 Android 端的内嵌核心二进制与 Go 源码版本一致。
- 在 run 脚本中同时处理 AAR 更新和 Flutter 启动逻辑,保证“一条命令即可真机调试”。
- Windows:
- 负责在 Debug/Release 对应目录中准备好
e2eepan-core.exe。 - 通过
run_windows.bat和build_windows.bat分别承担“调试体验”和“正式发行”的职责。
- 负责在 Debug/Release 对应目录中准备好
- 共识:
- 所有路径、环境变量、外部工具(gomobile、NuGet 等)的坑,尽量封装在脚本内解决。
- 客户端只依赖少量稳定的约定(例如“内核 exe 在我旁边”),不掺杂临时调试兼容逻辑。
五、常见问题与经验小结
- gomobile 相关错误:
- 如果
gomobile bind报unable to import bind: no Go package in golang.org/x/mobile/bind:- 检查是否在包含
go.mod的模块目录下执行过go get golang.org/x/mobile/bind@latest。 - 确保已经执行过一次
gomobile init。
- 检查是否在包含
- 如果
- PowerShell 语法坑:
- 不要在脚本里混用
&&,在当前 PowerShell 环境下会被解析错误。 - 统一使用一条命令 + 显式工作目录,或者用批处理脚本组合多条命令。
- 不要在脚本里混用
- Flutter Windows 构建依赖 NuGet:
- 第一次运行
flutter build windows或flutter run -d windows时,NuGet 可能会被自动下载。 - 日志中的 “Nuget.exe not found, trying to download or use cached version” 不是错误,只是提示。
- 第一次运行
- 产物与源码分离:
client/.gitignore中忽略了所有*.exe:- 避免将 Windows 可执行文件误提交到仓库。
- 调试与构建产物只存在于本地
build目录和脚本指定的位置。
六、后续可以考虑的优化
- 为这四个脚本增加一个简单的顶层 README 或“脚本使用说明”,帮助新同事快速理解 build/run 流程。
- 在脚本中加入更友好的错误提示(例如明确打印“gomobile 未初始化,请先运行 gomobile init”),进一步降低环境问题的排查成本。
- 为 Windows/Android 的构建增加 CI 任务,定期验证脚本在干净环境下是否仍能顺利运行。