目录
- 导读
- NDK
- JNI
- JNI原理
- JNI的引用
- JNI的简单实用
- Cmake工具demo的背后原理
- Java与Native的相互调用
- JNI中的签名
- 回顾和总结
If you feel you are sleepy, please have a rest. Then hard work.
要想搞清楚什么是JNI,我们需要将与之相管的内容全部整理出来:

导读
在Android开发中,Google提供了两种开发包,SDK和NDK,其中SDK是用来Java开发的,而NDK是用来c/c++开发的,即JNI编程方式,第三方应用可以通过JNI调用自己的c动态库,这就是NDK。
NDK
NDK(Native Develop Kit):是一套允许你使用原生代码语言,例如(c和c++)实现部分应用的工具集。在开发某些类型应用时,这有助于您重复使用这些语言编写的代码库。一般情况下NDK工具把C/C++编译为.co文件,然后再Java中调用。
- NDK可以提升应用性能(C/C++)
- 平台之间移植
- 使用第三方库
- 代码保护(不容易被反编译)
NDK到so

so库,即将C或者C++实现的功能进行打包,将其打包为共享库,让其他程序进行调用,这可以提高代码的复用性。linux生成的共享库以so结尾,在win下是dll结尾。
目前Android系统支持下面其中CPU结构,每一种对应着各自的应用程序二进制接口ABI(Application Binary Interface):定义了二进制文件(尤其是共享库文件)如何在相应的系统平台运行,从使用的指令集内存对齐以及可用的系统函数库。
- ARMv5 - armeabi
- ARMv7 - armeabi-v7a
- ARMv7 - arm64-v8a
- x86 - x86
- MPS - mips
- MIPS64 - mips64
- x86_64 - x86_64
JNI
JNI(Java Native Interface),即Java本地接口。JNI是Java调用Native 语言的一种特性。通过JNI可以使得Java与C/C++机型交互。由于JNI是JVM中规范的一部分,因此可以是我们可以复用以前用C/C++写的大量代码。JNI是一种弄在Java虚拟机机制下的执行代码的标准机制。 在使用JNI的时候,注意平台的跨建。如果要实现跨平台,就必须将本地代码在不同的操作系统平台下编译出相应的动态库。

JNI的命名规则:
1
JNIExport jstring JNICALL Java_com_example_hellojni_MainActivity_stringFromJNI( JNIEnv* env,jobject thiz )
jstring 是返回值类型
Java_com_example_hellojni 是包名
MainActivity 是类名
stringFromJNI 是方法名
其中JNIExport和JNICALL是不固定保留的关键字不要修改
如何实现JNI:
- 在Java中先声明一个native方法
- 编译Java源文件javac得到.class文件
- 通过javah -jni命令导出JNI的.h头文件
- 使用Java需要交互的本地代码,实现在Java中声明的Native方法(如果Java需要与C++交互,那么就用C++实现Java的Native方法)
- 将本地代码编译成动态库
- 通过Java命令执行Java程序,最终实现Java调用本地代码。
1
2
3
4
5
6
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (JNIEnv *env, jobject obj, jint i, jstring s)
{
const char *str = (*env)->GetStringUTFChars(env, s, 0);
(*env)->ReleaseStringUTFChars(env, s, str);
return 10;
}
- *env:一个接口指针
- obj:在本地方法中声明的对象引用
- i和s:用于传递的参数
JNI有自己的原始数据类型和数据引用类型如下:

JNI原理
在计算机系统中,每一种编程语言都有一个执行环境,执行环境来解释执行执行语言中的语句。
Java语言的执行环境是Java虚拟机(JVM),JVM其实是主机环境中的一个进程,每个JVM都在本地环境中有一个JavaVM结构体,该结构体在创建虚拟机的时候被返回。在JNI环境下创建JVM的函数为JNI_CreateJavaVM。
1
JNI_CreateJavaVM(JavaVM **pvm, void **penv, void*args);

其中JavaVM是JVM在JNI层的代表,JNI全局仅仅有一个JavaVM结构,在里面封装了一些函数指针(or函数表结构),JavaVm中封装的这些函数指针主要是对JVM操作接口。另外c和c++中的JavaVM的定义有所不同,在c中JavaVM是JNIInvokeInterface_类指针,而在C++中,进行了一次封装,比c中少一个参数。这也是为什么推荐使用c++来编写JNI函数。
JNIEnv:
JINEnv是当前Java线程的执行环境,一个JVM对应一个JavaVM结构体,一个JVM中可能创建多个Java线程,每个线程对应一个JNIEnv结构,它们保存在线程本地存储TLS中。因此不同的线程JNIEnv不同,而不能相互共享使用。 JavaEnv结构也是一个函数表,在本地代码通过JNIEnv函数表来操作Java数据或者调用Java方法。也就是说,只要在本地代码中拿到了JNIEnv结构,就可以在本地代码中调用Java代码。

- 调用Java 函数:JNIEnv代表了Java执行环境,能够使用JNIEnv调用Java中的代码
- 操作Java代码:Java对象传入JNI层就是jobject对象,需要使用JNIEnv来操作这个Java对象
JNIEnv的创建:
- C 中——JNIInvokeInterface:JNIInvokeInterface是C语言环境中的JavaVM结构体,调用 (AttachCurrentThread)(JavaVM, JNIEnv*, void) 方法,能够获得JNIEnv结构体
- C++中 ——_JavaVM:_JavaVM是C++中JavaVM结构体,调用jint AttachCurrentThread(JNIEnv** p_env, void* thr_args) 方法,能够获取JNIEnv结构体;
JNIEnv的释放:
- C 中释放:调用JavaVM结构体JNIInvokeInterface中的(DetachCurrentThread)(JavaVM)方法,能够释放本线程的JNIEnv
- C++ 中释放:调用JavaVM结构体_JavaVM中的jint DetachCurrentThread(){ return functions->DetachCurrentThread(this); } 方法,就可以释放 本线程的JNIEnv
JNIEnv和线程的关系:
- JNIEnv只在当前线程有效:JNIEnv仅仅在当前线程有效,JNIEnv不能在线程之间进行传递,在同一个线程中,多次调用JNI层方便,传入的JNIEnv是同样的
- 本地方法匹配多个JNIEnv:在Java层定义的本地方法,能够在不同的线程调用,因此能够接受不同的JNIEnv
JINEnv相关常用的函数:
1
2
3
4
5
6
7
8
9
jobject NewObject(JNIEnv *env, jclass clazz,jmethodID methodID, ...):
jobject NewObjectA(JNIEnv *env, jclass clazz,jmethodID methodID, const jvalue *args):
jobject NewObjectV(JNIEnv *env, jclass clazz,jmethodID methodID, va_list args):
jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len):
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
jobjectArray NewObjectArray(JNIEnv *env, jsize length,jclass elementClass, jobject initialElement);
jobject GetObjectArrayElement(JNIEnv *env,jobjectArray array, jsize index);
jsize GetArrayLength(JNIEnv *env, jarray array);
第一个参数jclass class 代表的你要创建哪个类的对象,第二个参数,jmethodID methodID代表你要使用那个构造方法ID来创建这个对象。只要有jclass和jmethodID,我们就可以在本地方法创建这个Java类的对象。
通过Unicode字符的数组来创建一个新的String对象。
env是JNI接口指针;unicodeChars是指向Unicode字符串的指针;len是Unicode字符串的长度。返回值是Java字符串对象,如果无法构造该字符串,则为null。
JNI的引用
Java内存管理这块是完全透明的,new一个对象或者释放一个对象,都是Java自己进行管理的,但是从Java虚拟机创建的对象传到C/C++代码就会产生引用,根据Java的垃圾回收机制,只要存在引用就不会对对象进行垃圾回收。
在JNI规范中定义了三种引用:局部引用、全局引用和弱全局引用。
- 局部引用(Local Reference)
局部引用,也成本地引用,通常是在函数中创建并使用。会阻止GC回收所有引用对象。这个是最常见的引用类型,基本上通过JNI返回来的引用都是局部引用,例如使用newobject,就会返回创建出来的是实例的局部引用,局部引用值在改native函数中有效,会在函数返回的时候自动释放,也可以使用DeleteLocalRef函数手动释放该对象。
- 全局引用(Global Reference)
全局引用可以跨方法、跨线程使用,直到被开发者显式释放。类似局部引用,一个全局引用在被释放前保证引用对象不被GC回收。和局部应用不同的是,没有俺么多函数能够创建全局引用。能创建全部引用的函数只有NewGlobalRef,而释放它需要使用ReleaseGlobalRef函数
- 弱全局引用(Weak Global Reference)
是JDK 1.2 新增加的功能,与全局引用类似,创建跟删除都需要由编程人员来进行,这种引用与全局引用一样可以在多个本地代码有效,不一样的是,弱引用将不会阻止垃圾回收期回收这个引用所指向的对象,所以在使用时需要多加小心,它所引用的对象可能是不存在的或者已经被回收。
- 引用比较
给定两个引用,我们可以通过下面的代码来判断是否是相同的引用。如果obj1和obj2指向相同的对象,则返回JNI_TRUE(或者1),否则返回JNI_FALSE(或者0).
1
(*env)->IsSameObject(env, obj1, obj2)
attention:有一个特殊的引用需要注意:NULL,JNI中的NULL引用指向JVM中的null对象,如果obj是一个全局或者局部引用,使用(*env)->IsSameObject(env, obj, NULL)或者obj == NULL用来判断obj是否指向一个null对象即可。但是需要注意的是,IsSameObject用于弱全局引用与NULL比较时,返回值的意义是不同于局部引用和全局引用的。代码如下:
1
2
3
4
jobject local_obj_ref = (*env)->NewObject(env, xxx_cls,xxx_mid);
jobject g_obj_ref = (*env)->NewWeakGlobalRef(env, local_ref);
// ... 业务逻辑处理
jboolean isEqual = (*env)->IsSameObject(env, g_obj_ref, NULL);
JNI的简单实用
实现JNI的方法用两种,第一种为传统方法,非常的复杂,这里就不做演示了,我们主要介绍第二种,实用CMAKE的方式。
在创建Android项目的时候选择Include C++ Support,里面有三个项目,我们选择第一个就行了:
-
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。
Android Studio 将添加cpp和External Build Files 组:
-
在 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,并将其置于模块根目录中。
Cmake工具demo的背后原理
CMake的入口:build.gradle配置文件中。
在defaultConfig中会有:
1
2
3
4
5
externalNativeBuild {
cmake {
cppFlags ""
}
}
在defaultConfig外面会有
1
2
3
4
5
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
- 在defaultConfig外面的externalNativeBuild里面的cmake指明了CMakeLists.txt的路径(在本项目下,和是build.gradle在同一个目录里面)。
- 在defaultConfig里面的externalNativeBuild里面的cmake主要填写的是CMake的命令参数。即由arguments中的参数最后转化成一个可执行的CMake的命令
在CMakeLists.txt文件中包含以下四个部分:

- cmake_minimum_required(VERSION 3.4.1):指定CMake的最小版本
- add_library:创建一个静态或者动态库,并提供其关联的源文件路径,开发者可以定义多个库,CMake会自动去构建它们。Gradle可以自动将它们打包进APK中。第一个参数——native-lib:是库的名称;第二个参数——SHARED:是库的类别,是动态的还是静态的;第三个参数——src/main/cpp/native-lib.cpp:是库的源文件的路径。
- target_link_libraries:指定CMake链接到目标库。开发者可以链接多个库,比如开发者可以在此定义库的构建脚本,并且预编译第三方库或者系统库。第一个参数——native-lib:指定的目标库;第一个参数——${log-lib}:将目标库链接到NDK中的日志库。
上面只是CMake的基本功能,CMake里面可以非常强大,有兴趣可以研究一下CMake手册。
Java与Native的相互调用
- 注册native函数
- JNI中的签名
- native代码反调用Java层代码

首先是native函数的注册:
- 静态注册:
先有java得到本地方法的声明,然后再通过JNI实现该声明方法
- 动态注册:
先通过JNI重载JNI_OnLoad()实现本地方法,然后直接在Java中调用本地方法。
静态方法注册:首先在Java代码中声明native函数,然后在c文件中实现方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class JniDemo1{
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI();
public native int integerFromJNI();
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jnitest_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_jnitest_MainActivity_integerFromJNI(JNIEnv *env, jobject instance) {
int a = 23;
return a;
}
动态方法注册:
上面我们介绍了静态注册native方法的过程,就是Java层声明的nativ方法和JNI函数一一对应。以我来说,刚开始做JNI的前期,可能会遵守静态注册的流程:1、编写带有native方法的Java类。 2、编写代码实现C文件中的方法,这样的单调的标准流程,而且还要忍受这么”长”的函数名。那有没有更简单的方式呢?比如让Java层的native方法和任意JNI函数连接起来?答案是有的——动态注册,也就是通过RegisterNatives方法把C/C++中的方法映射到Java中的native方法,而无需遵循特定的方法命名格式。
当我们使用System.loadLibarary()方法加载so库的时候,Java虚拟机就会找到这个JNI_OnLoad函数兵调用该函数,这个函数的作用是告诉Dalvik虚拟机此C库使用的是哪一个JNI版本,如果你的库里面没有写明JNI_OnLoad()函数,VM会默认该库使用最老的JNI 1.1版本。由于最新版本的JNI做了很多扩充,也优化了一些内容,如果需要使用JNI新版本的功能,就必须在JNI_OnLoad()函数声明JNI的版本。同时也可以在该函数中做一些初始化的动作,其实这个函数有点类似于Android中的Activity中的onCreate()方法。该函数前面也有三个关键字分别是JNIEXPORT,JNICALL ,jint。其中JNIEXPORT和JNICALL是两个宏定义,用于指定该函数时JNI函数。jint是JNI定义的数据类型,因为Java层和C/C++的数据类型或者对象不能直接相互的引用或者使用,JNI层定义了自己的数据类型,用于衔接Java层和JNI层
与JNI_OnLoad()函数相对应的有JNI_OnUnload()函数,当虚拟机释放的该C库的时候,则会调用JNI_OnUnload()函数来进行善后清除工作。
该函数会有两个参数,其中*jvm为Java虚拟机实例,JavaVM结构体定义一下函数:
1
2
3
4
DestroyJavaVM
AttachCurrentThread
DetachCurrentThread
GetEnv
首先加载共享库:
1
2
3
4
5
public class JniDemo1{
static {
System.loadLibrary("samplelib_jni");
}
}
接下来在c中实现:
1
jint JNI_OnLoad(JavaVM* vm, void* reserved)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <jni.h>
#include "Log4Android.h"
#include <stdio.h>
#include <stdlib.h>
using namespace std;
#ifdef __cplusplus
extern "C" {
#endif
static const char *className = "com/gebilaolitou/jnidemo/JNIDemo2";
static void sayHello(JNIEnv *env, jobject, jlong handle) {
LOGI("JNI", "native: say hello ###");
}
static JNINativeMethod gJni_Methods_table[] = {
{"sayHello", "(J)V", (void*)sayHello},
};
static int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
LOGI("JNI","Registering %s natives\n", className);
clazz = (env)->FindClass( className);
if (clazz == NULL) {
LOGE("JNI","Native registration unable to find class '%s'\n", className);
return -1;
}
int result = 0;
if ((env)->RegisterNatives(clazz, gJni_Methods_table, numMethods) < 0) {
LOGE("JNI","RegisterNatives failed for '%s'\n", className);
result = -1;
}
(env)->DeleteLocalRef(clazz);
return result;
}
jint JNI_OnLoad(JavaVM* vm, void* reserved){
LOGI("JNI", "enter jni_onload");
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
jniRegisterNativeMethods(env, className, gJni_Methods_table, sizeof(gJni_Methods_table) / sizeof(JNINativeMethod));
return JNI_VERSION_1_4;
}
#ifdef __cplusplus
}
#endif
1
2
3
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result ;
}
这里调用了GetEnv函数时为了获取JNIEnv结构体指针,其实JNIEnv结构体指向了一个函数表,该函数表指向了对应的JNI函数,我们通过这些JNI函数实现JNI编程。
然后就调用了jniRegisterNativeMethods函数来实现注册,这里面注意一个静态变量gJni_Methods_table。它其实代表了一个native方法的数组,如果你在一个Java类中有一个native方法,这里它的size就是1,如果是两个native方法,它的size就是2,大家看下我这个gJni_Methods_table变量的实现:
1
2
3
static JNINativeMethod gJni_Methods_table[] = {
{"sayHello", "(J)V", (void*)sayHello},
};
我们看到他的类型是JNINativeMethod ,那我们就来研究下JNINativeMethod:
JNI允许我们提供一个函数映射表,注册给Java虚拟机,这样JVM就可以用函数映射表来调用相应的函数。这样就可以不必通过函数名来查找需要调用的函数了。Java与JNI通过JNINativeMethod的结构来建立联系,它被定义在jni.h中,其结构内容如下:
1 2 3 4 5 typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;
- 第一个变量name,代表的是Java中的函数名
- 第二个变量signature,代表的是Java中的参数和返回值
- 第三个变量fnPtr,代表的是的指向C函数的函数指针
下面我们再来看下jniRegisterNativeMethods函数内部的实现:
首先通过clazz = (env)->FindClass( className);找到声明native方法的类
然后通过调用RegisterNatives函数将注册函数的Java类,以及注册函数的数组,以及个数注册在一起,这样就实现了绑定。
上面在讲解JNINativeMethod结构体的时候,提到一个概念,就是”signature”即签名,这个是什么东西?我们下面就来讲解下。
JNI的签名
为什么JNI中突然多出了一个概念叫”签名”:
因为Java是支持函数重载的,也就是说,可以定义相同方法名,但是不同参数的方法,然后Java根据其不同的参数,找到其对应的实现的方法。这样是很好,所以说JNI肯定要支持的,那JNI要怎么支持那,如果仅仅是根据函数名,没有办法找到重载的函数的,所以为了解决这个问题,JNI就衍生了一个概念——”签名”,即将参数类型和返回值类型的组合。如果拥有一个该函数的签名信息和这个函数的函数名,我们就可以顺序的找到对应的Java层中的函数了。
如何查看签名呢:可以使用javap命令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
javap -s -p MainActivity.class
Compiled from "MainActivity.java"
public class com.example.hellojni.MainActivity extends android.app.Activity {
static {};
Signature: ()V
public com.example.hellojni.MainActivity();
Signature: ()V
protected void onCreate(android.os.Bundle);
Signature: (Landroid/os/Bundle;)V
public boolean onCreateOptionsMenu(android.view.Menu);
Signature: (Landroid/view/Menu;)Z
public native java.lang.String stringFromJNI(); //native 方法
Signature: ()Ljava/lang/String; //签名
public native int max(int, int); //native 方法
Signature: (II)I //签名
}
签名有哪些:


native代码反调用Java层代码:
首先获取class对象:为了能够在C/C++中调用Java中的类,jni.h的头文件专门定义了jclass类型表示Java中Class类。JNIEnv中有3个函数可以获取jclass。
- jclass FindClass(const char* clsName):通过类的名称(类的全名,这时候包名不是用’”.”点号而是用”/”来区分的)来获取jclass。比如:
1
jclass jcl_string=env->FindClass("java/lang/String");
- jclass GetObjectClass(jobject obj):通过对象实例来获取jclass,相当于Java中的getClass()函数
- jclass getSuperClass(jclass obj):通过jclass可以获取其父类的jclass对象
获取属性方法:
在Native本地代码中访问Java层的代码,一个常用的常见的场景就是获取Java类的属性和方法。所以为了在C/C++获取Java层的属性和方法,JNI在jni.h头文件中定义了jfieldID和jmethodID这两种类型来分别代表Java端的属性和方法。在访问或者设置Java某个属性的时候,首先就要现在本地代码中取得代表该Java类的属性的jfieldID,然后才能在本地代码中进行Java属性的操作,同样,在需要调用Java类的某个方法时,也是需要取得代表该方法的jmethodID才能进行Java方法操作。
常见的调用Java层的方法如下:
- GetFieldID/GetMethodID:获取某个属性/某个方法
- GetStaticFieldID/GetStaticMethodID:获取某个静态属性/静态方法
1
2
3
4
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
大家发现什么规律没有?对了,我们发现他们都是4个入参,而且每个入参的都是*JNIEnv *env,jclass clazz,const char *name,const char *sig。关于JNIEnv,前面我们已经讲过了,这里我们就不详细讲解了,JNIEnv代表一个JNI环境接口,jclass上面也说了代表Java层中的”类”,name则代表方法名或者属性名。那最后一个char *sig代表什么?它其实代表了JNI中的一个特殊字段——签名,上面已经讲解过了。
构造一个对象
1
jobject NewObject(jclass clazz, jmethodID methodID, ...)
比如有我们知道Java类中可能有多个构造函数,当我们要指定调用某个构造函数的时候,会调用下面这个方法:
1
2
jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
obj = (*env)->NewObject(env, cls, mid);
- clazz:是需要创建的Java对象的Class对象
- methodID:是传递一个方法ID,想一想Java对象创建的时候,需要执行什么操作?就是执行构造函数。
有人会说这要走两行代码,有没有一行代码的,是有的,如下:
1
2
jobject NewObjectA(JNIEnv *env, jclass clazz,
jmethodID methodID, jvalue *args);
这里多了一个参数,即jvalue *args,这里是args代表的是对应构造函数的所有参数的,我们可以应将传递给构造函数的所有参数放在jvalues类型的数组args中,该数组紧跟着放在methodID参数的后面。NewObject()收到数组中的这些参数后,将把它们传给编程任索要调用的Java方法。
上面说到,参数是个数组,如果参数不是数组怎么处理,jni.h同样也提供了一个方法,如下:
1
2
jobject NewObjectV(JNIEnv *env, jclass clazz,
jmethodID methodID, va_list args);
这个方法和上面不同在于,这里将构造函数的所有参数放到在va_list类型的参数args中,该参数紧跟着放在methodID参数的后面。
JNI的常用方法API:https://www.jianshu.com/p/67081d9b0a9c
例子:https://www.jianshu.com/p/0f34c097028a
回顾和总结
要再c++层调用Java代码,首先要知道classname和方法名称:
- #define CLASS_NAME “com/lizhenda/lib/sdk/SDKHelper”
- SDKHelper::getMyInfo(LuaCallback callback)
- JniHelper::callStaticVoidMethod(CLASS_NAME, “getMyInfo”);
- cocos2d::JniHelper::getStaticMethodInfo(t, className.c_str(), methodName.c_str(), signature.c_str())
1
2
3
t.env->CallStaticVoidMethod(t.classID, t.methodID, convert(t, xs)...);
t.env->DeleteLocalRef(t.classID);
deleteLocalRefs(t.env);
其中methodid和classid可以通过JNIEnv中的函数获取,传入classname,methodname以及签名。