使用Go开发移动应用

Hacker News Top 工具

摘要

一位开发者分享了使用Go Mobile为Flutter应用构建后端的经验,详细介绍了通过protobuf和平台通道进行通信的方式。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/07/15 13:44

# 使用Go开发移动应用 来源:https://www.davidsobsessions.com/p/one-year-of-gomobile/ 过去一年里,我一直在用 Go Mobile 开发 Digital Carrot,整体体验非常积极。这篇文章旨在总结我在这个过程中学到的所有东西。 ## 为什么选择 Go Mobile? 简单来说,我非常喜欢 Go 并想用它。更详细的答案是以下几点综合作用的结果: - Digital Carrot 是一个跨平台应用,我希望有一种能轻松编译并在任何地方运行的工具。 - Go 拥有强大的系统功能库,我想利用这些功能让 Digital Carrot 变得可定制、可扩展。其中最重要的是 Expr(用于构建表达式)和 Goja(用于创建 JavaScript 插件)。 ## 我是如何使用 Go Mobile 的? Go Mobile 负责 Digital Carrot 的所有业务逻辑。虽然我很想用 Fyne 做 UI,但尝试后发现它还不够成熟。因此,我选择了 Flutter 作为前端,Go 作为后端。大致架构如下: - Flutter 通过平台通道([platform channels](https://docs.flutter.dev/platform-integration/platform-channels))调用 Swift/Kotlin - API 调用使用二进制编码的 Protobuf 消息 - 原生代码将原始二进制数据转发给 Go - Go 解码 Protobuf 消息并进行响应 - Go 也可以通过与原生 API 交互的接口调用原生代码 ### Flutter 到 Go 的通信 如上所述,Flutter 使用 Protobuf 消息与 Go 通信。这里 **必须** 使用 Protobuf,因为它能让我在 Go 和 Dart 中使用结构体/类,而无需手动大量编解码 JSON 对象。我可以在 Protobuf 中定义消息,并自动在 Dart 和 Go 中获得好用的对象。 Flutter、平台代码和 Go 之间的通信只能使用基本数据类型(字符串、二进制数据、布尔值等),因此 Protobuf 消息非常适合这种场景。 在 Flutter 中定义新的平台通道也很麻烦,因为它们需要在三个地方(Flutter、平台和 Go)定义,所以我最终采用一个单一函数来处理所有 API 调用。这个函数传递一个包含大型 `oneof` 块的消息来定义实际的 API 调用。大致如下: ```protobuf message CarrotAPI { oneof api { Function1API function1 = 10; Function2API function2 = 11; } } ``` `oneof` 块中的每个函数调用类似这样: ```protobuf message Function1API { message Request {} message Response {} Request request = 1; Response response = 2; } ``` Flutter 填充消息的 `request` 部分,将其放入 `CarrotAPI` 对象并发送给 Go。Go 通过 `CarrotAPI.api` 的 switch 语句判断调用的是哪个函数,并填充相应的响应。这种方法有点笨拙,但比每个函数都创建新平台通道要好得多,而且减少了在 Swift 和 Kotlin 中重复的工作量。 具体实现细节我就不展开了,其他文章([Using Go library in Flutter](https://medium.com/flutter-community/using-go-library-in-flutter-a04e3496aa05))对此有更好的解释。 ### Go 到 Swift/Kotlin 的通信 这一部分我纠结了很久,因为 Go 文档在这方面描述得不够清晰。实际上,要将消息从 Go 发送到平台代码,你需要创建一个 Go 接口,然后在平台端实现它,并在调用 Go 代码时传入。 示例如下: 我们先创建一个 Go 接口: ```go type IosMethods interface { // Screentime SetShields([]byte) bool HasScreentimePermissions() bool } ``` Go Mobile 会生成类似的 Objective-C 或 Kotlin 接口: ```objectivec @interface MobileIosMethods : NSObject { } @property(strong, readonly) _Nonnull id _ref; - (BOOL)hasScreentimePermissions; - (BOOL)setShields:(NSData* _Nullable)p0; @end ``` 我们可以在 Swift 或 Kotlin 中实现它,然后传回给 Go: ```swift class GoScreentime: NSObject, MobileIosMethodsProtocol { public func setShields(_ p0: Data?) -> Bool { return setShieldsFromJSONBytes(p0) } public func hasScreentimePermissions() -> Bool { return AuthorizationCenter.shared.authorizationStatus == .approved } } ``` ```swift @main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { [...] self.carrotApi = DigitalCarrot.MobileNewAppleMobileAPI( GoScreentime() ) [...] } } ``` 同样,这些接口只支持基本数据类型。这也是 Protobuf 可能派上用场的地方,但我最终没有用它,因为从 Go 到平台代码的调用并不多。更多信息可以参考这篇优秀的文章([Tutorial: Calling Go code from Swift on iOS (and vice versa) with gomobile](https://medium.com/@matryer/tutorial-calling-go-code-from-swift-on-ios-and-vice-versa-with-gomobile-7925620c17a4))。 ## 这套方案效果如何? 到目前为止,我非常喜欢这个技术栈。使用 Go 开发是一种享受,光这一点就值得我选择它。除了我个人的偏好,这套方案还有明显的优缺点。 ### 优点 #### 与服务器的无缝集成 Digital Carrot 的同步服务器同样用 Go 实现。这让测试同步变得非常轻松,因为我只需在服务器同步测试中直接导入客户端代码,而无需用 Docker 搭建复杂的测试环境来启动不同技术栈的客户端和服务器。我可以在 Mac 上直接运行 `go test`,测试在 20 秒内就能完成。 #### 清晰业务/展现层边界 这一点基本上不需要解释。因为业务逻辑和展示逻辑使用不同的语言编写,所以很难将它们混在一起。Flutter 组件只负责 UI(以及一些平台特定的操作,如请求权限),Go 组件处理所有业务逻辑(与服务器通信、保存数据、验证等)。 一个实际的好处是 API 契约的测试变得极其简单。所有的业务逻辑都在 Go 中测试,这意味着我不需要启动任何复杂的 UI 组件来测试应用。我只需在 Go 中编写集成测试,然后用 `go test` 运行。 还有一个意想不到的好处是回放测试。我可以手动在 UI 中测试应用,录制 UI 发出的 API 调用,然后以编程方式重放这些调用。虽然仍然需要一些 UI 测试,但可以非常精简,因为我们能够确信应用的业务逻辑是稳固的。 这种关注点分离还意味着将来替换 UI 变得非常容易。如果我想获得更原生的体验,可以在 iOS 上用 Swift UI 实现,Android 上用 Kotlin。考虑到 Google 有废弃项目的倾向,这对我来说是一个很好的保障。我比较确信 Go 永远不会消失,但谁能说清 UI 这个古怪的世界会发生什么呢? #### 丰富的库资源 这种架构意味着我可以使用 Go 和 Dart 提供的全套工具。Dart 生态提供了几乎任何与平台特定权限交互所需的工具,而 Go 则提供了丰富的系统编程和网络工具。 #### 业务逻辑可以通过任何协议运行 将所有通信放在网络可访问的接口(如 Protobuf)上,意味着后端几乎可以在任何地方运行。这在 Windows 和 Mac 上尤其有利。在这些平台上,后端作为守护进程在后台运行,UI 通过 gRPC 通过套接字或管道连接。Digital Carrot 需要持续在后台运行以阻止程序和网站,因此让 Go 守护进程独立运行非常棒,因为不需要将 UI 的累赘加载到内存中。结果 Digital Carrot 后台运行时只需大约 30-60MB 的 RAM。 虽然这对 Digital Carrot 帮助不大,但对其他类型的应用来说可能是一个优势。例如,你可以创建一个文档编辑器,可以运行在家庭服务器上或本地设备上,只需将 UI 指向网络上的不同 API 即可。 #### 未来的人工智能恶作剧? 我还没有尝试过,但让应用的所有 API 都能通过网络访问,意味着任何客户端都可以与之交互,比如 AI 代理。如果我想在应用中添加 AI 功能,不需要搞一些笨拙的变通方法让 AI 在应用中点击,只需让 AI 直接访问现有的、文档完善的 API 即可。 ### 缺点 #### 性能 每个函数调用都需要序列化和反序列化 Protobuf 消息。这会带来几毫秒的微小开销。对于像 Digital Carrot 这样的应用完全没问题,但对于性能敏感的应用(如游戏)则不可接受。 #### 复杂度 添加新业务逻辑更困难。你需要定义新的 Protobuf 消息,并在 Go 中连接它们。好消息是,这也促使我在实现新功能之前多考虑一下。 这种方法还意味着你需要熟悉至少两种编程语言,并携带一些额外的工具来编译 Dart 和 Go 中的 Protobuf 消息。 #### 应用体积更大 Go 二进制文件很大。整个后端未压缩约 53MB。很难说如果应用纯用 Dart 编写能节省多少空间,但我愿意打赌不会超过 1MB。 ### 丑陋之处 #### 双向通信 我遇到的一个陷阱是没有很好的方法从 Go 应用调用 Flutter。这在通知同步更改或发送通知时非常重要。Flutter 有一些工具可以实现这一点,但我始终无法可靠地运行。最后,我创建了一个每隔一秒在 Flutter 中运行的函数,轮询后端的状态变化。这算不上优雅或特别好的解决方案,但实现起来便宜又简单,所以我不打算改变它。 #### 异步 这个问题直到应用提交审核时才被发现。审核员拒绝了首次提交,理由是 UI 出现轻微卡顿。我不知道 Apple 用什么硬件测试才注意到这个问题,因为我从未察觉。但这让我陷入了一个不小的麻烦。 原来,Flutter 平台通道调用默认不是异步的!这意味着任何对 Go Mobile 的调用都会阻塞主 Flutter 线程,导致整个 UI 冻结。实际上,只有后端执行较重的操作(如从网络获取数据)时才会明显。 这导致了一些非常丑陋的代码:每次调用 Go 都必须交给一个单独的 Go 协程,在完成后调用 Flutter 的回调。从大局来看,这不是大问题,但这算是一个坑,在最后时刻突然出现,而且修复起来有些复杂。 #### 时区和网络错误 Go Mobile 中有一个长期存在的 bug:本地时间总是设置为 GMT。这给我带来了不少麻烦,直到我最终发现它。当时唯一的解决方案是从 Swift/Kotlin 将时区代码传递给 Go,这感觉很不合理。 在 iOS 上,Go Mobile 的 DNS 查找默认完全失效([GitHub issue](https://github.com/golang/go/issues/58416#issuecomment-3070442502))。它在模拟器中有效,但一旦在真机上运行就会抛出各种错误。这需要在 XCode 中包含 `libresolv` 包,花了我好几天才找到解决办法。 ## 结语 总的来说,我对 Go Mobile 非常满意。这趟旅程并不容易。Go Mobile 仍然不常用。很多事情我必须边做边摸索,但除了一些小 bug 外,一切运行良好。在未来的移动应用中,我会乐意继续使用这种架构。 不过,我不建议将整个业务逻辑作为默认架构。有一些特定场景非常适合此方案,例如: - 你的团队恰好对 Go 非常熟悉。 - 应用严重依赖 Go 的网络服务。 - 应用需要原生 UI,但共享大量业务逻辑。 在大多数情况下,人们最好将 Go 最擅长的部分用 Go 实现,其余部分用 Dart、Swift、Kotlin、React Native 或你喜欢的任何技术栈处理。Go 提供的一些库(如 Expr 和 Goja)在移动端没有很好的替代品,能够利用它们是非常强大的。

相似文章

就用Go

Lobsters Hottest

一篇带有强烈观点的开发者文章倡导使用Go编程语言,强调其简洁的语法、强大的标准库、高效的并发模型以及单二进制部署,作为对过于复杂的现代技术栈的实用替代方案。

从Go迁移到Rust

Hacker News Top

一份为Go开发者迁移到Rust编写的全面指南,专注于后端服务,对比正确性、运行时和人体工程学方面的权衡,并提供关于渐进式迁移的实用建议。

flutter/flutter

GitHub Trending (daily)

Flutter 是 Google 的开源 SDK,用于从单一代码库构建美观、快速的跨平台应用。这是 Flutter 的官方 GitHub 仓库。