logo头像
Snippet 博客主题

从零开始使用Cmake进行jni编程教程(三):jni调用java的方法

本文于 1296 天之前发表,文中内容可能已经过时。

本节内容主要是如何在C++层处理,并在java层返回对应的对象和数据。

应用场景

  1. c++函数中有多个返回
  2. c++函数中有多个参数,java端为了简化操作,直接传入一个包含多个参数的对象
  3. c++端需要直接创建java的类,调用方法

具体逻辑

在C++使用的Java的方法:

  • 找到JAVA类(重中之重)
  • 找到需要的属性
  • 找到需要的方法(注意静态方法和实例方法)

操作方法

  1. 编写java对象,用于存储多个参数的值和多个返回
  2. 在c++通过反射的方式来获取对应参数的值
  3. 调用c++中含有多个参数的方法
  4. 调用c++方法结果后,将对应的参数返回

这也是为什么一个so对应有一个jar包的原因,用于确保java方面的类的位置路径不会找错

基本类型

Java类型     | 签名         
------------ | ------------- 
boolean      |   Z  
short        |   S  
float        |   F  
byte         |   B  
int          |   I  
short        |   S  
double       |   D  
short        |   S  
char         |   C  
void         |   V  
short        |   S  
String       |   java/lang/String  

具体实现

1)调用Java中的静态方法

1.新建名叫LogUtils的类

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.minicoder.coder;

import android.util.Log;

public class LogUtils {
private static final String TAG = "LJJ";

public static void D(String message) {
Log.d(TAG, message);
}

}


接下来需要在对应的native方法的c++找到该类,并调用该方法

1
2
3
4
5
6
7
8
// 1.找到类
jclass logclz = env->FindClass("com/minicoder/coder/LogUtils");
// 2.找方法
jmethodID mtd_d = env->GetStaticMethodID(logclz, "D", "(Ljava/lang/String;)V");
jstring data = env->NewStringUTF("从jni中调用的日志");
// 3.调用java层的静态方法
env->CallStaticVoidMethod(logclz, mtd_d, data);


其中方法的签名和类型,就是上面列表中对应的具体类型,其实我本人是不建议大家去记忆的。我是直接这样处理的

1.将原有方法改为native

2.通过之前的javah方法编译后得到的

找方法时传入的三个值,分别对应1.找到的Jclass对象2.需要调用的方法名3.参数签名,随后就可以在C++层调用JAVA层的静态方法了。

2)调用实例方法

与调用静态方法,类似只不过如果该对象没有通过方法传入,则需要在C++里面创建实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
* Class: com_minicoder_coder_TestUtils
* Method: callInatanceMethod
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_minicoder_coder_TestUtils_callInatanceMethod
(JNIEnv *env, jclass clazz, jstring message) {
// 1.找到类
jclass logclz = env->FindClass("com/minicoder/coder/LogUtils");
// 2.找构造方法id,用于初始化实例
jmethodID construct = env->GetMethodID(logclz, "<init>", "()V");

jmethodID i = env->GetMethodID(logclz, "I", "(Ljava/lang/String;)V");
// 3.实例化对象
jobject testObj = env->NewObject(logclz, construct, NULL);

jstring data = env->NewStringUTF("实例方法打印日志");

// 4.调用实例化对象的方法
env->CallVoidMethod(testObj, i, data);

// 5.去掉类引用
// 去掉字符串引用
env->DeleteLocalRef(data);
// 去掉找到的类引用
env->DeleteLocalRef(logclz);
// 去掉实例化对象
env->DeleteLocalRef(testObj);

}

3)调用传入实例的字段和方法

在通过javah编译后,其实所有需要的相关信息都获取了。例如此处方法传入的两个Person对象(因为返回值为bool,还需要一个结果对象)

1
2
3
4
5
6
7
/**
* 传入一个人对象,进行修改后再
* @param person 传入的人对象
* @param change 传出改变后的人
* @return 是否改造成功
*/
public static native boolean changePerson(Person person,Person change);

对应编译出来的c++的.h文件

1
2
3
4
5
6
7
/*
* Class: com_minicoder_coder_TestUtils
* Method: changePerson
* Signature: (Lcom/minicoder/coder/Person;Lcom/minicoder/coder/Person;)Z
*/
JNIEXPORT jboolean JNICALL Java_com_minicoder_coder_TestUtils_changePerson
(JNIEnv *, jclass, jobject, jobject);

随后新建.cpp文件,老规矩三个步骤

  1. 获取类
  2. 找到字段
  3. 获取值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*
* Class: com_minicoder_coder_TestUtils
* Method: changePerson
* Signature: (Lcom/minicoder/coder/Person;Lcom/minicoder/coder/Person;)Z
*/
JNIEXPORT jboolean JNICALL Java_com_minicoder_coder_TestUtils_changePerson
(JNIEnv *env, jclass clazz, jobject person, jobject res) {

//1.找到对应的java类
jclass clz = env->FindClass("com/minicoder/coder/Person");
//2. 找到对应的字段
jfieldID jname = env->GetFieldID(clz, "name", "Ljava/lang/String;");
jfieldID jage = env->GetFieldID(clz, "age", "I");
jfieldID jgender = env->GetFieldID(clz, "gender", "I");


// 3.获取数据
jstring pring = (jstring) env->GetObjectField(person, jname);
int age = env->GetIntField(person, jage);
int gender = env->GetIntField(person, jgender);

// 4.数据改造
// 4.1字符串拼接
jstring nameTemp = env->NewStringUTF("红发");
// 4.2 年龄增加
int ageTemp = age + 10;
// 4.3 性别改变
int genderTemp = gender + 3;

//5.赋值到结果对象

env->SetIntField(res, jage, ageTemp);
env->SetIntField(res, jgender, genderTemp);
env->SetObjectField(res, jname, nameTemp);

// 5.1干掉本地引用
// 去掉类引用
env->DeleteLocalRef(clz);
// 去掉字符串引用
env->DeleteLocalRef(data);
env->DeleteLocalRef(nameTemp);
//6.返回结果值
bool end = false;

return end;

}