目录
基本流程
- 定义native接口
- 生成头文件(利用AS或者javah命令)和对应接口方法(在.h或者c文件都可)
- 实现接口方法(用c/c++都可)
- 编译成对应平台的动态库so文件;
- 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类型 | 类型签名 |
---|---|
boolean | Z |
byte | B |
int | I |
char | C |
short | S |
long | L |
float | F |
double | D |
void | V |
数组 | [类型签名,比如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
}
}