美文网首页
[剑走偏锋] Android使用Golang代替C/C++进行N

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

作者: var_rain | 来源:发表于2019-10-26 03:28 被阅读0次

    起因

    事情是这样的: 一次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
    

    要想优化一下编译后的产物大小可以这样

    go build -ldflags "-s -w" -buildmode=c-shared -o libhello.so hello.go
    

    -s 参数是去掉编译后的符号信息. -w 参数是去掉DWARF调试信息,不过需要注意的是,-w参数得到的产物无法进行调试,当然可以在发布的时候使用-w参数编译.
    这里编译完就可以拿到 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 的目的

    相关文章

      网友评论

          本文标题:[剑走偏锋] Android使用Golang代替C/C++进行N

          本文链接:https://www.haomeiwen.com/subject/oxqyvctx.html