這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
轉載隨意:文章出處 http://shengxiang.me/article/38/write-android-program-with-golang.html
Golang是一門強型別程式設計語言,2009年推出,在今年2014年,開始支援android的開發了。
環境配置好複雜,我不得不嘮叨幾句。
需要下載golang1.4rc版,下載ndk,然後編譯。然後用go get 下載gobind這個工具,然後,將寫好的代碼用gobind轉化下,然後使用特殊的編譯命令,將代碼編譯成.so檔案,將產生的相關檔案,放到android studio的項目中。然後java代碼中,利用jni調用引用的代碼。
... 好,接著往下看吧。
環境準備
- 一台Linux 64的機器
- 一個帶有AndroidStudioIDE的開發機器
因為環境配置實在複雜,所以我們引入的docker。
docker pull codeskyblue/docker-goandroiddocker run --rm -ti codeskyblue/docker-goandroid bashcd example; echo "view example projects
docker起來之後,什麼就都配置好了,NDK啦,java啦,GO的環境變數了,等等,並且還預裝了vim,gradle,tmux,git,syncthing,svn
開始寫代碼
寫代碼之前,先約定下目錄結構
go的代碼都放在src/golib下,編譯使用make.bash編譯指令碼,看下這個檔案樹
.|-- app.iml|-- build.gradle|-- libs/armeabi-v7a # go編譯產生的so檔案| `-- libgojni.so|-- main.go_tmpl # 一個模板檔案,先不用管它|-- make.bash # 編譯指令碼,用來產生.so和Java代碼`-- src |-- golib | |-- hi | | |-- go_hi֞֞֞ # 自動產生的程式碼 | | | `-- go_hi.go | | `-- hi.go # 需要編寫的代碼 | `-- main.go `-- main |-- AndroidManifest.xml |-- java | |-- go # 自動產生的程式碼 | | |-- Go.java | | |-- Seq.java | | `-- hi | | `-- Hi.java | `-- me/shengxiang/gohello # 主要的邏輯代碼 | `-- MainActivity.java֞֞֞ `-- res
我已經寫了一個例子,先直接搞下來
git clone https://github.com/codeskyblue/GoHello.git
編譯下,試試行不行(就算不行問題應該也不大,因為大問題都被我消滅了)
cd GoHello/app./make.bash../gradlew build
一切順利的話在build/outputs/apk
下應該可以看到app-debug.apk
這個檔案。(劇透下,這個檔案只有800多K)
編譯好的我放到qiniu上了,可以點擊下載看看
下面可以嘗試改改,我拋磚引玉說下
開啟hi.go
這個檔案
hi.go
的內容,比較簡單,我們寫Go代碼主要就是這部分
// Package hi provides a function for saying hello.package hiimport "fmt"func Hello(name string) { fmt.Printf("Hello, %s!\n", name) return "(Go)World"}
檔案末尾添加下面這行代碼
func Welcome(name string) string { return fmt.Sprintf("Welcome %s to the go world", name)}
使用./make.bash
重新編譯下
開啟MainActivity.java
修改下OnClickListener事件
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String message = Hi.Welcome("yourname"); Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show(); } });
編譯運行下,把產生的apk安裝到手機上試試。
原理解讀(有興趣的接著看)
首先說下gobind這個工具。
go_hi/go_hi.go
這個檔案時通過gobind這個工具產生的,用來配合一個簡單的程式,產生.so檔案
// go_hi.gopackage go_hiimport ( "golang.org/x/mobile/bind/seq" "example/hi")func proxy_Hello(out, in *seq.Buffer) { param_name := in.ReadUTF16() hi.Hello(param_name)}func init() { seq.Register("hi", 1, proxy_Hello)}
這個簡單的程式內容是這樣的
// main.gopackage mainimport ( "golang.org/x/mobile/app" _ "golang.org/x/mobile/bind/java" _ "example/hi/go_hi")func main() { app.Run(app.Callbacks{})}
src/MyActivity.java
檔案內容是這樣的
import ...import go.Go; // 引入Go這個包import go.hi.Hi; // gobind產生的程式碼public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Go.init(getApplicationContext()); // 初始化兩個線程 Hi.Hello("world"); }}
其中有一句Go.init(...)
這裡再看go.Go
這個包是什麼樣子的
public final class Go { // init loads libgojni.so and starts the runtime. public static void init(Context context) { ... 判斷該函數是否該執行的代碼 -- 省略 -- System.loadLibrary("gojni"); // gojni需要這句 new Thread("GoMain") { public void run() { Go.run(); // run()是一個native方法 } }.start(); Go.waitForRun(); // 這個也是一個native方法 // 這部分可以理解為,啟動了一個後台線程不斷的接收結果到緩衝中。 new Thread("GoReceive") { public void run() { Seq.receive(); } }.start(); } private static boolean running = false; private static native void run(); private static native void waitForRun();}
MyActivity.java中還有段代碼是 Hi.Hello("world");
,開啟Hi.java路徑在src/go/hi/Hi.java
,這個檔案也是gobind產生的,是用來給java方便的調用.so檔案
// Hi.java// File is generated by gobind. Do not edit.package go.hi;import go.Seq;public abstract class Hi { private Hi() {} // uninstantiable public static void Hello(String name) { go.Seq _in = new go.Seq(); go.Seq _out = new go.Seq(); _in.writeUTF16(name); Seq.send(DESCRIPTOR, CALL_Hello, _in, _out); // 下面接著說 } private static final int CALL_Hello = 1; private static final String DESCRIPTOR = "hi";}
Seq.send這部分實際上最終調用的是一段go代碼
func Send(descriptor string, code int, req *C.uint8_t, reqlen C.size_t, res **C.uint8_t, reslen *C.size_t) { fn := seq.Registry[descriptor][code] in := new(seq.Buffer) if reqlen > 0 { in.Data = (*[maxSliceLen]byte)(unsafe.Pointer(req))[:reqlen] } out := new(seq.Buffer) fn(out, in) seqToBuf(res, reslen, out)}
參考資料
- 這篇文檔基本算是這個庫的使用入門了。
- Binding Go and Java 官方的設計文檔
- docker-goandroid在docker.io上的地址
- gobind,goapp
後記(吐槽)
這個部落格系統用的是fuxiaohei寫的Goblog
強烈要求更新GoBlog,這文章我剛寫好,然後不小心點了一個回退,結果東西全沒了,我只有重寫了一遍。你能理解我當時什麼心情嗎?