起因
事情是這樣的: 一次Android項目開發(fā)過程中,要用到數(shù)據(jù)的加密解密,因為數(shù)據(jù)運算量比較大,所以需要用到native進行開發(fā),但是又極不情愿去寫C/C++那種既耽誤時間又不好調試的語言,所以想方設法的尋找替代方案,正好最近在用Golang,尋思著,Golang不是號稱速度接近C++又能快速開發(fā)的嗎,所以琢磨著能不能用Golang來寫Android的native部分,然后就有了一系列的踩坑過程 (這坑我踩了,剩下的你看著辦吧)
配料表
既然是用Golang開發(fā),當然就需要用到Golang的開發(fā)環(huán)境,至于怎么搭建,你自己去找吧 (爛大街的玩意)
大致列一下需要用到的環(huán)境和SDK:
- Golang SDK 1.8+
- Android SDK
- Android NDK
- Android Studio
- JNA ( Java Native Access ) Android
編譯準備
開搞
首先是寫一份Golang的源碼,打個比方說 hello.go
package main
import "C"
//export SayHello
func SayHello(name *C.char) *C.char{
return C.CString("Hello : " + name)
}
//export Sum
func Sum(a int, n int) int {
return a + n
}
func main(){
// 這個主方法一定要寫,不然不給編譯
}
是不是覺得一臉懵逼,那么有必要解釋一下
import "C"
這個是要告訴CGO我需要調用C的方法,使用C語言的東西//export SayHello
這個注釋是必須要有的,就相當于C語言中的extern
值得注意的是雙斜杠后面不能有空格,別問我為什么知道,所以//export SayHello
就相當于C語言的extern char* SayHello(char*)
- 為什么這里用
*C.char
而不用string
呢? 因為,如果使用Golang中的string
來定義的話,在Java中就不能直接以String的類型來傳遞,而會被Golang定義為一種名為GoString
的神奇類型,就像這樣
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
typedef _GoString_ GoString;
extern GoString SayHello(GoString);
使用string的好處就是,他會直接導致你的代碼量增加,因為你還要在Java中寫一個和GoString差不多的包含
String (字符串)
和int (字符串長度)
的類,而C語言中的char*
直接對應的就是Java中的String
,所以這里 建議使用*C.char
來代替 Golang中的string
- 最后就是
main
方法,這個是必須要有的,不使用的話置空就好,沒有的話會導致編譯.so
失敗,至于為什么,有待深入研究
參數(shù)配置
然后就開始編譯,在編譯之前,需要設置一下
go env
的參數(shù),從而達到我們想要的東西Windows
set GOOS=android
## GOARCH可選平臺,需要和 CC CXX 對應
## arm (armeabi-v7a) CC=armv7a-linux-androideabi19-clang.cmd CXX=armv7a-linux-androideabi19-clang++.cmd
## arm64 (arm64-v8a) CC=aarch64-linux-android19-clang.cmd CXX=aarch64-linux-android19-clang++.cmd
## 386 (x86) CC=i686-linux-android19-clang.cmd CXX=i686-linux-android19-clang++.cmd
## amd64 (x86_64) CC=x86_64-linux-android21-clang.cmd CXX=x86_64-linux-android21-clang++.cmd
set GOARCH=arm
## 這個一定要,不然你編譯出來的so各種未定義
set CGO_ENABLED=1
## 設置NDK的編譯器路徑,需要和 GOARCH 對應
set CC=${你的NDK目錄}\toolchains\llvm\prebuilt\windows-x86_64\bin\armv7a-linux-androideabi19-clang.cmd
set CXX=${你的NDK目錄}\toolchains\llvm\prebuilt\windows-x86_64\bin\armv7a-linux-androideabi19-clang++.cmd
Linux
export GOOS=android
export GOARCH=arm
export CGO_ENABLED=1
export CC=${你的NDK目錄}\toolchains\llvm\prebuilt\linux-x86_64\bin\armv7a-linux-androideabi19-clang
export CXX=${你的NDK目錄}\toolchains\llvm\prebuilt\linux-x86_64\bin\armv7a-linux-androideabi19-clang++
mac
我窮逼用不起蘋果,你們自己百度
之后就是開始編譯
.so
文件
go build -buildmode=c-shared -o libhello.so hello.go
要想優(yōu)化一下編譯后的產物大小可以這樣
go build -ldflags "-s -w" -buildmode=c-shared -o libhello.so hello.go
-s
參數(shù)是去掉編譯后的符號信息.-w
參數(shù)是去掉DWARF調試信息,不過需要注意的是,-w
參數(shù)得到的產物無法進行調試,當然可以在發(fā)布的時候使用-w
參數(shù)編譯.
這里編譯完就可以拿到armeabi-v7a
的so
文件了,如果需要其他架構的,請修改GOARCH= arm/arm64/386/amd64
中的任意一個,并修改CC
CXX
為對應架構的編譯器,然后重新編譯得到對應架構的so
文件
食用方法
將下載的
JNA
解壓,復制dist
目錄下的jna-platform.jar
和jna-min.jar
到libs
目錄下,并將android-armv7.jar
android-aarch64.jar
android-x86.jar
android-x86-64.jar
解壓,得到里邊的libjnidispatch.so
放到jniLibs
的對應目錄下,將編譯Golang源碼得到的so
復制到Android項目的jniLibs
目錄的對應目錄下,如圖:
(假裝有圖)
Project
└─ app
├─ libs
│ ├─ jna-min.jar
│ └─ jna-platform.jar
└─ src
└─ main
├─ Androidmanifest.xml
├─ java
├─ res
└─ jniLibs
├─ armeabi-v7a
│ ├─ libjnidispatch.so
│ └─ libhello.so
├─ arm64-v8a
│ ├─ libjnidispatch.so
│ └─ libhello.so
├─ x86
│ ├─ libjnidispatch.so
│ └─ libhello.so
└─ x86_64
├─ libjnidispatch.so
└─ libhello.so
在項目中新建一個接口,名稱隨意,繼承
com.sun.jna.Library
package com.demo.golang;
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface Hello extends Library {
// 加載libhello.so
Hello ins = Native.load("hello", Hello.class);
/**
* 對應 Golang 中的 SayHello 方法
*/
String SayHello(String name);
/**
* 對應 Golang 中的 Sum 方法
*/
int Sum(int a, int n);
}
然后在你想要調用的位置調用
Hello.ins.SayHello("Golang for Android with JNA")
比如我在MainActivity
里邊調用
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((TextView) findViewById(R.id.text_view))
.setText(Hello.ins.SayHello("Golang for Android with JNA ") + Hello.ins.Sum(500, 20));
}
}
跑起來看看,是不是非常完美?是不是感覺新技能
get
有木有覺得比 C/C++ 簡單得多?
總結
總體上來說,相對直接使用C/C++要簡單方便,但是,也有一定的缺陷,暫時我還沒研究出從 Golang 調用 Java 代碼的方法, 所以簡單來說就是只能通過 Java 調用Golang
簡單的總結一下相對 C/C++ JNI 來寫的一些缺點
- 暫時沒法從 Golang 調用 Java (當然這個我感覺應該不難)
- Java 調用 Golang 只能傳基本數(shù)據(jù)類型,沒辦法傳遞對象 (這個不知道定義一個結構體能不能實現(xiàn))
- Golang 調用 C/C++ 的鏈接庫不是很方便
暫時就這么多,后邊會花時間研究一下怎么簡化流程和調用更高級的API,以達到使用純 Golang 開發(fā) Android 的目的