JNI和NDK

543 阅读2分钟

很多人并不清楚JNI和NDK的区别,JNI是Java中的概念,NDK是Android中的概念。JNI全名Java Native Interface(Java本地接口或Java原生接口),原生是什么意思?你的腿是原生的吧,不会是假腿吧。你可以理解成一个系统内部最核心的部分。JNI用来让Java和其他编程语言进行通信,主要是C和C++。而NDK是基于JNI的一套用于原生应用开发的工具包,全名为Native Development Kit。

JNI都有哪些数据类型

Java类型JNI类型函数签名对应的类型
bytejbyteB
shortjshortS
intjintI
longjlongJ
floatjfloatF
doublejdoubleD
booleanjbooleanZ
charjcharC
voidjvoidV
ObjectjobjectLjava/lang/Object;

创建Native项目

截屏2023-01-15 23.32.51.png 选择Native C++。我们再看一下创建出来的示例代码都写了些什么。

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

这里包含了jni的头文件,这个jni.h文件很重要,里面定义了很多跨语言调用的函数。JNI函数名称必须按照这个规则,Java_包名替换点为下划线_类名_方法名。返回值jstring,表示我们在Java层定义的方法返回一个String,env为JNI的一些基础环境,jobject这里代表MainActivity,即当前对象。env->NewStringUTF(hello.c_str());我们创建一个Java层支持的字符串返回给Java层。

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Example of a call to a native method
        binding.sampleText.text = stringFromJNI()
    }

    /**
     * A native method that is implemented by the 'myapplication' native library,
     * which is packaged with this application.
     */
    external fun stringFromJNI(): String

    companion object {
        // Used to load the 'myapplication' library on application startup.
        init {
            System.loadLibrary("myapplication")
        }
    }
}

native方法在Java中使用native关键字,而在Kotlin中使用external关键字,是一个意思。在调用JNI函数之前,我们需要加载动态库so文件,可以使用loadLibrary和load两种方式,loadLibrary是直接指定库名称,而load则是指定so文件的名称,必须为以lib开头并以.so结尾的文件。

在app模块中的build.gradle中,我们需要配置CMake的配置文件

externalNativeBuild {
    cmake {
        path file('src/main/cpp/CMakeLists.txt')
        version '3.18.1'
    }
}

使用CMake这种方式,我们可以在编译Java代码的时候也顺便着把动态库也编译出来,并依赖上。我们也可以将编译好的so文件引入进来直接使用,本文暂不讲解。

示例程序就是一个简单的返回一个native层的字符串给Java层来使用的案例。它只是抛砖引玉,我们是可以通过Android NDK开发很多更加强大的功能的。