【NDK系列】给移动开发者的JNI快速入门

本文详细介绍了JNI编程的基本流程,包括定义接口、生成头文件、实现方法、编译动态库及Java层导入so库。同时,深入讲解了JNIEnv的概念,native操作Java如访问属性、调用方法和构建对象,以及Java操作native对象的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基本流程

  1. 定义native接口
  2. 生成头文件(利用AS或者javah命令)和对应接口方法(在.h或者c文件都可)
  3. 实现接口方法(用c/c++都可)
  4. 编译成对应平台的动态库so文件;
  5. Java层导入so库

关于导入so库一般有如下两个方法:

static{
	// 导入本地动态库,比如一些很大的so文件可以放在服务器然后下载到本地(data/data之下)
	System.load("libTest.so");
	
	// 导入apk libs中的动态库
	System.loadSystem("libTest.so");
}

JNIEnv

指定编译时采用c的编译方式,c不支持函数重载,c++支持函数重载:

#ifdef _cplusplus
external "C" {
#endif
  • JNIEXPORT标记函数为外部调用的函数,如果不标记则编译能通过,但是Java层无法调用;
  • JNICALL标记函数为jni调用的函数,可以省略;
  • env表示jni执行环境,每个线程都有一个实例,c语言中它是一个结构体指针,c++中它是一个结构体;
  • jobj表示java方法为普通方法,如果是静态方法则该参数为jclass;
JNIEXPORT jstring JNICALL com_example_TEST_testString(JNIEnv *env, jobject jobj){
	......
}

native操作Java

1 访问Java属性

如何获取方法或者字段的签名?

  • 方法一:使用javap -p -s classname即可查看
  • 方法二:记住规律,参见下节表格

关于java的访问修饰符

  • 对于native层来说不区分public或者private,都是可见的

访问非静态属性:

jclass j_clz=(*env)->GetObjectClass(evn, jobj);
jfield j_fid=(*env)->GetFieldID(env, "field_name","field_signature");//filed_signature比如:Ljava/lang/String;
jobject j_fieldvalue=(*env)->GetObjectField(env, jobj,j_fid);    //这里返回值通常会直接转换为实际的字段类型,比如jstring
(*env)->SetObjectField(env, j_fid, j_value);

访问静态属性:

jfieldID j_fid = (*env)->GetStaticFieldID(evn, jclz, "field_name","field_signature");
jint j_value = (*env)->GetStaticIntField(env, jclz, j_fid);
j_value ++;
(*env)->SetStaticIntField(env, jclz, j_fid, j_value);

jstring与char*互转(jint与c的int之间不需要互转):

//java string to c string
char* c_str = (*env)->GetStringUTFChars(env, j_str, NULL);
(*env)->ReleaseStringUTFChars(env, c_str,j_str);

// c string to java string
(*env)->NewStringUTF(env, c_str);

2. 调用Java方法

调用非静态方法:

jclass j_clz = (*env)->GetObjectClass(env, jobj);
jmethodID j_mid=(*env)->GetMethodID(env, j_clz, "method_name","method_signature");
jint result = (*env)->CallIntMethod(env, jobj, j_mid, p1, p2);

Java数据类型与签名类型的对应关系

Java类型类型签名
booleanZ
byteB
intI
charC
shortS
longL
floatF
doubleD
voidV
数组[类型签名,比如int[][I
L全限定名;,比如String, 其签名为Ljava/lang/String;(注意后面有个分号)

方法签名规则:(所有参数类型)返回值类型

java方法签名
Point offset(int x, float y)(IF)Ljava/awt/Point;
public void test(int i, double index)(ID)V
public String test(String string, double index)(Ljava/lang/String;D)Ljava/lang/String;
String getName()()Ljava/lang/String;
Object[] getValue()()[java/lang/Object;
void test()()V

3. 构建Java对象

// 构建Point对象并返回
jclass point_clz = (*env)->FindClass(env, "java/awt/Point");
jmethod construct_id = (*env)->GetMehodID(env, "<init>", "(II)V");
jobject obj = (*env)->NewObject(env, point_clz, construct_id, 11, 12);
return obj;

Java操作native

1. 操作native对象

java层:构建java对象时初始化并获取native对象的引用

public class Mat{
	public final long nativeObj;   //c/c++对象的引用
	public Mat(){
		nativeObj = n_Mat();
	}

	public void doSomething(){
		n_doSomething(nativeObj);
	}
	// 初始化并获取native对象的引用
	private static native long n_Mat(); 
	// 调用native对象的方法,这里传nativeObj是为了在native层找到指定的native对象
	private static native void n_doSomething(long nativeObj);
}

native层:构建对象并返回内存地址(强转jlong)

JNIEXPORT jlong JNICALL Java_org_opencv_core_Mat_n_1Mat(){
	try{
		// 对象转地址
		// return (jlong)new Mat();   
		return reinterpret_cast<jlong>(new Mat());//这种写法更加正规
	}catch( const std::exception &e){
		throwJavaException(env, &e, "Mat_n_1Mat");
	}catch(...){
		throwJavaException();
	}
	return 0;
}

JNIEXPORT void JNICALL Java_org_opencv_core_Mat_n_1doSomething(JNIEnv* env, jclass jclz, jlong nativePtr){
	// 地址转对象
	Mat* mat = reinterpret_cast<Mat>(nativePtr);
	if(mat){
		// do something
	}
}

静态/动态注册Native方法

参考资料

Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... 11 Chap 3:javah命令帮助信息... 16 Chap 4:用javah产生一个.h文件... 17 Chap5:jni教程(very very good) 19 Chap6: JNI传递返回值... 26 15.2.2.3 传递字符串... 28 15.2.2.4 传递整型数组... 29 15.2.2.5 传递字符串数组... 30 15.2.2.6 传递对象数组... 31 Chap7:Jni中C++和Java的参数传递... 33 Chap8:如何将java传递过来的jbyteArray转换成C/C++中的BYTE数组... 47 Chap5:使用JNI技术实现java程序调用第三方dll(c/c++)文件的功能... 47 Chap9:如何编写jni方法(转载)... 55 1、实例一:在jni中调用标准c中自带的函数printf(): 57 2、实例二、调用c 语言用户定义的函数... 58 3、实例三、在jni函数中访问java类中的对象实例域... 58 4、实例四:在jni函数中访问类的静态实例域... 60 5、实例五:在jni函数中调用java对象的方法... 60 6、实例六:在jni函数中调用java类的静态方法... 61 7、实例七:jni函数中传递基本数据类型参数... 62 8、实例八:在jni函数中传递对象类型参数... 62 9、实例九:在jni函数中处理字符串... 63 10、实例十:在jni函数中处理数组... 64 11、实例十一:在jni中的返回值问题... 65 12、实例十二:在jni中创建java类对象:... 66 Chap10:在 Windows 中实现 Java 本地方法... 66 1.Java 调用 C. 67 2.调试... 76 3.其他信息... 79 Chap11:如何在C/C++中调用Java. 80 1.环境搭建... 81 2.初始化虚拟机... 83 3.访问类方法... 85 4访问类属性... 87 5.访问构造函数... 88 6.数组处理... 89 7.中文处理... 89 8.异常... 91 9.线程和同步访问... 91 10.时间... 92 Chap12:基本JNI调用技术(c/c++与java互调) 93 Chap13:JNI的c代码中,另外一个线程获取 JNIEnv. 96 chap 14:当JNI遇到多线程--java对象如何被C++中的多个线程访问?. 97 chap 15:JNI在多线程中的应用... 101 chap 16:JNI限制(多线程)... 105 chap 17:使用 Java Native Interface 的最佳实践... 106 1.性能缺陷... 107 2.正确性缺陷... 117 3.避免常见缺陷... 121 4.结束语... 128 Chap18:JNI设计实践之路... 129 一、 前言... 129 二、 JNI基础知识简介... 130 三、 Java程序调用非Java程序... 131 四、 C/C++访问Java成员变量和成员方法... 138 五、 异常处理... 140 六、 MFC程序中嵌入Java虚拟机... 142 Chap19:JNI编程系列之基础篇... 148 System.loadLibrary("HelloWorld"); 149 JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject); 150 Chap20:JNI编程系列之中级篇(上)... 151 1. Java基本类型的传递... 151 2. String参数的传递... 151 3. 数组类型的传递... 153 4. 二维数组和String数组... 154 Chap21:JNI编程系列之高级篇... 155 1. 在一般的Java类中定义native方法... 156 2. 访问Java类的域和方法... 156 3. 在native方法中使用用户定义的类... 157 4. 异常处理... 158
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值