当前位置:网站首页>JNI基本使用
JNI基本使用
2022-08-04 05:25:00 【vivianluomin】
JNI数据类型
基本数据
Java基本数据类型域JNI数据类型的映射关系:
Java类型->JNI类型->C类型
JNI的基本数据类型(左边是Java,右边是JNI):
boolean jboolean
byte jbyte
char jchar
short jshort
int jint
long jlong
float jfloat
double jdouble
void void
引用类型(对象)
String jstring
Object jobject
数组,基本数据类型的数组
byte[] jByteArray
对象数组
object[](String[]) jobjectArray
Native函数参数说明
每个natvie函数,都至少有两个参数(JNIEnv*,jclass或者jobject)
- 当native方法为静态方法时:
jclass代表native方法所属类的class对象。 - 当native方法为非静态方法时:
jobject代码natvie方法所属的对象。
关于属性与方法的签名
属性的签名
属性的签名其实就是属性的类型的简称,对应关系如下:
数据类型 | 签名 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
float | F |
double | D |
void | V |
object | L开头,然后以/分隔包的完整类型,后面再加;如果说String签名就是Ljava/lang/String; |
Array | 以 [ 开头在加上数组元素类型的签名;比如 int[ ] 签名就是 [ I ,在比如int[ ][ ]的签名就是[ [ I ,object数组前面就是 [Ljava/lang/Object; |
类的签名格式是: L完整包名;
获取指定类的所有属性、方法的签名:
javap -s -p 完整类名
我们通过cd命令:
F:\Android\NDKLearn\app\build\intermediates\classes\debug>javap -s -p com.example.asus1.ndk
learn.MainActivity
Compiled from "MainActivity.java"
public class com.example.asus1.ndklearn.MainActivity extends android.support.v7.app.AppComp
atActivity {
public java.lang.String str;
descriptor: Ljava/lang/String;
public com.example.asus1.ndklearn.MainActivity();
descriptor: ()V
protected void onCreate(android.os.Bundle);
descriptor: (Landroid/os/Bundle;)V
public native java.lang.String stringFromJNI();
descriptor: ()Ljava/lang/String;
public native void accessFiled();
descriptor: ()V
static {
};
descriptor: ()V
}
其中descriptor就是我们性需要的签名,注意签名中末尾的分号不能省略。
方法前面的规律就是,括号不可以省略:
(参数类型签名)返回值类型签名
C/C++访问Java的属性、方法
有以下几种情况:
- 访问Java类的非静态属性。
- 访问Java类的静态属性。
- 访问Java类的非静态方法
- 访问Java类的静态方法
- 间接方法Java类的父类方法
- 访问Java类的构造方法。
访问Java的非静态属性
C代码:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_accessFiled(JNIEnv *env, jobject instance) {
// TODO
//通过对象拿到Class
jclass clz = env->GetObjectClass(instance);
//拿到对应属性的ID
jfieldID fid = env->GetFieldID(clz,"str","Ljava/lang/String;");
//通过属性ID拿到属性的值
jstring jstr = (jstring)(env->GetObjectField(instance,fid));
//通过Java字符串拿到c字符串,第二个参数是一个出参,用来告诉我们GetStringUTFChars内部是否复制了一份字符串
//如果没有复制,那么出参为isCopy,这时候就不能修改字符串的值了,因为Java中常量池中的字符串是不允许修改的(但是jstr可以指向另外一个字符串)
const char * cstr = env->GetStringUTFChars(jstr,NULL);
//在c层修改这个属性的值
char res[20] = "I LOVE YOU,";
strcat(res,cstr);
//重新生成Jasva的字符串,并且设置给对应的属性
jstring jstr_new = env->NewStringUTF(res);
env->SetObjectField(instance,fid,jstr_new);
//最后释放资源,通知垃圾回收器来回收
env->ReleaseStringUTFChars(jstr,cstr);
}
在Java中,直接调用:
TextView tv = (TextView) findViewById(R.id.sample_text);
accessFiled();
tv.setText(str);
访问Java的静态属性
extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_accessStaticFiled(JNIEnv *env, jobject instance) {
// TODO
jclass clz = env->GetObjectClass(instance);
jfieldID fid = env->GetStaticFieldID(clz,"NUM","I");
jint jInt = env->GetStaticIntField(clz,fid);
jInt++;
env->SetStaticIntField(clz,fid,jInt);
}
访问Java的非静态方法
extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_accessMethd(JNIEnv *env, jobject instance) {
// TODO
jclass clz = env->GetObjectClass(instance);
jmethodID mid = env->GetMethodID(clz,"genRandomInt","(I)I");
//调用该方法,最后一个是可变参数,就是调用该方法所传入的参数
jint jInt = env->CallIntMethod(instance,mid,100);
//printf("output from C: %d",jInt);
LOGD("output from C : %d",jInt);
}
访问Java的静态方法
extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_accessStaticMathod(JNIEnv *env, jobject instance) {
// TODO
jclass clz = env->GetObjectClass(instance);
jmethodID mid = env->GetStaticMethodID(clz,"getUUID","()Ljava/lang/String;");
//调用java的静态方法,拿到返回值
jstring jstr = (jstring)(env->CallStaticObjectMethod(clz,mid,NULL));
//把拿到的Java字符串转换为C的字符串
const char * cstr = env->GetStringUTFChars(jstr,NULL);
//后续操作,产生以UUID为文件名的文件
char fileName[100];
sprintf(fileName,"G:\\%s.txt",cstr);
FILE * f = fopen(fileName,"w");
fputs(cstr,f);
fclose(f);
LOGD("output from C : File had saved", jstr);
}
间接访问Java类的父类方法
父类:
public class Human {
protected void speek(){
System.out.println("Human Speek");
}
}
子类:
public class Man extends Human {
@Override
protected void speek() {
//super.speek();
System.out.println("Man Speek");
}
}
在MainActivity中
Human man = new Man();
public native void accessNonVirtualMathod();
如果是直接使用main.speek()的话,访问的是子类Man的方法
但是通过底层C的方式可以间接访问到父类Human的方法,跳过子类的实现,甚至我们可以直接哪个父类(如果父类有多个的话),这是Java做不到的。
C代码:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_accessNonVirtualMathod(JNIEnv *env, jobject instance) {
// 先拿到属性man
jclass clz = env->GetObjectClass(instance);
jfieldID fid = env->GetFieldID(clz,"man","Lcom/example/asus1/ndklearn/Human;");
jobject man = env->GetObjectField(instance,fid);
//拿到父类的类,以及speek的方法的id
jclass clz_human = env->FindClass("com/example/asus1/ndklearn/Human");
jmethodID mid = env->GetMethodID(clz_human,"speek","()V");
//调用自己的speek实现
env->CallVoidMethod(man,mid);
//调用父类的speek实现
env->CallNonvirtualVoidMethod(man,clz_human,mid);
}
- 当有这个类的对象的时候,使用
env->GetObjectClass(instance)
相当于Java中的test.getClass()
。 - 当没有这个类的对象的时候,
env->FindClass()
相当于Java中的Class.forName()
;
这里直接使用CallVoidMethod,虽然传进去的是父类的Method ID,但是访问的是子类的实现。
最后,通过CallNonVirtualVoidMethod,访问不覆盖的父类方法(C++使用virtual关键字来覆盖父类的实现)。
访问Java类的构造方法
Java代码:
//调用Date的构造函数
public native long accessConstructor();
C代码:
extern "C"
JNIEXPORT jlong JNICALL
Java_com_example_asus1_ndklearn_MainActivity_accessConstructor(JNIEnv *env, jobject instance) {
jclass clz_date = env->FindClass("java/util/Date");
//构造方法的函数名格式是:<init>
//不能写类名,因为构造方法函数名都一样区分不了,只能通过参数列表<签名>区分
jmethodID mid_Date = env->GetMethodID(clz_date,"<init>","()V");
//调用构造函数
jobject date = env->NewObject(clz_date,mid_Date);
//注意签名
jmethodID mid_getTime = env->GetMethodID(clz_date,"getTime","()J");
//调用getTime方法
jlong jtime = env->CallLongMethod(date,mid_getTime);
return jtime;
}
总结
属性,方法的访问和使用和Java的反射API类似。
数组的处理(主要是同步问题)
Java方法中,通过调用accessField,利用C修改静态属性
//数组处理
public native void sortArray(int array[]);
C代码:
int compare(const void * a, const void * b)
{
return (*(int *)a)-(*(int *)b);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_sortArray(JNIEnv *env, jobject instance,
jintArray array_) {
//创建Java数组
//env->NewIntArray(len);
//通过Jva数组,拿到C的数组指针
jint * c_arr = env->GetIntArrayElements(array_,NULL);
jsize len = env->GetArrayLength(array_);
//排序
qsort(c_arr,len, sizeof(jint),compare);
//操作完后需要同步C的数组到Java数组中
env->ReleaseIntArrayElements(array_,c_arr,0);
}
注意
- 通过GetIntArrayElements拿到C类型的数组的指针,然后才能进行C数组的处理
- C拿到Java的数组进行操作或者修改,需要调用ReleaseIntArrayElements进行更新,这时候Java的数组也会同步更新过了
这个方法的最后一个参数是模式:
3. 0:Java数组进行更新,并且释放C/C++数组
4. JNI_ABORT:Java数值不进行更新,但是释放C/C++数组
5. JNI_COMMIT:Java数组进行更新,不释放C/C++数组(函数执行完毕后,还是会被释放)。
JNI引用
JNI引用概念:引用变量
引用类型:局部引用和全局引用(全局引用里面包含全局弱引用)。
作用:在JNI中告知虚拟机何时回收一个JNI变量。
局部引用
局部引用,通过DeleteLocalRef手动释放对象。
典型使用场景:
- 访问一个很大的Java对象,使用完之后,还要进行负责的耗时操作
- 创建了大量的局部引用,占用了太多的内存,而且这些局部引用跟后面的操作没有关联性。
例子:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_localRef(JNIEnv *env, jobject instance) {
int i = 0;
for(;i<10;i++)
{
jclass clz_date = env->FindClass("java/util/Date");
jmethodID mid = env->GetMethodID(clz_date,"<init>","()V");
jobject jobject_date = env->NewObject(clz_date,mid);
//此处省略一万行代码
//不再使用jobject对象
//通知垃圾回收器回收这些对象,确保内存充足
env->DeleteLocalRef(jobject_date);
}
}
全局引用
主要作用:共享(可以跨多个线程),手动控制内存使用
//全局引用的字符串对象
jstring global_str;
//创建全局引用
extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_createGlobalRef(JNIEnv *env, jobject instance) {
global_str = env->NewStringUTF("I LOVE YOU");
//通过NewGlobalRef创建全局引用
env->NewGlobalRef(global_str);
}
//获取全局引用
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_asus1_ndklearn_MainActivity_getGlobalRef(JNIEnv *env, jobject instance) {
// TODO
return global_str;
}
//删除全局引用
extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_deleteGlobalRef(JNIEnv *env, jobject instance) {
// 通过DelteGlobalRef删除全局变量
env->DeleteGlobalRef(global_str);
}
弱全局引用
使用方法与全局引用类似:
- 通过NewWeakGlobalRef创建全局引用。
- 通过DeleteWeakGlobalRef删除全局引用。
与全局引用不一样的是,弱引用:
3. 节省内存,在内存不足时可以释放所引用的对象。
4. 可以引用一个不常用的对象,如果为NULL的时候(被回收了),可以手动再临时创建一个。
边栏推荐
- MySQL数据库(基础)
- Canal mysql data synchronization
- Wwise入门和实战
- The string class introduction
- Landing, the IFC, GFC, FFC concept, layout rules, forming method, use is analysed
- Unity动画生成工具
- C Expert Programming Chapter 5 Thinking about Linking 5.2 Advantages of Dynamic Linking
- 一个对象引用的思考
- Programming hodgepodge (4)
- 力扣题解8/3
猜你喜欢
8.03 Day34---BaseMapper query statement usage
Summary of MySQL database interview questions (2022 latest version)
部署LVS-DR群集【实验】
(Kettle) pdi-ce-8.2 连接MySQL8.x数据库时驱动问题之终极探讨及解决方法分析
Unity自动生成阻挡Collider的GameObject工具
7.18 Day23 - the markup language
7.15 Day21---MySQL----索引
Typora 使用保姆级教程 | 看这一篇就够了 | 历史版本已被禁用
数的划分之动态规划
企业需要知道的5个 IAM 最佳实践
随机推荐
Wwise入门和实战
C语言 -- 操作符详解
TensorRT例程解读之语义分割demo
力扣:509. 斐波那契数
4.3 Annotation-based declarative transactions and XML-based declarative transactions
Camera2 闪光灯梳理
力扣:70. 爬楼梯
[Cocos] cc.sys.browserType可能的属性
一个对象引用的思考
[SemiDrive source code analysis] [MailBox inter-core communication] 47 - Analysis of RPMSG_IPCC_RPC mode limit size of single transmission and limit bandwidth test
去重的几种方式
Cannot read properties of null (reading ‘insertBefore‘)
Can 't connect to MySQL server on' localhost3306 '(10061) simple solutions
力扣题解8/3
编程大杂烩(四)
如何将 DevSecOps 引入企业?
Interesting Kotlin 0x0E: DeepRecursiveFunction
8.03 Day34---BaseMapper查询语句用法
Gartner 权威预测未来4年网络安全的8大发展趋势
JS basics - forced type conversion (error-prone, self-use)