您现在的位置是:网站首页> Go语言
Go经验总结
- Go语言
- 2025-05-21
- 1623人已阅读
项目目录下编译
go build .
Go显示和设置Go的环境变量
go env
go env -w GO111MODULE=auto
golang打包成android
本文只讲述如果使用将golang工程打包成jar/aar/apk,适用于android开发者需要集成一些golang开发的微服务或者其他golang语言实现的功能。
环境安装
1. golang开发环境安装
安装包下载地址:下载,windows建议下载msi文件,直接安装之后环境变量都配置好了。 创建一个go的workspace,然后创建一个hello.go
//hello.go
//包名
package hello
//引入模块
import "fmt"
//main函数/程序入口
func main() {
fmt.Println("Hello, World!")
}
执行命令:go run hello.go
2. Android 开发环境安装
android studio 已默认安装了编译需要的ndk,但是路径并没有添加到path中,所以执行ndk-build时会提示找不到命令,这时只要找到ANDROID_SDK_ROOT/ndk和ANDROID_SDK_ROOT/ndk-bundle,将其添加到path中,这时的环境已经能满足下一步的要求了。
3. gomobile安装
要将go打包成aar/jar/apk以及IOS应用(需要xcode环境),则需要一个go的打包工具gomobile, 安装方式有两种
#方法1:
使用命令:`go get golang.org/x/mobile/cmd/gomobile`(需要翻墙)
#方法2:
#下载源码包:
git clone https://github.com/golang/mobile.git
#将源码拷贝到 $GOPATH/src/golang.org/x
#执行命令:
go build golang.org/x/mobile/cmd/gomobile
#这时会在C:\Users\username\go\bin下生成gobind和gomobile两个文件
#gomobile的初始化,
gomobile init
4. 打包示例
#将hello工程目录拷贝到C:\Go\src下面
#打包sdk
gomobile bind -target=android hello
#会在当前目录下生成hello.arr 和 hello.jar 两个sdk包
#打包apk
gomobile build -target=android hello
#会在当前目录下直接生成hello.apk
gomobile编译
(1.)下载代码,并生成二进制文件,gomobile和gobind
go get golang.org/x/mobile/cmd/gomobile
或者
git clone https://github.com/golang/mobile
copy到$GOPATH/src/golang.org/x/
//编译生成gobind二进制文件
cd mobile/cmd/gobind
go build .
拷贝gobind到$GOPATH/bin目录,并加入环境变量
//编译生成gomobile二进制文件
cd mobile/cmd/gomobile
go build .
拷贝gomobile到$GOPATH/bin目录,并加入环境变量
//查看是否安装成功
gomobile version
gomobile example测试
gomobile init
//此命令会生成名为basic的apk安装包
gomobile build -target=android golang.org/x/mobile/example/basic
//此命令将安装apk包到已连接的android设备
gomobile install golang.org/x/mobile/example/basic
//生成jar、aar文件
gomobile bind -target=android golang.org/x/mobile/example/bind/hello
或 D:\go\src\golang.org\x\mobile\example\bind>gomobile bind -target=android ./hello
gomobile example编译错误处理
配置环境变量
gomobile-ipfs编译
(1.)go代码中编译
代理路径: https://github.com/ipfs-shipyard/gomobile-ipfs
cd /go/bind/
gomobile bind -target=android ./ipfs
生成jar和aar文件
gomobile bind -target=android hello
生成hello.aar文件和hello-sources.jar文件,放到Android工程的libs目录里,aar文件供调用,source.jar可以看源码。
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package hello is a trivial package for gomobile bind example.
package hello
import "fmt"
func Greetings(name string) string {
fmt.Printf("hello go,this is log\n")
return fmt.Sprintf("Hello aaa, %s!", name)
}
func Test1(buf []byte) []byte{
fmt.Printf("in buf is:%x\n",buf)
outbuf := []byte{0x31,0x32,0x33,0x34}
return outbuf
}
然后应用里就可以很爽的调用:
/*
* Copyright 2015 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
package org.golang.example.bind;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import hello.Hello;
public class MainActivity extends Activity {
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.mytextview);
// Call Go function. test String
String greetings = Hello.greetings("Android and Gopher aaa11");
mTextView.setText(greetings);
// Call Go function. test byte[]
byte[] inbuf = new byte[6];
inbuf[0] = 0x39;
inbuf[1] = 0x38;
inbuf[2] = 0x37;
inbuf[3] = 0x36;
byte[] outbuf = Hello.test1(inbuf);
System.out.println(outbuf);
}
}
AndroidStdio的配置麻不麻烦呢?也不麻烦。配置下引用外部库即可。
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation fileTree(include: ['*.aar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:22.1.1'
//implementation project(':hello')
implementation files('libs/hello.aar')
}
最后再来说下环境,也很简单,只需配置一次即可。
总结下:
直接用AndroidNDK编写SDK,需要自己写JNI。而gomobile一个命令,把脏活累活都给弄好了。
可以一份代码支持Android和iOS,维护上比较方便。
体积上,gomobile的so最起码有2.8MB,比C要大不少,也还能接受。因为效率高啊。
如果再有人找我封装JNI层的.so?我想,我想用go来做!
至于执行的效率,可反编译过来看下,其实内部还是调的c的JNI,只不过gomobile命令把这些繁琐的事做了。
效率应差不了多少。至于稳定性,虽然gomobile是谷歌内部的一个实验性项目,但是你只使用gobind做native层的工作,这部分已经很稳定了。
gomobile init之前需要环境变量中配置了ndk环境,可把ndk环境加到系统环境变量,或者通过ndk标签指定ndk目录gomobile init -ndk 指定。注意,要求ndk版本是在19以上才行
Go生成go动态库或静态库的方法
预备知识
plugin模式
插件运行方式
go plugin包使用
相关知识(推荐:go语言教程)
go build 可以指定buildmode。分为了多种模式。具体模式如下。
模式 说明
当前go版本 1.10.3
archive 编译成二进制文件。一般是静态库文件。 xx.a
c-archive 编译成C归档文件。C可调用的静态库。xx.a。注意要编译成此类文件需要import C 并且要外部调用的函数要使用 “//export 函数名” 的方式在函数上方注释。否则函数默认不会被导出。
c-shared 编译成C共享库。同样需要 import “C” 和在函数上方注释 // export xxx
default 对于有main包的直接编译成可执行文件。没有main包的,编译成.a文件
exe 编译成window可执行程序
plugin 将main包和依赖的包一起编译成go plugin。非main包忽略。【类似C的共享库或静态库。插件式开发使用】
实例
结构:
-softplugin //根目录
-soft //软件目录
-plugins //插件目录
-itf //接口目录
无自定义数据
// plugins/hello.go
package main
import "fmt"
func Hello(){
fmt.Println("hello")
}
// go build -buildmode=plugin -o hello.so hello.go
// soft/basetype.go
package main
import (
"os"
"path"
"plugin"
"fmt"
)
func main(){
//加载插件
pluginDir := "../plugins"
//扫描文件夹下所有so文件
f, err := os.OpenFile(pluginDir, os.O_RDONLY, 0666)
if err != nil {
panic(err)
}
fi, err := f.Readdir(-1)
if err != nil {
panic(err)
}
plugins := make([]os.FileInfo, 0)
for _, ff := range fi {
if ff.IsDir() || path.Ext(ff.Name()) != ".so" {
continue
}
plugins = append(plugins, ff)
pdll, err := plugin.Open(pluginDir + "/" + ff.Name())
if err != nil {
fmt.Println(err)
continue
}
plg, err := pdll.Lookup("Hello")
if err != nil {
panic(err)
}
plg.(func())()
}
}
// go run basetype.go
定义插件接口 interface。
//------------------------------------------------------
// itf/itf1.go
package itf
type Printor interface{
Print(v interface{})
}
//------------------------------------------------------
// plugins/p1.go
package main
import (
"fmt"
"softplugin/itf"
)
type ScreenPrintor struct{}
func (ScreenPrintor)Print(v interface{}){
fmt.Println("p1p1 ",v)
}
func Install() Printor{
return &ScreenPrintor{}
}
//-----------------------------------------------------
// soft/s1.go
package main
import (
"softplugin/itf"
"os"
"path"
"plugin"
"fmt"
)
var(
printors = make([]itf.Printor, 0)
)
func main(){
//加载插件
pluginDir := "../plugins"
//扫描文件夹下所有so文件
f, err := os.OpenFile(pluginDir, os.O_RDONLY, 0666)
if err != nil {
panic(err)
}
fi, err := f.Readdir(-1)
if err != nil {
panic(err)
}
plugins := make([]os.FileInfo, 0)
for _, ff := range fi {
if ff.IsDir() || path.Ext(ff.Name()) != ".so" {
continue
}
plugins = append(plugins, ff)
pdll, err := plugin.Open(pluginDir + "/" + ff.Name())
if err != nil {
fmt.Println(err)
continue
}
plg, err := pdll.Lookup("Hello")
if err != nil {
panic(err)
}
printors = append(printors, (plg.(func() itf.Printor))())
}
for _, p := range printors {
p.Print("pppp")
}
}
Go不定参数
package main
import "fmt"
func MyPrintf(args ...interface{}) {
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println(arg, "is an int value.")
case string:
fmt.Println(arg, "is a string value.")
case int64:
fmt.Println(arg, "is an int64 value.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
func main() {
var v1 int = 1
var v2 int64 = 1111
var v3 string = "hello world!"
var v4 float32 = 1.1111
MyPrintf(v1, v2, v3, v4)
}
func P (v... string) {
for _,item := range v {
fmt.Println("item:",item)
}
}
func main() {
var l []string
l = append(l,"a")
l = append(l,"b")
fmt.Println("l is ",l)
P(l...)
}
搞定 Go 实时消息推送
在 Web 开发中,实时数据推送是一个常见需求。比如,股票价格更新或聊天消息通知。Server-Sent Events (SSE) 是一种基于 HTTP 的轻量级技术,特别适合服务器主动向客户端推送更新的场景。今天,我们将结合 go-zero,带你一步步实现一个简单的 SSE 服务,并附上完整代码和运行步骤。
什么是 SSE?
SSE(Server-Sent Events)是 HTML5 提供的一种技术,允许服务器通过持久化的 HTTP 连接向客户端单向推送事件。相比 WebSocket,SSE 更轻量,支持简单的实时更新场景,且基于标准 HTTP 协议,开箱即用。
SSE 的核心特点:
单向通信:服务器主动推送,客户端被动接收。
简单协议:基于 text/event-stream 格式,易于实现。
自动重连:浏览器内置重连机制,断开后可自动尝试恢复。
接下来,我们用 go-zero 实现一个 SSE 服务,功能是每秒向客户端推送当前服务器时间。
实现步骤
1. 项目初始化
首先,确保你已安装 Go 并引入 go-zero 依赖:
go get -u github.com/zeromicro/go-zero
创建一个项目目录,结构如下:
sse-demo/
├── main.go # 主程序
└── static/
└── index.html # 前端页面
2. 编写服务端代码
我们将使用 go-zero 的 REST 服务,同时集成 SSE 和静态文件服务。完整代码如下:
package main
import (
"fmt"
"net/http"
"sync"
"time"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/rest"
)
type SseHandler struct {
clients map[chan string]struct{}
lock sync.RWMutex
}
func NewSseHandler() *SseHandler {
return &SseHandler{
clients: make(map[chan string]struct{}),
}
}
// Serve 处理 SSE 连接
func (h *SseHandler) Serve(w http.ResponseWriter, r *http.Request) {
// 设置 SSE 必需的 HTTP 头
w.Header().Add("Content-Type", "text/event-stream")
w.Header().Add("Cache-Control", "no-cache")
w.Header().Add("Connection", "keep-alive")
// 为每个客户端创建一个 channel
clientChan := make(chan string)
h.lock.Lock()
h.clients[clientChan] = struct{}{}
h.lock.Unlock()
// 客户端断开时清理
defer func() {
h.lock.Lock()
delete(h.clients, clientChan)
h.lock.Unlock()
close(clientChan)
}()
// 持续监听并推送事件
for {
select {
case msg := <-clientChan:
// 发送事件数据
fmt.Fprintf(w, "data: %s\n\n", msg)
w.(http.Flusher).Flush()
case <-r.Context().Done():
// 客户端断开连接
return
}
}
}
// SimulateEvents 模拟周期性事件
func (h *SseHandler) SimulateEvents() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for range ticker.C {
message := fmt.Sprintf("Server time: %s", time.Now().Format(time.RFC3339))
// 广播给所有客户端
h.lock.RLock()
for clientChan := range h.clients {
select {
case clientChan <- message:
default:
// 跳过阻塞的 channel
}
}
h.lock.RUnlock()
}
}
func main() {
// 创建 go-zero REST 服务,集成静态文件服务
server := rest.MustNewServer(rest.RestConf{
Host: "0.0.0.0",
Port: 8080,
}, rest.WithFileServer("/static", http.Dir("static")))
defer server.Stop()
// 初始化 SSE 处理
sseHandler := NewSseHandler()
// 注册 SSE 路由
server.AddRoute(rest.Route{
Method: http.MethodGet,
Path: "/sse",
Handler: sseHandler.Serve,
}, rest.WithTimeout(0))
// 在单独的 goroutine 中模拟事件
go sseHandler.SimulateEvents()
logx.Info("Server starting on :8080")
server.Start()
}
代码解析
SseHandler 结构:
使用 map[chan string]bool 维护所有客户端的 channel,方便广播消息。
NewSseHandler 初始化这个 map。
Serve 方法:
设置 SSE 必需的 HTTP 头:Content-Type: text/event-stream、Cache-Control: no-cache 和 Connection: keep-alive。
为每个连接创建一个 channel,存储到 clients 中。
使用 select 监听 channel 消息或客户端断开信号(通过 r.Context().Done())。
收到消息时,格式化为 SSE 协议(data: 消息\n\n),并通过 Flush() 立即推送。
SimulateEvents 方法:
使用 time.Ticker 每秒生成一个事件(当前时间)。
遍历 clients,将消息广播给所有连接的客户端。
使用非阻塞发送(select + default),避免某个客户端阻塞影响整体。
main 函数:
使用 rest.MustNewServer 创建服务,监听 8080 端口。
通过 rest.WithFileServer 配置静态文件服务,映射 /static 到本地 static 目录。
注册 /sse 路由,绑定 SseHandler.Serve,并禁用超时,确保长连接不会被超时机制中断,如果是在 api 文件中定义 SSE 路由,需要加上 timeout: 0s。
在 goroutine 中启动事件模拟。
3. 编写前端代码
在 static/index.html 中编写简单的客户端代码:
<!DOCTYPE html>
<html>
<head>
<title>SSE 示例</title>
</head>
<body>
<h1>Server-Sent Events 演示</h1>
<div id="events"></div>
<script>
const eventList = document.getElementById('events');
// 连接到同一服务器的 SSE 端点
const source = new EventSource('/sse');
source.onmessage = function(event) {
const newElement = document.createElement("p");
newElement.textContent = event.data;
eventList.appendChild(newElement);
};
source.onerror = function() {
console.log("发生错误");
};
</script>
</body>
</html>
前端解析
使用 EventSource 连接到 /sse 端点。
onmessage 回调接收服务器推送的数据,动态添加到页面。
onerror 处理连接错误(例如服务器关闭)。
4. 运行和测试
保存文件:确保 main.go 和 static/index.html 在正确的位置。
启动服务:go run main.go
访问页面:打开浏览器,输入 http://localhost:8080/static/index.html。
效果:页面每秒显示一条新的服务器时间。
项目地址
https://github.com/zeromicro/go
下一篇:go编译树莓派上运行的程序