前言
Android平台从一开始就支持C/C++了,Android支持JNI编程方式第三方应用完全可以通过JNI调用自己的C动态库,于是NDK(Native Develop Kit)就诞生了。
介绍
JNI是Java程序设计语言功能最强的特征,允许Java类的某些方法原生实现(C/C++)。
环境配置
安装AS+NDK+CMAKE+LLDB
AS:AndroidStudio 4.0
NDK:允许Android使用C和C++代码。
CMake:外部构建工具,可与Gradle搭配使用来构建原生苦,如果使用ndk-build,则不需要此组件。
LLDB:debug调试
简单例子
1、创建 native c++ 工程
JNI入门学习
1、数据类型和类型描述符
Java 中有两种数据类型:
基本数据类型: boolean 、char、byte、int、short、long、float、double。
引用数据类型: String、Object[]、Class、Object 及其它类。
1.1基本数据类型
Java基本数据类型可以直接与C/C++相应的基本数据类型映射;如下映射关系
Java 类型 | JNI 类型 | C/C++ 类型 |
---|---|---|
boolean | jboolean | unsigned char (无符号 8 位整型) |
byte | jbyte | char (有符号 8 位整型) |
char | jchar | unsingned short (无符号 16 位整型) |
short | jshort | short (有符号 16 位整型) |
int | jint | int (有符号 32 位整型) |
long | jlong | long (有符号 64 位整型) |
float | jfloat | float (有符号 32 位浮点型) |
double | jdouble | double (有符号 64 位双精度型) |
1.1引用类型
与基本类型不同,引用类型对原生方法时不透明的,它的内部数据结构并不直接向原生代码公开。
Java 类型 | 原生类型 |
---|---|
Java.lang.Class | jclass |
Java.lang.Throwable | jthrowable |
Java.lang.String | jstring |
Other object | jobject |
Java.lang.Object[] | jobjectArray |
boolean[] | jbooleanArray |
byte[] | jbyteArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | jfloatArray |
double[] | jdoubleArray |
Other arrays | jarray |
1.1数据类型描述符
在JVM虚拟机中,存储数据类型名称时,是使用指定的描述来存储,而不是我们习惯的int,float等
Java 类型 | 签名 (描述符) |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
void | V |
其它引用类型 | L + 全类名 + ; |
type[] | [ |
method type | (参数)返回值 |
例如:
- 表示一个 String
Java 类型 : java.lang.String
JNI 描述符: Ljava/lang/String; (L + 类全名 + ;)
先执行javac Person.java(如果是当前目录,可以不写路径,否则需要写绝对路径)生成Person.class
javap Person 查看类
javap -s 来获取方法签名(描述符)
george@bogon myapplication % javac Person.java
george@bogon myapplication % javap Person
警告: 二进制文件Person包含com.george.myapplication.Person
Compiled from "Person.java"
public class com.george.myapplication.Person {
public com.george.myapplication.Person();
public java.lang.String getName();
public void setName(java.lang.String);
}
george@bogon myapplication % javap -s Person
警告: 二进制文件Person包含com.george.myapplication.Person
Compiled from "Person.java"
public class com.george.myapplication.Person {
public com.george.myapplication.Person();
descriptor: ()V
public java.lang.String getName();
descriptor: ()Ljava/lang/String;
public void setName(java.lang.String);
descriptor: (Ljava/lang/String;)V
public native java.lang.String stringFromJNI();
descriptor: ()Ljava/lang/String;
}
george@bogon myapplication %
2. JNIEnv 和JavaVm
2.1 JNIEnv
JNIEnv 表示Java调用native语言的环境,是一个封装了几乎全部JNI方法的指针。
JNIEnv只在创建它的线程生效,不能垮线程传递,不同线程的JNIEnv彼此独立。
native环境创建的线程,如果需要JNI,必须调用AttachCurrentThread关联,并使用DetachCurrentThread解除链接。
2.2 JavaVM:
JavaVM是虚拟机在JNI层的代表,一个进程只有一个JavaVM,所有的线程公用一个JavaVM
2.3 代码风格 (C/C++)
C: (*env)->NewStringUTF(env, “Hellow World!”);
C++: env->NewStringUTF(“Hellow World!”);
3.JNI API
参考官方 API 文档 或者 JNI 方法大全及使用示例
4.数据类型操作
JNI 处理 Java 传递过来的数据
// 定义native函数
public native void test1(
boolean b,
byte b1,
char c,
short s,
long l,
float f,
double d,
String name,
int age,
int[] i,
String[] strs,
Person person,
boolean[] bArray
);
// 调用native函数
test1(true,
(byte) 1,
',',
(short) 3,
4,
3.3f,
2.2d,
"小明",
28,
new int[]{1, 2, 3, 4, 5, 6, 7},
new String[]{"1", "2", "4"},
new Person("小明"),
new boolean[]{false, true}
);
jni处理Java传递过来的数据
#include
#include
#include
#include
#define TAG "native-lib"
// __VA_ARGS__ 代表 ...的可变参数
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__);
extern "C" // 支持C语言的代码
JNIEXPORT void JNICALL // 返回值为空
Java_com_george_myjni_MainActivity_test1(JNIEnv *env, jobject instance,
jboolean jboolean1,
jbyte jbyte1,
jchar jchar1,
jshort jshort1,
jlong jlong1,
jfloat jfloat1,
jdouble jdouble1,
jstring name_,
jint age,
jintArray i_,
jobjectArray strs,
jobject person,
jbooleanArray bArray_){
//1\. 接收 Java 传递过来的 boolean 值
unsigned char b_boolean = jboolean1;
LOGD("boolean-> %d", b_boolean);
//2\. 接收 Java 传递过来的 boolean 值
char c_byte = jbyte1;
LOGD("jbyte-> %d", c_byte);
//3\. 接收 Java 传递过来的 char 值
unsigned short c_char = jchar1;
LOGD("char-> %d", c_char);
//4\. 接收 Java 传递过来的 short 值
short s_short = jshort1;
LOGD("short-> %d", s_short);
//5\. 接收 Java 传递过来的 long 值
long l_long = jlong1;
LOGD("long-> %d", l_long);
//6\. 接收 Java 传递过来的 float 值
float f_float = jfloat1;
LOGD("float-> %f", f_float);
//7\. 接收 Java 传递过来的 double 值
double d_double = jdouble1;
LOGD("double-> %f", d_double);
//8\. 接收 Java 传递过来的 String 值
const char *name_string = env->GetStringUTFChars(name_, 0);
LOGD("string-> %s", name_string);
//9\. 接收 Java 传递过来的 int 值
int age_java = age;
LOGD("int:%d", age_java);
//10\. 打印 Java 传递过来的 int []
jint *intArray = env->GetIntArrayElements(i_, NULL);
//拿到数组长度
jsize intArraySize = env->GetArrayLength(i_);
for (int i = 0; i < intArraySize; ++i) {
LOGD("intArray->%d:", intArray[i]);
}
//释放数组
env->ReleaseIntArrayElements(i_, intArray, 0);
//11\. 打印 Java 传递过来的 String[]
jsize stringArrayLength = env->GetArrayLength(strs);
for (int i = 0; i < stringArrayLength; ++i) {
jobject jobject1 = env->GetObjectArrayElement(strs, i);
//强转 JNI String
jstring stringArrayData = static_cast(jobject1);
//转 C String
const char *itemStr = env->GetStringUTFChars(stringArrayData, NULL);
LOGD("String[%d]: %s", i, itemStr);
//回收 String[]
env->ReleaseStringUTFChars(stringArrayData, itemStr);
}
//12\. 打印 Java 传递过来的 Object 对象
//12.1 获取字节码
const char *person_class_str = "com/george/myjni/Person";
//12.2 转 jni jclass
jclass person_class = env->FindClass(person_class_str);
//12.3 拿到方法签名 javap -a
const char *sig = "()Ljava/lang/String;";
jmethodID jmethodID1 = env->GetMethodID(person_class, "getName", sig);
jobject obj_string = env->CallObjectMethod(person, jmethodID1);
jstring perStr = static_cast(obj_string);
const char *itemStr2 = env->GetStringUTFChars(perStr, NULL);
LOGD("Person: %s", itemStr2);
env->DeleteLocalRef(person_class); // 回收
env->DeleteLocalRef(person); // 回收
//13\. 打印 Java 传递过来的 booleanArray
jsize booArrayLength = env->GetArrayLength(bArray_);
jboolean *bArray = env->GetBooleanArrayElements(bArray_, NULL);
for (int i = 0; i < booArrayLength; ++i) {
bool b = bArray[i];
jboolean b2 = bArray[i];
LOGD("boolean:%d",b)
LOGD("jboolean:%d",b2)
}
//回收
env->ReleaseBooleanArrayElements(bArray_, bArray, 0);
}
JNI 处理 Java 对象(JNI把对象传递给Java)
定义java对象
public class Person {
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private int age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private String name;
}
定义native接口
public native Person getPerson();
JNI处理
extern "C" // 支持C语言的代码
JNIEXPORT jobject JNICALL // 返回值为空
Java_com_george_myjni_MainActivity_getPerson(JNIEnv *env, jobject instance){
// 1. 拿到java类的全路径
const char *person_java = "com/george/myjni/Person";
const char *method = "";// java构造方法标示
// 2. 找到需要处理的java对象class
jclass j_person_class = env->FindClass(person_java);
//3.拿到空参构造方法
jmethodID person_constructor = env->GetMethodID(j_person_class,method,"()V");
//4. 创建对象
jobject person_obj = env->NewObject(j_person_class,person_constructor);
//5. 拿到 setName 方法的签名,并拿到对应的setName方法
const char *nameSig = "(Ljava/lang/String;)V";
jmethodID nameMethodId = env->GetMethodID(j_person_class,"setName",nameSig);
//6. 拿到setAge 方法的签名,并拿到setAge方法
const char *ageSig = "(I)V";
jmethodID ageMethod = env->GetMethodID(j_person_class,"setAge",ageSig);
// 7. 正在调用 java对象函数
const char *name = "小明";
jstring newStringName = env->NewStringUTF(name);
env->CallVoidMethod(person_obj,nameMethodId,newStringName);
env->CallVoidMethod(person_obj,ageMethod,28);
const char *sig = "()Ljava/lang/String;";
jmethodID jtoString = env->GetMethodID(j_person_class, "toString", sig);
jobject obj_string = env->CallObjectMethod(person_obj, jtoString);
jstring perStr = static_cast(obj_string);
const char *itemStr2 = env->GetStringUTFChars(perStr,NULL);
LOGD("Person:%s",itemStr2);
return person_obj;
}
5.JNI动态注册
上面介绍的都是静态注册;
静态注册:优点是简单方便;
缺点:当前类定义的native方法名改变或者包改变,就需要改动cpp里的代码。动态注册可以解决这个问题。
public native void dynamicRegister(String name);
/**
* 对应java类的全路径名,.用/代替
*/
const char *classPathName = "com/george/myjni/MainActivity";
extern "C" // 支持c语言
JNIEXPORT void JNICALL // 告诉虚拟机,这是jni函数
native_dynamicRegister(JNIEnv *env, jobject instance, jstring name) {
const char *j_name = env->GetStringUTFChars(name, NULL);
LOGD("动态注册:%s", j_name)
// 释放
env->ReleaseStringUTFChars(name, j_name);
}
/**
* 源码结构
* typedef struct{
* const char* name;
* const char* signature;
* void* fnPtr;
* } JNINativeMethod
*/
static const JNINativeMethod jniNativeMethod[] = {
{"dynamicRegister", "(Ljava/lang/String;)V", (void *) (native_dynamicRegister)}
};
/**
* 该函数定义在jni.h头文件中,System.loadLibrary()时会调用JNI_OnLoad()函数
* @param javaVm
* @param pVoid
* @return
*/
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *javaVm, void *pVoid) {
// 通过虚拟机 创建全新的 env
JNIEnv *jniEnv = nullptr;
jint result = javaVm->GetEnv(reinterpret_cast(&jniEnv), JNI_VERSION_1_6);
if (result != JNI_OK) {
return JNI_ERR; // 主动报错
}
jclass mainActivityClass = jniEnv->FindClass(classPathName);
jniEnv->RegisterNatives(mainActivityClass, jniNativeMethod,
sizeof(jniNativeMethod) / sizeof(JNINativeMethod));// 动态注册的数量
return JNI_VERSION_1_6;
}
6.异常处理
JNI的异常行为和Java有所不同。Java抛出一个异常时,虚拟机停止执行代码块,并进入调用栈反向检查能处理特定类型异常的程序代码块,叫捕获异常。虚拟机清除异常并将控制权交给异常处理程序。相比之下,JNI要求开发人员在异常发生后显式地实现异常处理流。
public native void dynamicRegister2(String name);
/**
* 测试抛出异常
*
* @throws NullPointerException
*/
private void testException() throws NullPointerException {
throw new NullPointerException("MainActivity testException NullPointerException");
}
使用动态注册的方式,主要代码如下(具体代码参考动态注册补全)
extern "C" //支持 C 语言
JNIEXPORT void JNICALL // 告诉虚拟机,这是jni函数
native_dynamicRegister2(JNIEnv *env, jobject instance, jstring name) {
const char *j_name = env->GetStringUTFChars(name, NULL);
LOGD("动态注册2: %s", j_name)
jclass clazz = env->GetObjectClass(instance);//拿到当前类的class
jmethodID mid =env->GetMethodID(clazz, "testException", "()V");//执行 Java 测试抛出异常的代码
env->CallVoidMethod(instance, mid); // 执行会抛出一个异常
if(env->ExceptionCheck()){
LOGD("检测异常")
env->ExceptionDescribe();
env->ExceptionClear();
jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(newExcCls, "JNI 中发生了一个异常信息"); // 返回一个新的异常到 Java
}
// 注释掉:这种方法抓不到异常,为什么?
// jthrowable exc = env->ExceptionOccurred(); // 检测是否发生异常,测试中直接down掉了,找不到异常信息了。
// LOGD("动态注册2-exc: %s", exc)
// if (exc) {//如果发生异常
// env->ExceptionDescribe(); // 打印异常信息
// env->ExceptionClear(); // 清除掉发生的异常
// jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
// env->ThrowNew(newExcCls, "JNI 中发生了一个异常信息"); // 返回一个新的异常到 Java
// }
//释放
env->ReleaseStringUTFChars(name, j_name);
}
static const JNINativeMethod jniNativeMethod2[] = {
{"dynamicRegister2", "(Ljava/lang/String;)V", (void *) (native_dynamicRegister2)}
};
/**
* 该函数定义在jni.h头文件中,System.loadLibrary()时会调用JNI_OnLoad()函数
* @param javaVm
* @param pVoid
* @return
*/
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *javaVm, void *pVoid) {
// 通过虚拟机 创建全新的 env
JNIEnv *jniEnv = nullptr;
jint result = javaVm->GetEnv(reinterpret_cast(&jniEnv), JNI_VERSION_1_6);
if (result != JNI_OK) {
return JNI_ERR; // 主动报错
}
jclass mainActivityClass = jniEnv->FindClass(classPathName);
jniEnv->RegisterNatives(mainActivityClass, jniNativeMethod,
sizeof(jniNativeMethod) / sizeof(JNINativeMethod));// 动态注册的数量
jniEnv->RegisterNatives(mainActivityClass, jniNativeMethod2,
sizeof(jniNativeMethod2) / sizeof(JNINativeMethod));// 动态注册的数量
return JNI_VERSION_1_6;
}
7.局部和全局引用
Java程序中,虚拟机通过追踪类实例,引用并回收不在的引用,垃圾管理类实例的使用期限。JNI提供了一组函数允许原生代码显式地管理对象引用及使用期间源代码。JNI支持三种引用:局部引用、全局引用和弱引用。
局部引用:
局部引用不能在后续的调用中被缓存及重用。如:FindClass返回局部引用,当原生方法返回时,它被自动释放,可以使用DeleteLocalRef函数显式的释放原生代码。
例如(参考上面动态注册补全):
jclass personClass;
extern "C" //支持 C 语言
JNIEXPORT void JNICALL //告诉虚拟机,这是jni函数
native_test4(JNIEnv *env, jobject instance) {
LOGD("测试局部引用")
if (personClass == NULL) {
const char *person_class = "com/george/myjni/Person";
personClass = env->FindClass(person_class);
LOGD("personClass == null 执行了。")
}
//Java Person 构造方法实例化
const char *sig = "()V";
const char *method = "";//Java 构造方法标识
jmethodID init = env->GetMethodID(personClass, method, sig);
//创建出来
env->NewObject(personClass, init);
}
static const JNINativeMethod jniNativeMethod3[] = {
{"dynamicTest4", "()V", (void *) (native_test4)}
};
全局引用
jclass personClass2;
extern "C" // 支持c语言
JNIEXPORT void JNICALL // 告诉虚拟机,这是jni函数
native_test5(JNIEnv *env, jobject instance){
LOGD("测试全局引用")
if (personClass2 == NULL){
// 提升全局解决不能重复使用问题
const char *person_class = "com/george/myjni/Person";
jclass jclass1 = env->FindClass(person_class);
personClass2 = static_cast(env->NewGlobalRef(jclass1));
LOGD("personClass2 == null 执行了。")
}
// java person 构造方法实例化
const char *sig = "()V";
const char *method = "";// Java 构造方法标识
jmethodID init = env->GetMethodID(personClass2, method, sig);
// 创建出来
env->NewObject(personClass2, init);
// 显式释放主动删除全局引用
env->DeleteLocalRef(personClass2);
personClass2 = NULL;
}
删除全局引用:
当不再需要一个全局引用时,用DeleteGlobalRef函数释放
env->DeleteLocalRef(personClass);
personClass = NULL;
弱全局引用
并不阻止潜在的对象被垃圾回收
jclass personClass3;
extern "C"
JNIEXPORT void JNICALL
native_test6(JNIEnv *env, jobject instance){
LOGD("测试弱全局引用")
if (personClass3 == NULL){
const char *person_class = "com/george/myjni/Person";
jclass jclass1 = env->FindClass(person_class);
personClass3 = static_cast(env->NewWeakGlobalRef(jclass1));
LOGD("personClass == null 执行力")
}
const char *sig = "()V";
const char *method = "";
jmethodID init = env->GetMethodID(personClass3, method, sig);
env->NewObject(personClass3, init);
env->DeleteWeakGlobalRef(personClass3);
personClass3 = NULL;
}
可以用IsSameObject函数检验一个弱全局引用是否仍然指向活动的类实例。
8.JNI线程操作
- 局部引用不能在多线程间共享,只有全局引用可以被多线程共享
- JNIEvn接口指针不能被其它线程缓存或使用
同步:同步是多线程设计最终特征。
MonitorEnter函数与对MonitorExit调用匹配,避免死锁。
public native void nativeCount();
private void test4(){
Log.d(TAG,"多线程");
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
count();
nativeCount();
}
}).start();
}
}
private int count;
private void count(){
synchronized (this){
count++;
Log.d(TAG,"count="+count);
}
}
extern "C"
JNIEXPORT void JNICALL
native_count(JNIEnv *env, jobject instance){
jclass cls = env->GetObjectClass(instance);
jfieldID fieldId = env->GetFieldID(cls, "count","I");
int val = env->GetIntField(instance, fieldId);
val++;
LOGI("count=%d",val);
env->SetIntField(instance, fieldId, val);
}
发现运行结果多样性(有些没有打印出,有些重复打印,有些顺序不对等),通过多线程对count字段操作,无法保证count可见性了。需要JNI本地实现同步。
通过MonitorEnter 和MonitorExit来保证count可见性(访问有序)。
extern "C"
JNIEXPORT void JNICALL
native_count(JNIEnv *env, jobject instance){
jclass cls = env->GetObjectClass(instance);
jfieldID fieldId = env->GetFieldID(cls, "count","I");
if (env->MonitorEnter(instance)!=JNI_OK){
LOGE("%s:MonitorEnter()failed",__FUNCTION__)
}
int val = env->GetIntField(instance, fieldId);
val++;
LOGI("count=%d",val);
env->SetIntField(instance, fieldId, val);
if (env->ExceptionOccurred()){
LOGE("ExceptionOccurred()...")
if (env->MonitorExit(instance)!=JNI_OK){
LOGE("%s:MonitorExit() failed",__FUNCTION__)
}
}
if (env->MonitorExit(instance)!=JNI_OK){
LOGE("%s:MonitorExit() failed",__FUNCTION__)
}
}
原生线程
为了执行特定任务,原生构建可以并行使用原生线程。
JNI通过JavaVM提供AttachCurrentThread函数,JavaVM接口指针应尽早被缓存,否则不能被获取
java通过native在c++中开启子线程,然后在子线程中调用java主线程更新ui
public void test5(){
Log.d(TAG,"原生线程");
testThread();
}
/**
* AndroidUI操作,让C++线程里面来调用
*/
public void updateUI(){
if (Looper.getMainLooper() == Looper.myLooper()){
new AlertDialog.Builder(this)
.setTitle("UI")
.setMessage("native 运行在主线程,直接更新 UI ...")
.setPositiveButton("确认",null)
.show();
}else {
runOnUiThread(new Runnable() {
@Override
public void run() {
new AlertDialog.Builder(MainActivity.this)
.setTitle("UI")
.setMessage("native 运行在子线程切换为主线程更新 UI...")
.setPositiveButton("确认",null)
.show();
}
});
}
}
public native void testThread();
public native void unThread();
jvm参数是在JNI_OnLoad中初始化的。
JavaVM *jvm;
jobject instance;
void *customThread(void *pVoid) {
// 调用的话,一定需要JNIEnv *env
// JNIEnv *env 无法跨越线程,只有JavaVM才能跨越线程
JNIEnv *env = NULL; // 全新的env
// jvm是在 JNI_OnLoad中初始化过的,别忘记了,否则报错
int result = jvm->AttachCurrentThread(&env, 0); // 把native的线程,附加到JVM
if (result != 0) {
return 0;
}
jclass mainActivityClass = env->GetObjectClass(instance);
// 拿到MainActivity的updateUI
const char *sig = "()V";
jmethodID updateUI = env->GetMethodID(mainActivityClass, "updateUI", sig);
env->CallVoidMethod(instance, updateUI);
// 解除 附加 到 JVM 的native线程
jvm->DetachCurrentThread();
return 0;
}
extern "C"
JNIEXPORT void JNICALL
native_testThread(JNIEnv *env, jobject thiz) {
LOGD("native_testThread")
instance = env->NewGlobalRef(thiz);// 全局的就不会被释放,所以可以在线程里面用
// 如果非全局的,函数一结束,就被释放了
pthread_t pthreadID;
pthread_create(&pthreadID, 0, customThread, instance);
pthread_join(pthreadID, 0);
LOGD("native_testThread-end")
}
extern "C"
JNIEXPORT void JNICALL
native_unThread(JNIEnv *env, jobject thiz) {
if (NULL != instance) {
env->DeleteGlobalRef(instance);
instance = NULL;
}
}
/**
* 该函数定义在jni.h头文件中,System.loadLibrary()时会调用JNI_OnLoad()函数
* @param javaVm
* @param pVoid
* @return
*/
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *javaVm, void *pVoid) {
jvm = javaVm;
// 通过虚拟机 创建全新的 env
JNIEnv *jniEnv = nullptr;
jint result = javaVm->GetEnv(reinterpret_cast(&jniEnv), JNI_VERSION_1_6);
if (result != JNI_OK) {
return JNI_ERR; // 主动报错
}
jclass mainActivityClass = jniEnv->FindClass(classPathName);
jniEnv->RegisterNatives(mainActivityClass, jniNativeMethod9,
sizeof(jniNativeMethod9) / sizeof(JNINativeMethod));// 动态注册的数量
return JNI_VERSION_1_6;
}
总结
JNI技术实现Java应用程序与原生代码通信。