数据类型
- 基本数据类型
- 引用数据类型
对引用数据类型的操作
- 字符串操作
- 数组操作
- NIO操作
- 访问域
- 调用方法
- 域和方法描述符
字符串操作
//1. 通过char* 创建jstring
//注意当时字符数组的时候,一定要补全结束的符'\0'
char hello2[] = {'h','e','l','l','0','\0'};
char* str = env->NewStringUTF(hello2);
//2. 通过jstring 获取的字符串 在使用完需要显示释放
jboolean isCopy = JNI_FALSE;
const char * ch = env->GetStringUTFChars(content, &isCopy);
if(ch)
{
if(JNI_TRUE==isCopy){
LOGE("copied %s \n",ch);
} else{
LOGE("new %s \n",ch);
}
}
//3. 释放资源
env->ReleaseStringUTFChars(content,ch);
数组操作
拷贝数组的内存区域
jfloatArray arr = env->NewFloatArray(15);
if(arr){
LOGE("创建成功了");
}
jfloat buff[15];
jsize size = 15;
//将java区数组复制到C数组
env->GetFloatArrayRegion(arr,0,size,buff);
for(int i=0;i<size;i++){
buff[i] = 100;
}
//从C数组向java数组中提交修改
env->SetFloatArrayRegion(arr,0,size,buff);
return arr;
通过指针指向java数组区域
extern "C"
JNIEXPORT void JNICALL
Java_com_canjun_ndkbeginer_MainActivity_reverseArray(JNIEnv *env, jobject thiz, jintArray grades,
jint len) {
//通过C指针指向java数组
jboolean isCopy;
jint* nGrades = env->GetIntArrayElements(grades,&isCopy);
if(nGrades){
if(isCopy){
LOGE("nGrades 备份 创建成功了");
} else{
LOGE("nGrades 直接指向 创建成功了");
}
}
for (int i = 0; i < len; ++i) {
for(int j=i+1;j<len;++j){
if(nGrades[i]<nGrades[j]){
jint tmp = nGrades[j];
nGrades[j] = nGrades[i];
nGrades[i] = tmp;
}
}
}
// mode 有三种值
// 0 将内容复制回去,并释放原生数组
// JNI_COMMIT 将内容复制回去,但是不释放原生数组, 用于周期定更新数据
// JNI_ABORT 不将内容复制回去,但是释放原生数组
jint mode = JNI_COMMIT;
env->ReleaseIntArrayElements(grades,nGrades,mode);
}
NIO操作
原生IO在缓冲管理区,大型网络,文件io和字符集支持方面的性能有所改进。JNI可以通过NIO实现原生与java程序之间传递大量数据
-
直接创建字节缓冲区
// C语言创建DirectByteBuffer,并赋值数据 extern "C" JNIEXPORT jobject JNICALL Java_com_canjun_ndkbeginer_MainActivity_obtainByteBuffer(JNIEnv *env, jobject thiz) { //这个数组一定要在堆中建立 char* buffer = (char*)malloc(sizeof(char)*10); for (int i = 0; i < 10; ++i) { buffer[i] = 'a'; } return env->NewDirectByteBuffer(buffer,10); } //java获取数据 ByteBuffer b = (ByteBuffer) obtainByteBuffer(); Log.e("新字符", b.remaining() + ""); byte[] buffer = new byte[10]; b.get(buffer); for (int i=0;i<10;i++){ Log.e("新字符", (char) buffer[i] + ""); }
-
直接在字节缓冲去中获取
//在java中定义 ByteBuffer b = ByteBuffer.allocateDirect(1024); byte[] input = new byte[10]; for (int i=0;i<10;i++){ input[i] = 'x'; } b.put(input); } initByteBuffer(b); //在C语言中获取java中定义的byteBuffer对象 extern "C" JNIEXPORT void JNICALL Java_com_canjun_ndkbeginer_MainActivity_initByteBuffer(JNIEnv *env, jobject thiz, jobject buffer) { char* output = static_cast<char *>(env->GetDirectBufferAddress(buffer)); for (int i = 0; i < 10; ++i) { LOGE("native %c \n",output[i]); } }
访问域的操作
准备工作
public class Person {
public static int leg = 2;
public String name;
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' + "leg = " + leg+ '\''+
'}';
}
public String updateName(String greet){
return greet + "," +name;
}
public static void thanks(String arr){
Log.e("Person", " thank yout very muchu " + arr);
}
public static void thanks(String[] arr){
if(arr==null||arr.length!=2){
Log.e("Person", "参数不合法");
return;
}
Log.e("Person", arr[0] + " thank yout very muchu " + arr[1]);
}
}
//java层的调用
Person p = new Person("zfc");
//native方法
parsePerson(p);
Log.e("MainActivity",p.toString());
//native层的实现
//获取person中的信息
extern "C"
JNIEXPORT void JNICALL
Java_com_canjun_ndkbeginer_MainActivity_parsePerson(JNIEnv *env, jobject thiz, jobject p) {
//获取字节码对象
jclass clazz = env->GetObjectClass(p);
//获取静态字段信息
jfieldID fieldId = env->GetStaticFieldID(clazz,"leg","I");
jint leg = env->GetStaticIntField(clazz, fieldId);
//获取非静态字段信息
jfieldID nameFiledId = env->GetFieldID(clazz,"name","Ljava/lang/String;");
if(!nameFiledId){
LOGE("无效的nameFiledId");
return;
}
jobject name = env->GetObjectField(p, nameFiledId);
jboolean isCopy;
const char * chName = env->GetStringUTFChars((jstring)name,&isCopy);
LOGE("姓名:%s 腿:%d条",chName,leg);
//改变对象的值
env->SetStaticIntField(clazz,fieldId,3);
jstring newName = env->NewStringUTF("杨戬");
env->SetObjectField(p,nameFiledId,newName);
}
访问方法的操作
//java层的调用
Person p = new Person("zfc");
parseMethodForPerson(p);
//native层的实现
extern "C"
JNIEXPORT void JNICALL
Java_com_canjun_ndkbeginer_MainActivity_parseMethodForPerson(JNIEnv *env, jobject thiz, jobject p) {
//获取字节码对象
jclass clazz = env->GetObjectClass(p);
//获取静态方法 public static void thanks(String[] arr)
jmethodID m_thanks_id = env->GetStaticMethodID(clazz,"thanks","([Ljava/lang/String;)V");
if(!m_thanks_id){
LOGE("method thanks not found");
return;
}
jclass strClazz = env->FindClass("java/lang/String");
jobjectArray arr = env->NewObjectArray(2,strClazz,env->NewStringUTF("!"));
env->SetObjectArrayElement(arr,1,env->NewStringUTF("zfc"));
env->CallStaticVoidMethod(clazz,m_thanks_id,arr);
//获取非静态方法 public String updateName(String greet);
jmethodID m_updateName_id = env->GetMethodID(clazz,"updateName","(Ljava/lang/String;)Ljava/lang/String;");
if(!m_updateName_id){
LOGE("method updateName not found");
return;
}
auto newName = (jstring)env->CallObjectMethod(p,m_updateName_id,env->NewStringUTF("zhangzihe"));
const char* newChName = env->GetStringUTFChars(newName,0);
LOGE("new name is %s",newChName);
//释放资源
env->ReleaseStringUTFChars(newName, newChName);
}
异常处理
- 捕获异常
- 抛出异常
捕获异常
//java 层代码 ExceptionUtil.java
public class ExceptionUtil {
public static void callException(){
throw new NullPointerException("1/0");
}
public static native void callNative();
public static native void callNative2();
}
//native层代码
extern "C"
JNIEXPORT void JNICALL
Java_com_canjun_ndkbeginer_ExceptionUtil_callNative(JNIEnv *env, jclass clazz) {
//获取静态方法id static void callException()
jmethodID m_callException_id = env->GetStaticMethodID(clazz,"callException","()V");
if(!m_callException_id){
LOGE("method callException not found");
return;
}
env->CallStaticVoidMethod(clazz,m_callException_id);
//符合C语言特点
jthrowable ex = env->ExceptionOccurred();
if(ex){
env->ExceptionClear();
LOGE("exception occur");
}
}
触发异常
extern "C"
JNIEXPORT void JNICALL
Java_com_canjun_ndkbeginer_ExceptionUtil_callNative2(JNIEnv *env, jclass clazz) {
jclass run_clazz = env->FindClass("java/lang/RuntimeException");
jint code = env->ThrowNew(run_clazz,"xxxxx");
}
注意此处的异常是jvm异常,native的执行顺序并不会终止
全局引用 和局部引用
引用在java中扮演非常重要的角色。虚拟机通过追踪类实例的引用,回收不再引用的垃圾,来管理对象的期限。原生代码不再管理范畴,因此jni提供一组函数显示地管理对象的引用及使用期间的原生代码。JNI支持三种引用 局部引用 全局引用和 弱全局引用。
- 局部引用
- 全局引用
- 若全局引用
局部引用
大多数jni函数的返回值,就是局部引用。局部引用会随之原生方法执行结束,而自定释放;当然也可以显示调用进行释放。根据jni规范,虚拟机至少运行创建16个局部引用。
//显示删除局部引用
env->DeleteLocalRef(clazz);
//申请更多的局部引用
env->EnsureLocalCapacity(10);
全局引用
//创建全局引用
g_run_clzz = env->NewGlobalRef(run_clazz);
//删除全局引用
env->DeleteGlobalRef(g_run_clzz);
全局弱引用
//创建弱全局引用
w_run_clazz = env->NewWeakGlobalRef(run_clazz);
//如果弱引用依然有效
if(!env->IsSameObject(w_run_clazz,NULL)){
jboolean result = env->IsSameObject(w_run_clazz,NULL);
//是否弱全局引用
env->DeleteWeakGlobalRef(w_run_clazz);
LOGE("weak invalid! %d \n", result);
}
线程
作为原生环境的一部分,虚拟机支持运行原生代码。在开发原生构件时,要记住JNI技术的一些约束。
1. 只在原生方法执行期间及正在执行原生方法的线程环境下,局部引用才有效,局部引用不能线程间共享,但是全局引用可以。
2.被传递给原生方法的环境变量指针在与方法调用相关的线程也是有效的;但是不能被其他线程缓存或使用
- 同步
- 原生线程
同步
同步是多线成中的重要概念。与java同步类似,jni的监视器允许原生代码利用java对象同步。虚拟机保证存取监视器的线程安全执行,而其他线程等待监视器变为可用。
//java中的同步代码块
synchronized(obj){
...
...
}
//native中的同步代码块
//同步代码块
if(JNI_OK == env->MonitorEnter(clazz){
//错误处理
}
if(JNI_OK == env->MonitorExit(clazz)){
//错误处理
}
//注意上面的两个方法的调用应该成对出现,避免思索。
原生线程
因为虚拟机不知道原生线程,所以原生线程不能java组件直接通讯。为了能够通讯,可以将原生线程附着到虚拟机,待执行原生线程结束后,再脱离虚拟机。
JNIEnv *env2;
cachedJvm->AttachCurrentThread(&env2,NULL);
cachedJvm->DetachCurrentThread();