34

[剑走偏锋] Android使用Golang代替C/C++进行Native开发

 4 years ago
source link: https://www.tuicool.com/articles/MZFZVrV
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

想一个好点的标题

事情是这样的: 一次Android项目开发过程中,要用到数据的加密解密,因为数据运算量比较大,所以需要用到native进行开发,但是又极不情愿去写C/C++那种既耽误时间又不好调试的语言,所以想方设法的寻找替代方案,正好最近在用Golang,寻思着,Golang不是号称速度接近C++又能快速开发的吗,所以琢磨着能不能用Golang来写Android的native部分,然后就有了一系列的踩坑过程 (这坑我踩了,剩下的你看着办吧)

想不到好标题怎么办啊

既然是用Golang开发,当然就需要用到Golang的开发环境,至于怎么搭建,你自己去找吧 (烂大街的玩意)

大致列一下需要用到的环境和SDK:

想个标题真麻烦

开搞

首先是写一份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 失败,至于为什么,有待深入研究

为什么会有标题这种奇怪的东西

然后就开始编译,在编译之前,需要设置一下 go env 的参数,从而达到我们想要的东西

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

这里编译完就可以拿到 armeabi-v7aso 文件了,如果需要其他架构的,请修改 GOARCH= arm/arm64/386/amd64 中的任意一个,并修改 CC CXX 为对应架构的编译器,然后重新编译得到对应架构的 so 文件

干脆不要标题了

将下载的 JNA 解压,复制 dist 目录下的 jna-platform.jarjna-min.jarlibs 目录下,并将 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 只能传基本数据类型,没办法传递对象 (这个不知道定义一个结构体能不能实现)
  • Golang 调用 C/C++ 的链接库不是很方便

暂时就这么多,后边会花时间研究一下怎么简化流程和调用更高级的API,以达到使用纯 Golang 开发 Android 的目的


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK