数据类型
基本数据类型
Java类型 | native类型 | 字长(位) |
---|---|---|
boolean | jboolean | 8 |
byte | jbyte | 8 |
char | jchar | 16 |
short | jshort | 16 |
int | jint | 32 |
long | jlong | 64 |
float | jfloat | 32 |
double | jdouble | 64 |
引用类型
Java类型 | Native类型 |
---|---|
All objects | jobject |
java.lang.Class实例 | jclass |
java.lang.String | jstring |
object[] | jobjectArray |
boolean[] | jbooleanArray |
byte[] | jbyteArray |
java.lang.Throwable | jthrowable |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | jfloatArray |
double | jdoubleArray |
JNI相关接口
String相关接口
/**
* 功能描述: 将java层的char字符数组转换成jstring
*
* @param [unicodeChars,len] 字符数组,长度
* @return String
*/
jstring NewString(const jchar* unicodeChars, jsize len)
/**
* 功能描述: 将java层的char字符数组转换成jstring
*
* @param [unicodeChars] 字符数组
* @return String
*/
jstring NewStringUTF(const char* bytes)
/**
* 功能描述: 将一个native的utf8字符串转化成一个jstring对象
*
* @param [bytes] native的字符串
* @return String
*/
jstring NewStringUTF(const char* bytes)
/**
* 功能描述: 获取String的char数组,调用后需要通过ReleaseStringChars释放资源
*
* @param [jstring,isCopy] String对象,是否拷贝
* 如果返回的字符串是原来的java.lang.String的一份拷贝, 在GetStringChars返回之后,isCopy指向的内存地址将会被设置为JNI_TRUE。而如果返回的字符串指针直接指向原来的java.lang.String对象,则该地址会被设置为JNI_FALSE.如果返回了JNI_FALSE, 则原生代码将不能改变返回的字符串,因为改变了这个字符串,原来的java字符串也会被修改,这违背了java.lang.String实例不可改变的原则,通常可以置为NULL表示不在乎是否返回的是String的拷贝对象
* @return char[] java层的char数组
*/
const jchar* GetStringChars(jstring string, jboolean* isCopy)
void ReleaseStringChars(jstring string, const jchar* chars)
/**
* 功能描述: 将一个jstring转换为一个native的utf-8 string,调用后需要使用ReleaseStringUTFChars释放资源
*
* @param [string,isCopy] String
* @return char*
*/
const char* GetStringUTFChars(jstring string, jboolean* isCopy)
void ReleaseStringUTFChars(jstring string, const char* utf)
/**
* 功能描述: 获取jstring的长度
* 由于 UTF-8 编码的字符串以'\0'结尾,而 Unicode 字符串不是。如果想获取一个指向 Unicode 编码的 jstring 字符串长 度,在 JNI 中可通过这个函数获取
* @param [jstring] String
* @return jsize
*/
jsize GetStringLength(jstring string)
/**
* 功能描述: 获取 UTF-8 编码字符串的长度,也可以通过标准 C 函数 strlen 获取
* @param [string] String
* @return jsize
*/
jsize GetStringUTFLength(jstring string)
/**
* 功能描述: 获取源字符串的指针
* 在某些情况下,比如支付串占用的内存很大,但是我们只是需要读取其中内容时,无需将其转换为native的字符串浪费内存空间,可以通过该函数来获取,注意:使用的时候检查是否因为内存溢出而导致返回值为 NULL,因为 JVM 在执行 GetStringCritical 函数时,仍有发生数据复制的可能性,尤其是当 JVM 内部存储的数组不连续时,为了返回一个指向连续内存空间的指针,JVM 必须复制所有数据
* @param [string,isCopy] String,isCopy
* @return jsize
*/
const jchar* GetStringCritical(jstring string, jboolean* isCopy)
/**
* 功能描述: 与GetStringCritical配合使用
* 特别注意!因为通过 GetStringCritical 得到的是一个指向 JVM 内部字符串的直接指针,获取这个直接指针后会导致暂停 GC 线程,当 GC 线程被暂停后,如果其他线程触发 GC 继续运行的话,都会导致阻塞调用者。所以,GetStringCritical 和 ReleaseStringCritical 这对函数中间的任何本地代码都不可以执行导致阻塞的调用或为新对象在 JVM 中分配内存,否则,JVM 有可能死锁
*/
void ReleaseStringCritical(jstring string, const jchar* carray)
/**
* 功能描述: 获得字符串指定范围的内容,这里的字符串指的是 Java 层的字符串。函数会把源字符串复制到一个预先分配的缓冲区内,效果同GetStringChars类似,使用后需要调用相应的函数释放资源
*/
void GetStringRegion(jstring str, jsize start, jsize len, jchar* buf)
void GetStringUTFRegion(jstring str, jsize start, jsize len, char* buf)
数组相关
/**
* 功能描述: 新建一个int[]数组,该方式创建的数组,会在JNI方法退出后自动清除,也可以通过DeleteLocalRef(obj)来及时清理
* @param [length] 长度
* @return int[]
*/
jintArray NewIntArray(jsize length)
/**
* 功能描述: 拷贝一个数组并返回其指针,使用后需要通过ReleaseIntArrayElements来释放
* @param [array,isCopy] int[],isCopy
* @return jni*
*/
jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
/**
* 功能描述: 释放拷贝数组
* @param [array,elems,mode] 源数组,拷贝的数组,释放模式
* mode值含义: 0:数据将会拷贝回原始数据, 同时释放拷贝数据 1(JNI_COMMIT):数据将会拷贝回原始数据, 不释放拷贝数据
2(JNI_ABORT): 释放拷贝数据, 之前的任何数据操作会丢弃
* @return jni*
*/
void ReleaseIntArrayElements(jintArray array, jint* elems,jint mode)
/**
* 功能描述: 将jni数组中的元素拷贝至java数组中
* @param [array,start,len,buf] array:java数组,start:起始位置,len:长度,buf:jni数组
* @return
*/
void SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint* buf)
/**
* 功能描述: 将java数组中的元素拷贝至jni数组中
* @param [array,start,len,buf] array:java数组,start:起始位置,len:长度,buf:jni数组
* @return
*/
void GetIntArrayRegion(jintArray array, jsize start, jsize len, jint* buf)
与Java层交互相关,通过JNI调用使用Java方法和变量
调用java方法的步骤,1:获取jclass 2:通过class获取到methodId 3:调用CallXXXMethod来调用函数
调用java类变量的步骤, 1:获取jclass 2:通过class获取到fieldId 3.通过GetXXXFiled或者SetXXXFiled操作对象属性
//获取class的方式
/**
* 功能描述: 获取对象实例的jclass
* @param [obj] 对象实例
* @return jclass
*/
jclass GetObjectClass(jobject obj)
/**
* 功能描述: 通过class名称获取的jclass
* @param [name] class名称,例子:android/media/MediaScannerClient
* @return jclass
*/
jclass FindClass(const char* name)
//获取函数Id的方式
/**
* 功能描述: 获取class的mhthodId
* @param [clazz,name,sig] class:jclass类,name:函数名称,sig:参数和返回值(类型为java类型在native层的标志,参数在括号内,返回值在括号外
* @return jmethodID
*/
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
/**
* 功能描述: 获取class的静态函数mhthodId
*/
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
//调用函数的方式CallXXXMethod
/**
* 功能描述: 调用对象实例的函数
* @param [obj,methodID,...] obj:对象实例,methodID:函数ID,…:函数参数
* @return
*/
void CallVoidMethod(jobject obj, jmethodID methodID, …)
/**
* 功能描述: 调用对象实例的函数
* @param [obj,methodID,...] obj:对象实例,methodID:函数ID,…:可变参数列表
* @return
*/
void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args)
/**
* 功能描述: 调用对象实例的函数
* @param [obj,methodID,...] obj:对象实例,methodID:函数ID,…:函数数组
* @return
*/
void CallVoidMethodA(jobject obj, jmethodID methodID, const jvalue* args)
/**
* 功能描述: 调用class的静态函数
* @param [clazz,methodID,...] clazz:类,methodID:函数ID,…:函数数组
* @return
*/
void CallStaticVoidMethod(jclass clazz, jmethodID methodID, …)
/**
* 功能描述: 调用被子类重写的父类函数
* @param [obj,clazz,methodID,...] obj:对象实例,clazz:父类的class,methodID:函数ID,…:函数数组
* @return
*/
void CallNonvirtualVoidMethod(jobject obj, jclass clazz, jmethodID methodID, …)
//Java变量
/**
* 功能描述: 获取变量的fieldId
* @param [obj,name,sig] obj:对象实例,clazz:父类的class,name:字段名,sig:java类型在native层的标志
* @return
*/
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
/**
* 功能描述: 获取对象的某个属性
* @param [obj,fieldID] obj:对象实例,fieldID:变量的id
* @return 属性值
*/
jobject GetObjectField(jobject obj, jfieldID fieldID)
/**
* 功能描述: 为对象的变量赋值,注意:该赋值不受变量访问权限的影响
* @param [obj,fieldID,value] obj:对象实例,fieldID:变量的id,value:需要设置的值
* @return
*/
void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
//demo
//JNI代码
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_anative_MainActivity_test(JNIEnv *env, jobject instance) {
// TODO
jstring str = env->NewStringUTF("测试一下");
jclass clazz = env->GetObjectClass(instance);
jmethodID methodId = env->GetMethodID(clazz, "printStrings",
"(Ljava/lang/String;Ljava/lang/String;)V");
env->CallVoidMethod(instance, methodId, str,str);
jfieldID fieldId = env->GetFieldID(clazz, "name", "Ljava/lang/String;");
env->SetObjectField(instance, fieldId, str);
return str;
}
//Java代码
public class MainActivity extends AppCompatActivity {
private String name;
// 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);
test();
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(name);
}
public void printStrings(String s1, String s2) {
System.out.println(s1);
System.out.println(s2);
}
public native String test();
}
垃圾回收
JNI提供的三种引用类型
- Local Reference:本地引用。在JNI层函数中使用的非全局引用对象都是Local Reference。它包括函数调用时传入的jobject、在JNI层函数中创建的jobject。LocalReference最大的特点就是,一旦JNI层函数返回,这些jobject就可能被垃圾回收。
- Global Reference:全局引用,这种对象如不主动释放,就永远不会被垃圾回收。
- Weak Global Reference:弱全局引用,一种特殊的GlobalReference,在运行过程中可能会被垃圾回收。所以在程序中使用它之前,需要调用JNIEnv的IsSameObject判断它是不是被回收了。
注:JNI 的规范指出,JVM 要确保每个 Native 方法至少可以创建 16 个局部引用,经验表明,16 个局部引用已经足够平常的使用了
相关函数
/**
* 功能描述: 创建一个对象的全局引用,此时obj不等于返回的ref,但是可以用ref来替代obj
* @param [obj] obj:对象实例
* @return
*/
jobject NewGlobalRef(jobject obj)
/**
* 功能描述: 删除全局引用
* @param [ref] :对象的全局引用
* @return
*/
void DeleteGlobalRef(jobject globalRef)
//本地引用,注意,创建大量本地对象的时候,比如在for循环中创建时,最好及时删除引用防止内存溢出
jobject NewLocalRef(jobject ref)
void DeleteLocalRef(jobject localRef)
//全局弱引用
jweak NewWeakGlobalRef(jobject obj)
void DeleteWeakGlobalRef(jweak obj)
/**
* 功能描述: 判断两个引用是否指向相同的对象
* @param [ref1,ref2]
* @return JNI_TRUE(1):相同 JNI_FALSE(2):不同
*/
jboolean IsSameObject(jobject ref1, jobject ref2)
/**
* 功能描述: 判断是否有足够的空间创建局部引用
* @param [capacity] 需要的容量
* @return 0:有足够的空间 <0:空间不足
*/
jint EnsureLocalCapacity(jint capacity)
/**
* 功能描述: 创建一个大小至少为capacity的本地栈来存放本地应用
* @param [capacity] 需要的容量
* @return 0:有足够的空间 <0:空间不足
*/
jint PushLocalFrame(jint capacity)
/**
* 功能描述: 与PushLocaFrame配合使用,清除之前入栈的所有引用,注意:该函数调用后,在这两段之间创建的所有引用均不能使用,否则会报异常
* @param [capacity] 需要的容量
* @return 0:有足够的空间 <0:空间不足
*/
jobject PopLocalFrame(jobject result)
异常
JNI层的异常不会中断本地函数的执行,直到从JNI层返回到Java层后,虚拟机才会抛出这个异常,在JNI处理异常非常重要,需要在catch到异常时及时清理资源防止内存泄漏
/**
* 功能描述: 是否抛出新的异常
* @return true:存在新异常 false:没有异常
*/
jboolean ExceptionCheck()
/**
* 功能描述: 获取新的异常
* @return true:存在新异常 false:没有异常
*/
jthrowable ExceptionOccurred()
/**
* 功能描述: 打印异常
* @return
*/
void ExceptionDescribe()
/**
* 功能描述: 清除当前异常
* @return
*/
void ExceptionClear()
/**
* 功能描述: 抛出一个Java层异常
* @return
*/
jint ThrowNew(jclass clazz, const char* message)
//demo
if (env->ExceptionCheck()) {
/*
* We don't do much with the exception, except that we print a debug message for it,
* clear it, and throw a new exception.
*/
env->ExceptionOccurred();
jclass newExcCls;
env->ExceptionDescribe();
env->ExceptionClear();
newExcCls = env->FindClass("java/lang/IllegalArgumentException");
if (!newExcCls) {
/*Unable to find the exception class, give up. */
return;
}
env->ThrowNew(newExcCls, "thrown from C code");
}