1.JNI-Java Native Interface的缩写
API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI标准至少要保证本地代码能工作在任何Java虚拟机环境。
Android NDK官方原文档:developer.android.google.cn/ndk/
官方的Android体系架构图

可以看到Android上层的Application和ApplicationFramework都是使用Java编写,底层包括系统和使用众多的Libraries都是C/C++编写的,所以上层Java要调用底层的C/C++函数库必须通过Java的JNI来实现
1.1 JNI 与 NDK 区别
- JNI:JNI是一套编程接口,用来实现Java代码与本地的C/C++代码进行交互;
- NDK: NDK是Google开发的一套开发和编译工具集,可以生成动态链接库,主要用于Android的JNI开发
1.2 JNI 作用
- 扩展:JNI扩展了JVM能力,驱动开发,例如开发一个wifi驱动,可以将手机设置为无限路由;
- 高效: 本地代码效率高,游戏渲染,音频视频处理等方面使用JNI调用本地代码,C语言可以灵活操作内存;
- 复用: 在文件压缩算法 7zip开源代码库,机器视觉,OpenCV开放算法库等方面可以复用C平台上的代码,不必在开发一套完整的Java体系,避免重复发明轮子;
- 特殊: 产品的核心技术一般也采用JNI开发,不易破解;
1.3 JNI在Android中作用: JNI可以调用本地代码库(即C/C++代码),并通过 Dalvik 虚拟机与应用层和应用框架层进行交互,Android中JNI代码主要位于应用层和应用框架层;
应用层: 该层是由JNI开发,主要使用标准JNI编程模型; 应用框架层: 使用的是Android中自定义的一套JNI编程模型,该自定义的JNI编程模型弥补了标准JNI编程模型的不足;
2. 两种基本的JNI开发流程
Android Studio版本:3.0.1 NDK下载和CMake下载
在Android Studio2.2以后,AS开始支持使用Cmake编译JNI的C++代码,使用LLDB调试程序。在此之前编译JNI代码使用ndk-build编译工具。
在Android Studio 3.0.1中配置jni需要在SDK Tools中下载支持JNI开发的配置,如下图 在SDK Manager->SDK tool中下载下列四项:



2.1 新建项目开发JNI
接下来在Android studio3.0中正式开发JNI ,Android studio已经支持创建C/C++的开发,并使用CMake的模式构建NDK开发。
- 创建一个支持C/C++的Android项目


C++ Standard:使用下拉列表选择您希望使用哪种 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。
点击Finish等待项目创建完成。
- 支持C/C++项目的整体结构

和以前唯一不同的就是多出了cpp目录以及External Build Files两个目录,那么这两个都有什么用呢?
cpp 目录存放所有 native code 的地方,包括源码,头文件,预编译项目等。对于新项目,Android Studio 创建了一个 C++ 模板文件:native-lib.cpp,并且将该文件放到了你的 app 模块的 src/main/cpp/ 目录下。这份模板代码提供了一个简答的 C++ 函数:stringFromJNI(),该函数返回一个字符串:”Hello from C++”External Build Files 目录存放 CMake 或 ndk-build 构建脚本的地方。有点类似于 build.gradle 文件告诉 Gradle 如何编译你的APP 一样,CMake 和 ndk-build 也需要一个脚本来告知如何编译你的 native library。对于一个新的项目,Android Studio 创建了一个 CMake脚本:CMakeLists.txt,并且将其放到了你的 module 的根目录下
native-lib.cpp文件内容:
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring
JNICALL
Java_com_jni_demo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
简单可以看到,首先定义hello变量,之后return回该字符
MainActivity.java文件的内容
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
//应用启动时加载
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
//native方法
public native String stringFromJNI();
}
Android Studio2.2起开始使用Cmake编译C++代码,当然也兼容之前的ndk-build的方式。
配置Cmake 查看官网文档:developer.android.com/studio/proj…
CMakeLists.txt,简单的翻译下
# 有关使用CMake在Android Studio的更多信息,请阅读文档:https://d.android.com/studio/projects/add-native-code.html
# 设置CMake的最低版本构建本机所需库
cmake_minimum_required(VERSION 3.4.1)
# 创建并命名库,将其设置为静态的
# 或共享,并提供其源代码的相对路径。
# 你可以定义多个library库,并使用CMake来构建。
# Gradle会自动将包共享库关联到你的apk程序。
add_library( # 设置库的名称
native-lib
# 将库设置为共享库。
SHARED
# 为源文件提供一个相对路径。
src/main/cpp/native-lib.cpp )
# 搜索指定预先构建的库和存储路径变量。因为CMake包括系统库搜索路径中默认情况下,只需要指定想添加公共NDK库的名称,在CMake验证库之前存在完成构建
find_library( # 设置path变量的名称
log-lib
# 在CMake定位前指定的NDK库名称
log )
# 指定库CMake应该链接到目标库中,可以链接多个库,比如定义库,构建脚本,预先构建的第三方库或者系统库
target_link_libraries( # 指定目标库
native-lib
# 目标库到日志库的链接 包含在NDK
${log-lib} )
build.gradle文件

可以看出,AS帮我们配置cmake时自动帮我们添加了,上述两块代码。但是在我们自己配置cmake工具时,需要自己手动填写,拷贝。
我们Build编译一下,在编译输出文件夹 可以看到:

编译到运行示例 APP 的流程过程:
- Gradle 调用外部构建脚本,也就是 CMakeLists.txt;
- CMake 会根据构建脚本的指令去编译一个 C++ 源文件,也就是 native-lib.cpp,并将编译后的产物扔进共享对象库中,并将其命名为
libnative-lib.so,然后 Gradle 将其打包到 APK 中; - 在运行期间,APP 的 MainActivity 会调用 System.loadLibrary() 方法,加载 native library。而这个库的原生函数,stringFromJNI(),就可以为 APP 所用了;
- MainActivity.onCreate() 方法会调用 stringFromJNI(),然后返回 “Hello from C++”,并更新 TextView 的显示;
注意:Instant Run 并不兼容使用了 native code 的项目。Android Studio 会自动禁止 Instant Run 功能。
.so库在apk里面:

在原项目基础上增加自己的代码
//在MainActivity.java中增加一个native方法
public native String getHelloJni();
你会发现getHelloJni( )方法是红色的。不要急,按住Alt+Enter回车后,系统会自动为你在之前.cpp文件中创建一个getHelloJni( )的C++代码,是不是很智能……
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring
JNICALL
Java_com_jni_demo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_jni_demo_MainActivity_getHelloJni(JNIEnv *env, jobject instance) {
// TODO
return env->NewStringUTF(returnValue);
}
在原项目基础上修改库的名字



2.2 在现有Project上开发JNI
- 第一步: 在java文件夹中新建JniTest.java,把需要的native方法先定义好。
package com.baidu.jni.demo;
/**
* <p/>
* 功能 :
* <p/>
* <p>
* <p>Copyright baidu.com 2018 All right reserved</p>
*
* @author tuke 时间 2018/6/1
* @email tuke@baidu.com
* <p>
* 最后修改人 无
*/
public class JniTest {
static {
System.loadLibrary("sayhello");
}
public native String getHello(String string, int[] ints);
public native String getSayBaibai(int a ,float b,boolean c);
}
然后build 项目,在build文件夹找到是否生成JniTest.class字节码文件.只有生成了.class字节码文件才能下一步

-
第二步:需要使用javah 命令生成.h头文件。
- 1,手动命令生成 在AS下的Terminal 进入当前工程目录:
cd app/build/intermediates/classes/debug
然后通过命令行:
javah -jni com.baidu.jni.demo.JniTest
JDK 10以后移除了javah命令,JDK10、JDK11、JDK12新特性,使用javac -h . xxxx.java 代替
其中com.baidu.jni.demo.是包名,JniTest是java代码。
然后会在当前目录下生成:com_baidu_jni_demo_JniTest.h头文件
然后在src->main 新建jni文件夹,新建xxx.cpp文件,并且把刚才生成的com_baidu_jni_demo_JniTest.h头文件,拷贝到jni文件夹,编写jnitest.c如下:
//
// Created by Tu,Ke on 2018/6/1.
//
#include "com_baidu_jni_demo_JniTest.h"
//extern "C"
JNIEXPORT jstring JNICALL
Java_com_baidu_jni_demo_JniTest_getHello
(JNIEnv * env, jclass, jstring, jintArray) {
return env->NewStringUTF("helloworld");
}
//extern "C"
JNIEXPORT jstring JNICALL
Java_com_baidu_jni_demo_JniTest_getSayBaibai(JNIEnv *env, jobject instance, jint a, jfloat b,
jboolean c) {
// TODO
return env->NewStringUTF("helloworld");
}
- 2,工具生成javah命令 在Android Studio 的Preference 中的External Tool中新建工具:

在"Program"是JDK的javah命令的路径,我的Mac结尾没有.exe,有的资料是javah.exe可能是Windows这样配置。

在JniTest.java文件右键,选择External Tool ->javah -jni 然后会自动在src->main下新建一个jni文件夹,并且自动生成com_baidu_jni_demo_JniTest.h头文件,和第一种方法一模一样。
接着新建C文件,include进来就OK。
在JniTest.java文件中新建一个native方法,开始是红色的,使用上面的External Tool->javah -jni 生成之后如下图:

此时.h头文件已经被更新:

然后在C文件里继续实现就好。
- 第三步:还是使用Cmake编译C++代码,和上面不同的是 手动添加CmakeLists.txt文件,并修改build.gradle文件
添加CmakeLists.txt文件,在app的目录下:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
sayhello
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/jni/hello.cpp )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
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 )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
sayhello
# Links the target library to the log library
# included in the NDK.
${log-lib} )
build.gradle添加,下面两项:

这样整个JNI过程就完成了,下面就是靠自己学习编写JNI代码实现so逻辑了。
3.JNI数据类型
- JNI数据类型映射 由头文件代码可以看到,jni.h有很多类型预编译的定义,并且区分了 C 和 C++的不同环境。
#ifdef HAVE_INTTYPES_H
# include <inttypes.h> /* C99 */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#else
typedef unsigned char jboolean; /* unsigned 8 bits */
typedef signed char jbyte; /* signed 8 bits */
typedef unsigned short jchar; /* unsigned 16 bits */
typedef short jshort; /* signed 16 bits */
typedef int jint; /* signed 32 bits */
typedef long long jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#endif
/* "cardinal indices and sizes" */
typedef jint jsize;
#ifdef __cplusplus
/*
* Reference types, in C++
*/
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
//……
typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
//……
#else /* not __cplusplus */
/*
* Reference types, in C.
*/
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
//……
#endif
当是C++环境时,jobject, jclass, jstring, jarray等都是继承自_jobject类,而在 C 语言环境是,则它的本质都是空类型指针typedef void* jobject;
- 基本数据类型
下图是Java基本数据类型和本地类型的映射关系,这些基本数据类型都是可以直接在 Native 层直接使用的:
- 引用数据类型 另外,还有引用数据类型和本地类型的映射关系:

需要注意的是,
1)引用类型不能直接在 Native 层使用,需要根据 JNI 函数进行类型的转化后,才能使用; 2)多维数组(含二维数组)都是引用类型,需要使用 jobjectArray 类型存取其值;
- 方法和变量 ID
同样不能直接在 Native 层使用。当 Native 层需要调用 Java 的某个方法时,需要通过 JNI 函数获取它的 ID,根据 ID 调用 JNI 函数获取该方法;变量的获取也是类似。ID 的结构体如下:
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID* jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID* jmethodID; /* method IDs */
4.JNI 描述符
- 域描述符
-
基本类型描述符 下面是基本的数据类型的描述符,除了 boolean 和 long 类型分别是 Z 和 J 外,其他的描述符对应的都是Java类型名的大写首字母。另外,void 的描述符为 V
-
引用类型描述符 一般引用类型描述符的规则如下,注意不要丢掉“;”:
L + 类描述符 + ;如,String 类型的域描述符为:Ljava/lang/String;数组的域描述符特殊一点,如下,其中有多少级数组就有多少个“[”,数组的类型为类时,则有分号,为基本类型时没有分号[ + 其类型的域描述符
-
例如:
int[] 描述符为 [I
double[] 描述符为 [D
String[] 描述符为 [Ljava/lang/String;
Object[] 描述符为 [Ljava/lang/Object;
int[][] 描述符为 [[I
double[][] 描述符为 [[D
对应在 jni.h 获取 Java 的字段的 native 函数如下,name为 Java 的字段名字,sig 为域描述符
//C
jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);
//C++
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
{ return functions->GetFieldID(this, clazz, name, sig); }
jobject GetObjectField(jobject obj, jfieldID fieldID)
{ return functions->GetObjectField(this, obj, fieldID); }
- 类描述符
类描述符是类的完整名称:包名+类名,java 中包名用 . 分割,jni 中改为用 / 分割
如,Java 中 java.lang.String 类的描述符为 java/lang/String
native 层获取 Java 的类对象,需要通过 FindClass() 函数获取, jni.h 的函数定义如下:
//C
jclass (*FindClass)(JNIEnv*, const char*);
//C++
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
name 就是类的引用类型描述符,如 Java 对象 cn.cfanr.jni.JniTest,对应字符串为Lcn/cfanr/jni/JniTest; 如下:
jclass jclazz = env->FindClass("Lcn/cfanr/jni/JniTest;");
- 方法描述符
方法描述符需要将所有参数类型的域描述符按照声明顺序放入括号,然后再加上返回值类型的域描述符,其中没有参数时,不需要括号,如下规则:
(参数……)返回类型
例如:
Java 层方法 ——> JNI 函数签名
String getString() ——> Ljava/lang/String;
int sum(int a, int b) ——> (II)I
void main(String[] args) ——> ([Ljava/lang/String;)V
另外,对应在 jni.h 获取 Java 方法的 native 函数如下,其中 jclass 是获取到的类对象,name 是 Java 对应的方法名字,sig 就是上面说的方法描述符:
所有参数就是 类的对象,函数名,方法描述符(其实就包含参数列表和返回值类型了)
//C
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//C++
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
{ return functions->GetMethodID(this, clazz, name, sig); }
不过在实际编程中,如果使用 javah 工具来生成对应的 native 代码,就不需要手动编写对应的类型转换了。
5.JNIEnv 分析
JNIEnv 是 jni.h 文件最重要的部分,它的本质是指向函数表指针的指针(JavaVM也是),函数表里面定义了很多 JNI 函数,同时它也是区分 C 和 C++环境的(由上面介绍描述符时也可以看到),在 C 语言环境中,JNIEnv 是strut JNINativeInterface*的指针别名。
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv; //C++中的 JNIEnv 类型
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv; //C语言的 JNIEnv 类型
typedef const struct JNIInvokeInterface* JavaVM;
#endif
-
JNIEnv的特点
- JNIEnv 是一个指针,指向一组 JNI 函数,通过这些函数可以实现 Java 层和 JNI 层的交互,就是说通过 JNIEnv 调用 JNI 函数可以访问 Java 虚拟机,操作 Java 对象;
- 所有本地函数都会接收 JNIEnv 作为第一个参数;(不过 C++ 的JNI 函数已经对 JNIEnv 参数进行了封装,不用写在函数参数上)
- 用作线程局部存储,不能在线程间共享一个 JNIEnv 变量,也就是说 JNIEnv 只在创建它的线程有效,不能跨线程传递;相同的 Java 线程调用本地方法,所使用的 JNIEnv 是相同的,一个 native 方法不能被不同的 Java 线程调用;
-
C++的 JNIEnv
由typedef _JNIEnv JNIEnv;可知,C++的 JNIEnv 是 _JNIEnv 结构体,而 _JNIEnv 结构体定义了 JNINativeInterface 的结构体指针,内部定义的函数实际上是调用 JNINativeInterface 的函数,所以C++的 env 是一级指针,调用时不需要加 env 作为函数的参数,例如:env->NewStringUTF(env, "hello")
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
#if defined(__cplusplus)
jint GetVersion()
{ return functions->GetVersion(this); }
jclass DefineClass(const char *name, jobject loader, const jbyte* buf, jsize bufLen)
{ return functions->DefineClass(this, name, loader, buf, bufLen); }
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
jmethodID FromReflectedMethod(jobject method)
{ return functions->FromReflectedMethod(this, method); }
jfieldID FromReflectedField(jobject field)
{ return functions->FromReflectedField(this, field); }
jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic)
{ return functions->ToReflectedMethod(this, cls, methodID, isStatic); }
jclass GetSuperclass(jclass clazz)
{ return functions->GetSuperclass(this, clazz); }
//……
}
6.JNI 的两种注册方式
Java 的 native 方法是如何链接 C/C++中的函数的呢?可以通过静态和动态的方式注册JNI。
- 静态注册:原理:根据函数名建立 Java 方法和 JNI 函数的一一对应关系。流程如下:
- 先编写 Java 的 native 方法;
- 然后用 javah 工具生成对应的头文件,执行命令 javah packagename.classname可以生成由包名加类名命名的 jni 层头文件,或执行命名javah -o custom.h packagename.classname,其中 custom.h 为自定义的文件名;
- 实现 JNI 里面的函数,再在Java中通过System.loadLibrary加载 so 库即可;
静态注册的方式有两个重要的关键词 JNIEXPORT和 JNICALL,这两个关键词是宏定义,主要是注明该函数式 JNI 函数,当虚拟机加载 so 库时,如果发现函数含有这两个宏定义时,就会链接到对应的 Java 层的 native 方法。
由前面生成头文件的方法,重新创建一个cn.cfanr.test_jni.Jni_Test.java的类
public class Jni_Test {
private static native int swap();
private static native void swap(int a, int b);
private static native void swap(String a, String b);
private native void swap(int[] arr, int a, int b);
private static native void swap_0(int a, int b);
}
用 javah 工具生成以下头文件:
#include <jni.h>
/* Header for class cn_cfanr_test_jni_Jni_Test */
#ifndef _Included_cn_cfanr_test_jni_Jni_Test
#define _Included_cn_cfanr_test_jni_Jni_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: cn_cfanr_test_jni_Jni_Test
* Method: swap
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap__
(JNIEnv *, jclass); // 凡是重载的方法,方法后面都会多一个下划线
/*
* Class: cn_cfanr_test_jni_Jni_Test
* Method: swap
* Signature: (II)V
*/
JNIEXPORT void JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap__II
(JNIEnv *, jclass, jint, jint);
/*
* Class: cn_cfanr_test_jni_Jni_Test
* Method: swap
* Signature: (Ljava/lang/String;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap__Ljava_lang_String_2Ljava_lang_String_2
(JNIEnv *, jclass, jstring, jstring);
/*
* Class: cn_cfanr_test_jni_Jni_Test
* Method: swap
* Signature: ([III)V
*/
JNIEXPORT void JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap___3III
(JNIEnv *, jobject, jintArray, jint, jint); // 非 static 的为 jobject
/*
* Class: cn_cfanr_test_jni_Jni_Test
* Method: swap_0
* Signature: (II)V
*/
JNIEXPORT void JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap_10
(JNIEnv *, jclass, jint, jint); // 不知道为什么后面没有 II
#ifdef __cplusplus
}
#endif
#endif
可以看出 JNI 的调用函数的定义是按照一定规则命名的:
JNIEXPORT 返回值 JNICALL Java_全路径类名_方法名_参数签名(JNIEnv* , jclass, 其它参数);
其中 Java_ 是为了标识该函数来源于Java。
经检验(不一定正确),如果是重载的方法,则有“参数签名”,否则没有;另外如果使用的是 C++,在函数前面加上 extern “C”(表示按照 C 的方式编译),函数命名后面就不需要加上“参数签名”。
另外还需要注意几点特殊规则:
- java的包名或类名或方法名中含下划线
_,在c++里要用_1连接; - 重载的本地方法命名要用双下划线
__连接; 参数签名的斜杠“/”改为下划线_连接,分号;改为_2连接,左方括号[改为_3连接;- 对于 Java 的 native 方法,static 和非 static 方法的区别在于第二个参数,
static 的为 jclass,非 static 的 为 jobject;JNI 函数中是没有修饰符的。
优点: 实现比较简单,可以通过 javah 工具将 Java代码的 native 方法直接转化为对应的native层代码的函数; 缺点:
- javah 生成的 native 层函数名特别长,可读性很差;
- 后期修改文件名、类名或函数名时,头文件的函数将失效,需要重新生成或手动改,比较麻烦;
- 程序运行效率低,首次调用 native 函数时,需要根据函数名在 JNI 层搜索对应的本地函数,建立对应关系,有点耗时;
-
动态注册 原理:
直接告诉 native 方法其在JNI 中对应函数的指针。通过使用JNINativeMethod结构来保存Java native 方法和 JNI 函数关联关系,步骤: -
先编写 Java 的 native 方法;
-
编写 JNI 函数的实现(
函数名可以随便命名); -
利用结构体
JNINativeMethod 保存Java native方法和 JNI函数的对应关系; -
利用
registerNatives(JNIEnv* env)注册类的所有本地方法; -
在
JNI_OnLoad方法中调用注册方法; -
在Java中通过System.loadLibrary加载完JNI动态库之后,
会调用JNI_OnLoad函数,完成动态注册;
jni.h中的
//JNINativeMethod结构体
typedef struct {
const char* name; //Java中native方法的名字
const char* signature; //Java中native方法的描述符
void* fnPtr; //对应JNI函数的指针
} JNINativeMethod;
/**
* @param clazz java类名,通过 FindClass 获取
* @param methods JNINativeMethod 结构体指针
* @param nMethods 方法个数
*/
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
//JNI_OnLoad
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);
参考链接: