一、背景

很多开发者在开发过程中经常遇到有人问你JNI或者NDK的问题,而且JNI和NDK是非JAVA语言,需要C++来完成。C++在处理多媒体文件具有一定的有事,所以Java也提供了一个方法就是对NDK的支持。

很多人可能还在迷茫如何去编译如何去调用,下面我将讲解一下如何如入门

二、NDK的环境搭建

如果我们通过AndroidStudio来开发,那么只要设置了ndk.dirs=””即可。

1.Java环境:正常的classpath和javahome的配置

2.ndk:在androidstudio中配置好了ndk环境

三、AndroidStudio的配置

上面是开发环境的配置,接下来就是关于编译的配置

1.build.gradle:配置编译环境

在defaultConfig节点新增:

externalNativeBuild {cmake {cppFlags ""}}ndk {abiFilters 'x86_64', 'armeabi-v7a', 'arm64-v8a'}

主要配置编译环境和nkd的输出

2.jni编译文件配置

我们采用的是cmake进行编译了,所以在android{}节点下配置编译即可

externalNativeBuild {cmake {path "src/main/cpp/CMakeLists.txt"version "3.22.1"}}

path:需要执行的cmake地址,正常在main下面cpp文件下,用来编译链接cpp文件的

三、JNI类的编译与连接流程

上面讲解了编译配置,那么我们开始做编译类的工作了。正常我们会在main下新建一个cpp文件夹,用来专门放cpp文件和cmake编译脚本的。

1.新增native类:

随便在包下新增一个类作为测试:

public class TestJni {static {System.loadLibrary("test_jni");}public native String getMessage();}

Java和kotlin有所区别,kotlin的native不是native,是external。

2.编译生成h文件

2.1第一步先通过javac对java文件进行编译

找到目标文件所在的根目录,

jni> javac .\TestJni.java,执行完会生成一个class文件

2.2生成.h文件(jni文件)

通过javah -jni 类,这个命令是对刚才编译完的class导出为jni文件。这个命令要回到src节点下

在src节点下,在终端通过命令来编译:javah -jni packagename.className

一定要输入完整的包名和类名,如果Test在包com.jni.test下,那么就是javah -jni com.jni.test.Test

会在src下面生成一个.h的c文件,这个文件就是我们要用到的jni信息文件。这个文件名一般是就是包名和类型的组合

3.cpp文件的编写

通过上面我们基本完成了jni的前期准备,接下来就是cpp文件的创建,jni是通过c++来创建的,刚才的c文件其实是一个没有函数体的函数声明,我们需要自己通过c++文件来实现函数体

3.1我们先把生成的信息放到cpp文件夹中

正常编译一个cpp需要至少一个cmake和cpp。h文件可以有可以没有,如果你是通过jni生成的,需要导入。

#include "com_love_lovestudy_testdemo_jni_TestJni.h"JNIEXPORT jstring JNICALL Java_com_love_lovestudy_testdemo_jni_TestJni_getMessage(JNIEnv* env, jobject cls){const char a[]="ssssss";return env->NewStringUTF(a);}

我在test-jni中实现了h文件的函数体,返回了一个字符串。

4CMakeLists.txt的配置与编译

我们是直接通过cmake编译,不需要mk文件参与,所以所有的配置信息都在这里面,配置的核心就是:

1.so文件输出的位置
#so库的输出路径set(CMAKE_LIBRARY_OUTPUT_DIRECTORY${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

我们配置了输出在cpp文件统计目录的jnilibs文件夹中

2.so文件名
#1. 添加自己的so库test-lib,设置一个名字,输出就是这个名字add_library( # Sets the name of the library.test_jni# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).test_jni.cpp)

test_jni就是文件名,这个文件名和连接需要用到,test_jni.cpp是我们需要编译的目标cpp

3.cpp文件的导入

参考2,其实这两个就是导入和设置一个文件名,输出的so文件就是这个名字

4.链接cpp文件
target_link_libraries( # Specifies the target library.test_jni# Links the target library to the log library# included in the NDK.${log-lib})

连接的test_jni就是上面3的文件连接别名,这两个一定要一致,否则无法生成。

通过这个四部我们基本能生成自己的so文件了,一个cmake只能链接一个cpp文件,只能生成一个so文件,所以cmake与cpp是1v1的关系,后面会讲解多个cmake管理。

完成的如下:
#so库的输出路径set(CMAKE_LIBRARY_OUTPUT_DIRECTORY${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})#版本cmake_minimum_required(VERSION 3.4.1)#1. 添加自己的so库test-lib,设置一个名字,输出就是这个名字add_library( # Sets the name of the library.test_jni# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).test_jni.cpp)find_library( # Sets the name of the path variable.log-lib# Specifies the name of the NDK library that# you want CMake to locate.log )#add 库的名字和target的要一致,因为target链接add的文件target_link_libraries( # Specifies the target library.test_jni# Links the target library to the log library# included in the NDK.${log-lib})

5.生成so文件

生成so文件需要把cmakelist插入build.gradle文件。

通过build->make project或者rebuild project。执行完会生成如下

生成的so文件名:一般都是lib+你自己定义的名字

四、测试so的正确性

1.在native类中加载so文件

static {System.loadLibrary("test_jni");}

注意:我们生成的so文件名为libtest_jni.so,但是我们不需要lib这个前缀,只需要把我们在cmake中配置的名字加载进去即可。

2.正常调用

 testJni=new TestJni();textView.setText(testJni.getMessage());

五、总结

通过以上步骤,我们已完成了JNI/NDK的搭建与配置完毕,大家可以很清楚的看到so文件的生成整个过程。已基本掌握的JNI的编译与生成整个流程,包括问题的处理基本都有。可以很直观的定义自己的JNI文件