美文网首页
不会写JNI?最简单的写法,你知道吗?

不会写JNI?最简单的写法,你知道吗?

作者: 沉淀者 | 来源:发表于2020-09-25 17:51 被阅读0次

    Q:读完这边文章之后你能收获什么?
    A:不知道你们写过jni吗,你要是一点也没了解过先别看完这段话,先去看下面第一点传统的JNI是怎么写的,然后再回来看剩下的话。

    接着:你还在通过类型转换的方式不断去jni.h文件查找类型从而写出对应C或者C++的jni代码吗?其实这不是java,也不是C或者C++,相当于重新学习一门新的语言JNI语言,这样太浪费时间了。通过Swig我们可以解放我们的双手,专心写我们正宗的C或者C++代码,不需要写这种半JNI半C的语言,也不需要我们去写Java层的native方法,是不是很神奇,是不是很想学?别急,通过这篇文章之后你就可以随心所欲专心致志写自己的C或者C++代码了。

    生成 jni方式有两种方式。一种是通过SWIG从C++代码生成过度的java代码;另一种是通过javah的方式从java代码自动生成过度的C++代码。两种方式下的步骤流程正好相反。采用第二种方式生成jni,实现JNI封装代码和处理数据类型之间转换繁琐且耗时,因此本文采用swig的方式生成java代码。先介绍下第二种传统方式

    一、解析传统的JNI写法

    注意:这里不详细介绍JNI的传统写法,因为我的最终目的是不需要写这些文件,但是你还是得去先了解下传统的写法是怎么样的,这样才能对下面我介绍的方法比较深入,这里我介绍一遍文章,写的很详细,你们可以去看看它里面传统JNI写法。但是你只需要看完第八点就行了。后面介绍怎么生成.so文件看我这里介绍比较详细。不怎么熟悉JNI,NDK的也可以先去了解下他的第一篇文章。
    https://www.jianshu.com/p/b4431ac22ec2

    1.写Java层的本地方法
    public class JNI {
    
        static {
            System.loadLibrary("Hello");
        }
    
        /**
         * 定义native方法
         * 调用C代码对应的方法
         * @return
         * cjh.com.example.ndk.JNI
         */
        public native String sayHello();
        
    }
    
    2.通过javah生成头文件然后写对应的C实现方法
    /**
     * jstring :返回值
     * Java_全类名_方法名
     * JNIEnv* env:里面有很多方法
     * jobject jobj:谁调用了这个方法就是谁的实例
     * 当前就是JNI.this
     * cjh.com.example.ndk.JNI.sayHello
     */
    
     jstring Java_cjh_com_ndkdemo_JNI_sayHello(JNIEnv* env,jobject jobj){
    
         //jstring     (*NewStringUTF)(JNIEnv*, const char*);
         char* text = "I am from c!!!";
         return  (*env)->NewStringUTF(env,text);
     }
    
    3.生成.so文件

    这边先不介绍怎么生成.so文件,后面我会详细介绍。

    4.开始使用
     @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            String result = new JNI().sayHello();
            System.out.println("result==" + result);
        }
    
    5.分析

    你是不是对下面这个代码很晕

     jstring Java_cjh_com_ndkdemo_JNI_sayHello(JNIEnv* env,jobject jobj){
         //jstring     (*NewStringUTF)(JNIEnv*, const char*);
         char* text = "I am from c!!!";
         return  (*env)->NewStringUTF(env,text);
     }
    

    这到底是C还是C++还是Java,不,你错了,这是JNI特有的语法,你还得在找string对应的JNI的类型jstring.
    JNIEnv到底是什么啊,怎么用啊,我怎么知道它里面的生成字符串的方法是哪个啊,这里面这个多个方法,我怎么知道什么意思啊?你是不是很多疑问。没学过JNI之前听别人说都是写C或者C++的啊,怎么变成写JNI语法了,你骗我,我不学了。

    别...放弃,下面跟着我来,你就不需要写这段最复杂最讨厌的代码了,只需要写我们熟悉的C或者C++就行了

    二、Swig

    1.Swig是什么?

    SWIG(Simplified Wrapper and Interface Generator)是一个将C/C++接口转换为其他语言接口的工具,从而可以讲C/C++的库集成到其他语言的系统中。目前SWIG已经可以支持Python, Java, C#,Ruby,PHP,R语言等十多种语言。 是不是还是不清楚,我跟你说啊。来喽,Swig是C++或者C开发人员经常使用的工具,通过它,你就可以生成上面那段烦人的代码了。是不是很神奇,你先听我说它是怎么实现的。你只需要写我们熟悉的C或者C++的.c、.cpp文件就行了。然后你再写一个Swig的配置文件,是.i结尾的。然后Swig命令就可以生成上面那段烦人的代码了,你什么都不需要看,也不需要去了解里面是什么内容。通通抛弃。它还会生成Java的接口文件,什么是Java接口文件,我们之前传统的写法不是需要在Java中先写我们的native方法吗,去供上层调用。这个就是Java成的native文件,我们也不需要写它帮我们生成了,我们只需要把这些文件打包成jar包然后引用就行了。

    好累啊,我不想写下去了,我没动力。答应我,给我三连好不好,好不好,好不好。不然我放弃了.....

    2.安装使用

    下载链接:http://www.swig.org/download.html
    下载完成后配置环境变量,这个你就得去百度下很简答的,我这里不说那么多了,好嘞的哦。
    官方资料:http://www.swig.org/Doc3.0/SWIGDocumentation.html#Android_examples

    3.总体流程

    这里先总结下大概流程,在你脑海中有个大致过程,看起来会轻松很多。
    1)写C或者C++
    2)写Swig的配置文件Unix.i
    3)使用Swig命令生成文件
    4)编译.so文件和打包jar
    大概先这么说,下面看我详细介绍。

    c或者c++编译生成.so动态库包含两种方式:一种是通过创建Android.mk文件采用ndk-build编译的方式,这种方式一般只用于老版本的安卓项目中,因此已经不推荐使用。第二种方式就是采用cmake构建工具的方式直接生成.so动态库,这种方式是当前主流方式,因此本文采用cmake方式进行编译生成.so动态库。

    四、操作步骤

    按照我的操作步骤来,后面你就懂流程了。我这边只是介绍大概流程,参照的还是这边文章,他这里面有图,我这边只是大概说下流程。你先去那边了解下大概流程,什么是mk编译和CMake编译,大概看下就回来,他那边没有介绍Swig,具体还是看我这边。https://www.jianshu.com/p/b4431ac22ec2

    1、新建工程

    1)新建一个工程

    新建Android工程时勾选Include C++ support,之后按照默认下一步。Customize C++ SupportCustom的自定义项目中包含三部分。以下说明:
    · C++ Standard:即C++标准,使用下拉列表选择你希望使用的C++的标准,选择Toolchain Default 会使用默认的CMake设置。
    · Exceptions Support:如果你希望启用对C++异常处理的支持,请选择此复选框。如果启动此复选框,Android Studio 会将-fexceptions标志添加到模块级build.gradle文件的cppFlags中,Gradle会将其传递到CMake。
    · Runtime Type Information Support:如果开发者希望支持RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将-frtti标志添加到模块级build.gradle文件的cppFlags中,Gradle会将其传递到CMake。

    2)新建项目文件结构说明

    · 在 cpp 文件夹中:可以找到属于项目的所有原生源文件等构建库。对于新项目,Android Studio会创建一个示例C++源文件 native-lib.cpp,并将其置于应用模块src/main/cpp/目录中。这个示例代码提供了一个简单的C++函数stringFromJNI(),此函数可以返回字符串“Hello from C++”
    · 在 External Build Files 文件夹中:可以找到CMake或 ndk-build 的构建脚本。与build.gradle文件指示Gradle构建应用一样,CMake和ndk-build需要一个构建脚本来了解如何构原生库。对于新项目,Android Studio 会创建一个CMake 构建脚本CMakeLists.txt,并将其置于模块根目录中。

    2.编写C++源代码

    1)新建文件夹

    在src/main目录下面创建jni文件夹,与java同级。jni目录下面创建src文件夹用来保存源代码,也就是.cpp和.h等源代码。这里采用C++方式进行演示。

    Hello.h

    #include <cstring>
    using namespace std;
    #ifndef NDKDEMO_HELLO_H
    #define NDKDEMO_HELLO_H
    
    string getText();
    
    #endif //NDKDEMO_HELLO_H
    

    Hello.cpp

    #include "Hello.h"
    string getText(){
        return "I am from c++";
    }
    

    2.编写Unix.i文件

    在jni目录下面创建一个.i文件结尾的swig解析文件,本demo中创建为Unix.i文件。文件如下:

    --Unix.i文件

    %module(directors="1") HelloLib   //指定模块名 directors="1" 代表可以对C++的类在JAVA中继承
    %include "std_string.i"
    %{
    #include "Hello.cpp"//这是最终打包成Unix_wrap.cxx文件里面包含的C或者C++内容
    %}
    %include "Hello.h"   //这是生成的Java包含的内容
    
    3、Swig生成文件

    1)执行命令

    在终端切换当前路径到jni目录下面,开始使用swig命令编译生成java代码。命令如下:

    swig.exe -c++ -java -package com.geo.earthworklib -outdir F:/AllProjects/EarthworkLib/app/src/main/java/com/geo/earthworklib -o Unix_wrap.cxx Unix.i
    
    //-c++ 指定当前语言是C++还是C,默认是C,只有这两种,没有其他的
    //-java 生成的包装语言,可以使其他任何一种支持的语言 如-python -csharp
    //-package 生成的swig java类的包名
    //-outdir java文件放在哪里
    //-o 输出的CXX文件的文件名
    //i文件路径
    

    2)生成的文件说明

    此时就在当前目录会生成一个Unix_wrap.cxx文件,这个就是生成的jni语法的c++包装类,也就是使用cmake编译生成.so的源文件。此时还会在设置的-outdir路径下面生成java接口文件,这个接口文件也就是最终打包成jar包调用的。

    四、CMake生成.so文件

    1.编写CMakeLists.txt
    #指定CMake的最小版本
    cmake_minimum_required(VERSION 3.4.1)
    
    #设置生成的so动态库最后输出的路径
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI})
    #创建一个静态或者动态库,并提供其关联的源文件路径,开发者可以定义多个库,
    #CMake会自动去构建它们。Gradle可以自动将它们打包进APK中。
    #第一个参数——native-lib:是库的名称
    #第二个参数——SHARED:是库的类别,是动态的还是静态的
    #第三个参数——src/main/cpp/native-lib.cpp:是库的源文件的路径
    add_library( # Sets the name of the library.
                 earthworklib
    
                 # Sets the library as a shared library.
                 SHARED
    
                 # Provides a relative path to your source file(s).
                 src/main/jni/Unix_wrap.cxx )
    
    
    2.检查当前module的build.gradle文件下检查配置是否正确
    externalNativeBuild {
        cmake {
        //创建项目时添加额配置
            cppFlags "-frtti -fexceptions"
        //指定生成的cpu架构
            abiFilters 'armeabi-v7a','x86'
        }
    }
    externalNativeBuild {
        cmake {
        //CMakeLists.txt文件的路径
            path "CMakeLists.txt"
        }
    }
    
    3.编译

    编译完成之后会在CMakeLists.txt文件中指定生成.so的文件目录下这里也就是libs文件夹下生成.so文件

    4.生成Jar包

    给链接给你们学习,自己去学习下.
    注意下现在生成的原始jar包位置在这里app\build\intermediates\packaged-classes
    https://www.jianshu.com/p/1a69e2fcaed5

    五、总结

    其实说白了就是你先编写好.cpp源文件,然后使用Swig工具就可以生成含jni语法的Unix.wrap.cxx文件,这个文件然后通过传统的方式mk或者cmake方式最终就可以生成.so库了。Swig也会生成Java接口文件,只需要把这个文件打包成Jar包,这样.so和jar包都有了就OK了。其实就分两步,第一步是使用Swig生成文件,第二部就是使用Cmake或者mk生成.so。大功告成,就是这样。你只需要去编写Swig的.i文件后面就是一系列自然的事了。

    关于Swig的参考资料少之又少,下面给链接给你们参考学习。
    https://www.jianshu.com/p/a91f4e3e20c3

    你是不是觉得还要去了解Swig命令,还要去了解CMake是什么,不知道CMakeLists文件怎么写。这么多步骤好烦杂啊,下面一篇文章我会介绍更简单的方法,什么都不需要干,只需要编译一下什么都有了。你是不是觉得我在吹牛,你过来看啊,你要是累了你先大概在回忆一下大概流程,后面我会把Swig包含在我写的CMakeLists文件里面,通过构建工具一步解决。

    六、最后

    我其实有很多话想说明的,但是写起来实在是太耗费时间了,很多还是没怎么解释清楚的,我知道你们肯定还有很多困惑,你需要多看两遍,熟悉操作之后就很简单了。你们不懂得可以下面留言评论,我知道的话一定知无不言言无不尽。

    最后,创作不易,感谢您的阅读,要是有收获请记得三连点击,别告诉我下次一定!
    您的支持是我写作的最大动力!谢谢亲

    image.png

    相关文章

      网友评论

          本文标题:不会写JNI?最简单的写法,你知道吗?

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