当前位置:网站首页>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的时候(被回收了),可以手动再临时创建一个。
边栏推荐
- flink cdc一启动,源端Oracle那台服务器的CPU就飙升到80%以上,会是啥原因呢?
- Tactile intelligent sharing - SSD20X realizes upgrade display progress bar
- CentOS7 —— yum安装mysql
- npm安装依赖报错npm ERR! code ENOTFOUNDnpm ERR! syscall getaddrinfonpm ERR! errno ENOTFOUND
- 7.15 Day21---MySQL----Index
- 离线采集怎么看sql执行计划
- Unity行为树AI分享
- 想低成本保障软件安全?5大安全任务值得考虑
- 【问题解决】同一机器上Flask部署TensorRT报错记录
- 【论文阅读笔记】无监督行人重识别中的采样策略
猜你喜欢
Can‘t connect to MySQL server on ‘localhost3306‘ (10061) 简洁明了的解决方法
Towards Real-Time Multi-Object Tracking (JDE)
Cannot read properties of null (reading ‘insertBefore‘)
数的划分之动态规划
Can 't connect to MySQL server on' localhost3306 '(10061) simple solutions
想好了吗?
TSF微服务治理实战系列(一)——治理蓝图
Teenage Achievement Hackers Need These Skills
canal实现mysql数据同步
8.03 Day34---BaseMapper查询语句用法
随机推荐
7.16 Day22---MYSQL(Dao模式封装JDBC)
day13--postman interface test
Towards Real-Time Multi-Object Tracking (JDE)
注意!软件供应链安全挑战持续升级
7.18 Day23 - the markup language
OpenRefine开源数据清洗软件的GREL语言
3面头条,花7天整理了面试题和学习笔记,已正式入职半个月
How to view sql execution plan offline collection
C1认证之web基础知识及习题——我的学习笔记
What are the functions of mall App development?
7.18 Day23----标记语言
npm报错Beginning October 4, 2021, all connections to the npm registry - including for package installa
Redis common interview questions
idea设置识别.sql文件类型以及其他文件类型
C Expert Programming Chapter 4 The Shocking Fact: Arrays and Pointers Are Not the Same 4.5 Other Differences Between Arrays and Pointers
力扣:343. 整数拆分
败给“MySQL”的第60天,我重振旗鼓,四面拿下蚂蚁金服offer
8款最佳实践,保护你的 IaC 安全!
如何将 DevSecOps 引入企业?
高性能高可靠性高扩展性分布式防火墙架构