使用 Go 开发移动应用
在过去的一年里,我一直使用 Go Mobile 开发 Digital Carrot,这是一段相对积极的经历。本文旨在作为我在这个过程中所学知识的汇集。我为什么选择 Go Mobile?简单的答案是我非常喜欢 Go,并希望使用它。更长的答案是以下几个因素的结合:Digital Carrot 是一个跨平台应用,我想要一种可以轻松编译和运行的解决方案。Go 拥有丰富的系统特性库,我希望利用这些特性使 Digital Carrot 可定制和可插拔。其中最重要的是 Expr(用于构建表达式)和 Goja(用于创建 JavaScript 插件)。我如何使用 Go Mobile?Go mobile 被用于 Digital Carrot 所有的业务逻辑。虽然我很想使用 Fyne 进行用户界面开发,但试过之后我觉得它还不够成熟。因此,我选择了使用 Flutter 结合 Go 后端。它大致是这样的:Flutter 通过平台通道调用 Switf/Kotlin,使用二进制编码的 protobuf 消息进行 API 调用,本地代码将原始二进制数据转发给 Go,Go 解码 protobuf 消息并响应。当需要与本地 API 交互时,Go 也可以通过接口调用本地代码。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 填充消息的请求部分,将其放入 CarrotAPI 对象中并发送给 Go。Go 可以通过对 CarrotAPI.api 的 switch 语句来判断正在调用哪个函数,并填充正确的响应。这种方式显得有些笨重,但比为每个函数创建新的平台通道要好得多,并且节省了在 Swift 和 Kotlin 之间重复的劳动。我不会深入讨论如何实现这一点,因为还有其他文章更好地解释了这一点。Go 到 Swift/Kotlin 的通信是我长期以来一直挣扎的一个领域,因为 Go 的文档对此没有很好的说明。基本上,要从 Go 向平台代码发送消息,您需要创建一个 Go 接口,然后在平台一侧实现该接口,并在调用 Go 代码时传递给 Go。以下是一个示例:我们创建一个 Go 接口: ```go type IosMethods interface { // Screentime SetShields([]byte) bool HasScreentimePermissions() bool } ``` Go Mobile 生成的 Objective-C 或 Kotlin 接口如下: ```objective-c @interface MobileIosMethods : NSObject <goSeqRefInterface, MobileIosMethods> { } @property (strong, readonly, nonatomic) _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 到平台代码之间的调用并不多。关于这一点的更多信息可以在这篇优秀的文章中找到。所有这些如何运作到目前为止?迄今为止,我非常喜欢这个技术栈。使用 Go 是一种乐趣,仅此一点就值得我继续下去。除了我个人的奇怪需求,这种方法有一些严重的优缺点。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡