关于使用Go开发Android和iOS底层代码,你想了解的都在这。
先来讲一下使用Go语言开发Android和iOS底层代码的好处:
- 可以编译成静态的
libgojni.so
或<PKG>.framework
- 跨平台代码复用率极高
- 二进制程序的安全性很好
- 代码可维护性很好
本文章介绍的内容:
- 如何传递Go语言的’对象’至目标平台的语言
- 如何传递目标平台语言至Go语言
- 可穿越语言边界的数据类型,以及如何传递复杂类型
- 如何在Go语言中使用目标平台语言已有的package
- 如何有针对性的区分
android
和ios
GO语言代码
本文章合适谁看?
合适已初步了解gomobile
和gobind
的童鞋,并想深入了解gobind
的一些细节的人非常合适看本文章。
官网链接:https://godoc.org/golang.org/x/mobile/cmd/gobind
如何传递Go语言的’对象’至目标平台的语言
其实GO语言没有’对象’的说法,毕竟不存在类(class),所以,这里所述的’对象’其实是指Go语言的结构体的实例。
我们写一个counter.go
文件:
|
|
目录结构:
|
|
Android代码:
|
|
Output: 06-24 21:13:19.431 28611-28611/com.linkscue.gobind.example I/MainActivity: onCreate: counter value 11
iOS代码:
|
|
Output: 2018-06-24 21:11:17.758095+0800 bind[16911:48934206] counter value 11
解析:这里使用GO语言定义了
Counter
结构体,并提供了NewCounter()
来生成’对象’,然后在目标平台上就可以直接调用它来生成一个’对象’了,接着在目标平台代码上可以使用这个’对象’提供的一些方法。
如何传递目标平台语言至Go语言
这个的前提是自己先知道在Go语言底下将使用目标代码的哪些接口,因此,需要事先在Go语言下定义好接口,比如:
|
|
完整代码文件printer.go
:
|
|
目录结构:
|
|
Android代码:
|
|
Output: 06-24 21:26:24.291 29190-29190/com.linkscue.gobind.example W/MainActivity: I print: Hello world! current time: 1529846784
iOS代码:
- 头文件
SysPrinter.h
|
|
- 源文件
SysPrinter.m
|
|
- 调用方法
|
|
Output: 2018-06-24 21:30:46.722281+0800 bind[18140:48989176] I print Hello world! current time: 1529847046
解析:在GO语言中定义了接口
Printer
接口,只要目标代码实现了Print(s stirng)
即可。只不过这里的Print(s string)
是Java或Objc实现的罢了,Go语言负责将所需要打印的字符串s
生产出来,然后让java或objc代码来消费它。
可穿越语言边界的数据类型(type)
了解哪些类型(type)可以穿越语言边界很有用,可以让在设计的时候,就避开了很多坑。
- 有符号的整形和浮点数
- 字符串和布尔值(字符串映射成
String
或NSString*
) - 字节切片(
[]byte
),字节切片穿越边界之后是允许修改其中内容的 - 函数:任何使用上述类型的函数,没有返回值或只有一个返回值,若有两个返回值时,第二个参数必须是内置的error类型
- 接口:任何包含了符合上述条件的函数的接口
- 结构体:任何包含符合上述条件类型的结构体都能穿越语言边界
官网:https://godoc.org/golang.org/x/mobile/cmd/gobind#hdr-Type_restrictions
传递复杂类型
可以看到,允许穿越语言边界的类型是比较少的,像List和Map比较常用的,建议可以通过Json序列化来传递。
比如,可以写一段这样子的Android调用gojni的代码:
Android 代码:
|
|
GO语言代码:
|
|
解析:像
[]string
字符串数组(切片)是不允许穿越语言边界的,但使用Json序列化成String之后就不存在限制了。
如何在Go语言中使用目标平台语言已有的package
在GO语言中,还可以像Cgo
一样,直接使用已存在的package,这里的奇技淫巧不一定能使用得上,不过我们可以先了解一下。
假设:现在希望使用各自平台的目标代码来获取当前系统的Unix时间。
已知:Android,可以使用System.CurrentTimeMillis()
来获取,iOS,可以使用NSDate.date.timeIntervalSince1970
来获取。
目标结构:
|
|
提示:需要使用
_android
和_ios
来区分各平台的代码,一方面是清晰,另一方面是不这样子做编译不过…
Android的reverse_android.go
:
|
|
iOS的reverse_ios.go
:
|
|
提示:
// +build ios
这行注释是必须的,说明仅在编译的tags包含了ios
的时候才会编译这个文件,而Android则不需要// +build android
这个注释也可以。同时,这一行的注释必须是在package hello
之前,必须包含一行空白行。
如何有针对性的区分android
和ios
GO语言代码
上一小节已说明得比较清楚了,这里复述一次:
- 使用
_android
和_ios
文件命名方式来区分不同平台的代码 - 在
_ios
文件上,必须包含// +build ios
注释 - 这一行注释必须在
package hello
之前,并预留一行空白行
提示:在
ios/
目录下执行gomobile bind -a -v -target=ios ../hello
命令时,实际调用go build的命令是这样子的go build -tags ios -v -buildmode=c-archive -o /path/to/hello-arm.a
,编译ios
的时候使用附加上-tags ios
。
有兴趣阅读:https://golang.org/pkg/go/build/#hdr-Build_Constraints
阅后福利
不应在go语言init()
反向使用目标代码的package
不然会出现以下错误:
- Android错误日志:
06-24 22:18:18.611 31929-31943/com.linkscue.gobind.example E/Go: panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x608c6f28]
goroutine 1 [running]:
github.com/scue/gobind-example/hello.init.0()
/Users/scue/go/src/github.com/scue/gobind-example/hello/reverse_android.go:13 +0x1c
06-24 22:18:18.611 31929-31943/com.linkscue.gobind.example A/libc: Fatal signal 6 (SIGABRT) at 0x00007cb9 (code=-6), thread 31943 (.gobind.example)
- iOS错误日志:
Thread 2: EXC_BAD_ACCESS (code=1, address=0x0)
使用gobind的示例iOS textLabel.text
设定之后无效?
定睛一看,发现这个Label很皮,跑到左上角去了,把它拖下来即可。