前一章说到了基本类型转化和数组等通用类型通用 交互的转换。那么间接的说明了 JNI 调用Java的方法, 参数为基本类型数据和基本类型数组的方式;那么这一篇就是介绍 JNI如何调用Java 的对象的方法,成员变量,还有类方法,静态变量等。
Cmake, 加入c++头文件的方式
在开发中通常把 C++头文件和源文件分开的方式。首先创建cppHeader 文件夹 来放C++头文件,和cppSouce文件夹来放源文件,当然,也可以使用其它的文件夹,或者命名不同。
打开CmakeLists.txt 文件配置添加
include_directories(src/main/cpp/cppHeader) 到文件中。
这样可以保证我们的头文件 在cppHeader 文件夹下,可以被引用到。 开始回归正题。C 调用Java 对象,分为几种,1、在C中 new Java对象。2、C调用Java 非静态方法。3、C调用Java静态方法,4、C调用Java 非静态 变量。5、C调用Java 静态变量。
整体的基本的JNI开发的 步骤如下:
(1)创建 Java 对象。
package com.xiaoyunchengzhu.jni.jnicalljava;
/**
* Created by zhangshiyu on 2017/11/24.
*/
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return " {name:"+name+",age:"+age+"}";
}
}
User 对象就相当于被操作的类,toString方法 用来输出验证对象。
package com.xiaoyunchengzhu.jni.jnicalljava;
import android.util.Log;
/**
* Created by zhangshiyu on 2017/11/24.
*/
public class CallTest {
private static final String TAG="CallTest";
/**
* no-static field
*/
private String name="calltest_name";
/**
* static field
*/
public static String sName="static calltest_name";
/**
* no-static method
* @param value1
* @param value2
* @return
*/
public int add(int value1,int value2){
return value1+value2;
}
/**
* static method
* @param value1
* @param value2
* @return
*/
public static int sAdd(int value1,int value2){
return value1+value2;
}
/**
* void method
* @param msg1
* @param count
*/
public void show(String msg1,int count){
Log.i(TAG,msg1+count);
}
/**
* reference type
* @param user
* @return
*/
public String getUser(User user){
return "User-- name:"+user.getName()+",age:"+user.getAge();
}
}
CallTest 作为 被C 调用的类。
(2)创建native 方法;
package com.xiaoyunchengzhu.jni.jnicalljava;
/**
* Created by zhangshiyu on 2017/11/24.
*/
public class CalledUtil{
static {
System.loadLibrary("calljava");
}
public static native String callJniString();
public static native User newUser();
//获取用户用户名
public static native String getUserName(User user);
public static native int getUserAge(User user);
/**
* 获取Jni 转换的 User 对象;
* @param user
* @return
*/
public static native String callGetName(User user);
//在JNI 中C 调用CallTest 类中的方法 和成员变量。
public static native int callCallTestAddMethod(int value1,int value2);
public static native int callCallTestSaddMethod(int value1,int value2);
public static native String getCallTestSnameField();
public static native String getJniUser(User user);
}
有多个native 方法。先分析 newUser 方法。
(3)在头文件去声明native方法。
这是calljava.h 文件 。
//
// Created by zhangshiyu on 2017/11/24.
//
#ifndef JNICALLJAVADEMO_CALLJAVA_H
#define JNICALLJAVADEMO_CALLJAVA_H
#endif //JNICALLJAVADEMO_CALLJAVA_H
#include <jni.h>
#include <string>
#include <stdlib.h>
#include <android/log.h>
#include <convertUtil.h>
extern "C"{
JNIEXPORT jstring JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_callJniString(JNIEnv *, jobject /* this */);
JNIEXPORT jobject JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_newUser(JNIEnv *, jobject /* this */);
JNIEXPORT jstring JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_getJniUser(JNIEnv *,jobject,jobject);
JNIEXPORT jstring JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_getUserName(JNIEnv *,jobject,jobject);
JNIEXPORT jint JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_getUserAge(JNIEnv *,jobject,jobject);
JNIEXPORT jstring JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_callGetName(JNIEnv *,jobject,jobject);
JNIEXPORT jint JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_callCallTestAddMethod(JNIEnv *,jobject,jint,jint);
JNIEXPORT jint JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_callCallTestSaddMethod(JNIEnv *,jobject,jint,jint);
JNIEXPORT jstring JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_getCallTestSnameField(JNIEnv *,jobject);
}
//
// Created by zhangshshiyu on 2017/11/29.
//
#ifndef JNICALLJAVADEMO_CONVERTUTIL_H
#define JNICALLJAVADEMO_CONVERTUTIL_H
#endif //JNICALLJAVADEMO_CONVERTUTIL_H
#include <android/log.h>
void printLog(const char* );
(4)实现头文件声明的函数。
covertUtil.cpp 文件
//
// Created by zhangshiyu on 2017/11/29.
//
#include <convertUtil.h>
void printLog(const char* msg){
__android_log_print(ANDROID_LOG_INFO,"JniCall","%s",msg);
}
calljava.cpp 文件
#include <calljava.h>
JNIEXPORT jstring JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_callJniString(JNIEnv *env, jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
JNIEXPORT jobject JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_newUser(JNIEnv *env, jobject /* this */){
//获取 Calltest class
jclass userclass=env->FindClass("com/xiaoyunchengzhu/jni/jnicalljava/User");
if (userclass==NULL){
printLog("userclass is null");
return NULL;
}
//获取构造方法id
jmethodID constructor = (env)->GetMethodID(userclass, "<init>", "()V");
if (NULL == constructor) {
printLog("can't constructor CallTest");
return NULL;
}
//新建对象 user
jobject user=env->NewObject(userclass,constructor);
return user;
}
JNIEXPORT jstring JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_getJniUser(JNIEnv *env,jobject,jobject user){
//获取 Calltest class
jclass calltestclass=env->FindClass("com/xiaoyunchengzhu/jni/jnicalljava/CallTest");
if (calltestclass==NULL){
printLog("calltestclass is null");
return NULL;
}
//获取构造方法id
jmethodID constructor = (env)->GetMethodID(calltestclass, "<init>", "()V");
if (NULL == constructor) {
printLog("can't constructor CallTest");
return NULL;
}
//新建对象 Calltest
jobject calltest=env->NewObject(calltestclass,constructor);
if (calltest==NULL){
printLog("calltest is null");
return NULL;
}
//获取 getUser 方法id
jmethodID getUserId=env->GetMethodID(calltestclass,"getUser","(Lcom/xiaoyunchengzhu/jni/jnicalljava/User;)Ljava/lang/String;");
if (getUserId==NULL){
printLog("getUserId is null");
return NULL;
}
//调用calltet 的 getuser 方法
jstring result= (jstring) env->CallObjectMethod(calltest, getUserId, user);
return result;
}
JNIEXPORT jstring JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_getUserName(JNIEnv * env,jobject,jobject user){
//获取 user class
jclass userclass=env->FindClass("com/xiaoyunchengzhu/jni/jnicalljava/User");
if (userclass==NULL){
printLog("userclass is null");
}
jfieldID filedNameId=env->GetFieldID(userclass,"name","Ljava/lang/String;");
if (filedNameId==NULL){
printLog("filedNameId is null");
}
jstring name= (jstring) env->GetObjectField(user, filedNameId);
return name;
}
JNIEXPORT jint JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_getUserAge(JNIEnv * env,jobject,jobject user){
//获取 user class
jclass userclass=env->FindClass("com/xiaoyunchengzhu/jni/jnicalljava/User");
if (userclass==NULL){
printLog("userclass is null");
}
jfieldID filedAgeId=env->GetFieldID(userclass,"age","I");
if (filedAgeId==NULL){
printLog("filedAgeId is null");
}
jint age= env->GetIntField(user, filedAgeId);
return age;
}
JNIEXPORT jstring JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_callGetName(JNIEnv *env,jobject,jobject user){
//获取 user class
jclass userclass=env->FindClass("com/xiaoyunchengzhu/jni/jnicalljava/User");
if (userclass==NULL){
printLog("userclass is null");
}
//分别获取 getName 方法id 和 getAge 方法 id;
jmethodID getNameId=env->GetMethodID(userclass,"getName","()Ljava/lang/String;");
if (getNameId==NULL){
printLog("getNameid is null");//("getNameid is null");
}
//分别调用 getName 方法,和getAge 方法获取name 和age
jstring jname= (jstring) env->CallObjectMethod(user, getNameId);
if (jname==NULL){
printLog("jname is null");//print("jname is null");
}
return jname;
}
JNIEXPORT jint JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_callCallTestAddMethod(JNIEnv *env,jobject,jint value1,jint value2){
//获取 Calltest class
jclass calltestclass=env->FindClass("com/xiaoyunchengzhu/jni/jnicalljava/CallTest");
if (calltestclass==NULL){
printLog("calltestclass is null");
return NULL;
}
//获取构造方法id
jmethodID constructor = (env)->GetMethodID(calltestclass, "<init>", "()V");
if (NULL == constructor) {
printLog("can't constructor CallTest");
return NULL;
}
//新建对象 Calltest
jobject calltest=env->NewObject(calltestclass,constructor);
if (calltest==NULL){
printLog("calltest is null");
return NULL;
}
//获取 add 方法id
jmethodID addId=env->GetMethodID(calltestclass,"add","(II)I");
if (addId==NULL){
printLog("addid is null");
return NULL;
}
//调用calltet 的 add 方法
jint result= env->CallIntMethod(calltest, addId, value1, value2);
return result;
}
JNIEXPORT jint JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_callCallTestSaddMethod(JNIEnv *env,jobject,jint value1,jint value2){
//获取 Calltest class
jclass calltestclass=env->FindClass("com/xiaoyunchengzhu/jni/jnicalljava/CallTest");
if (calltestclass==NULL){
printLog("calltestclass is null");
return NULL;
}
//获取 sadd 方法id
jmethodID saddID=env->GetStaticMethodID(calltestclass,"sAdd","(II)I");
if (saddID==NULL){
printLog("saddid is null");
return NULL;
}
int result=env->CallStaticIntMethod(calltestclass,saddID,value1,value2);
return result;
}
JNIEXPORT jstring JNICALL
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_getCallTestSnameField(JNIEnv *env,jobject){
//获取 Calltest class
jclass calltestclass=env->FindClass("com/xiaoyunchengzhu/jni/jnicalljava/CallTest");
if (calltestclass==NULL){
printLog("calltestclass is null");
return NULL;
}
//获取 sadd 方法id
jfieldID snameId=env->GetStaticFieldID(calltestclass,"sName","Ljava/lang/String;");
if (snameId==NULL){
printLog("snameId is null");
return NULL;
}
jstring sname= (jstring) env->GetStaticObjectField(calltestclass, snameId);
return sname;
}
(5) 在Java中使用 native 方法。
package com.xiaoyunchengzhu.jni.jnicalljava;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
String result;
TextView tv;
User user=new User();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
tv = (TextView) findViewById(R.id.sample_text);
user.setName("java name");
user.setAge(20);
StringBuilder stringBuilder=new StringBuilder();
stringBuilder.append("Test:"+ CalledUtil.callJniString());
stringBuilder.append("\nnewUser:"+CalledUtil.newUser().toString());
stringBuilder.append("\ngetUserName:"+CalledUtil.getUserName(user));
stringBuilder.append("\ngetUserAge:"+CalledUtil.getUserAge(user));
stringBuilder.append("\ncallGetName:"+CalledUtil.callGetName(user));
stringBuilder.append("\ncallCallTestAddMethod:12+13="+CalledUtil.callCallTestAddMethod(12,13));
stringBuilder.append("\ncallCallTestSaddMethod:15+16="+CalledUtil.callCallTestSaddMethod(15,16));
stringBuilder.append("\ngetCallTestSnameField:"+CalledUtil.getCallTestSnameField());
stringBuilder.append("\ngetJniUser:"+CalledUtil.getJniUser(user));
tv.setText(stringBuilder.toString());
}
}
JNI调用Java 分析
在C中创建 Java 对象,即new Java对象。
在C 中 new 对象的步骤为:
1、获取class 。2、获取构造方法Id.3、新建对象。
//获取 Calltest class
jclass userclass=env->FindClass("com/xiaoyunchengzhu/jni/jnicalljava/User");
if (userclass==NULL){
printLog("userclass is null");
return NULL;
}
//获取构造方法id
jmethodID constructor = (env)->GetMethodID(userclass, "<init>", "()V");
if (NULL == constructor) {
printLog("can't constructor CallTest");
return NULL;
}
//新建对象 user
jobject user=env->NewObject(userclass,constructor);
1.获取class ,用env->FindClass(const char* name)方法,参数只有一个,Java类的路径。包名/类名。这样的组合方式,但是包名中使用斜线代替点。比如 com.xiaoyunchengzhu.jni.jnicalljava.User对象,就是 "com/xiaoyunchengzhu/jni/jnicalljava/User";这一步相当于反射获取Class 一样。
2、获取构造方法id,env->GetMethodID(jclass clazz,const char* name ,const char* sig); 参数有三个,第一个 是clazz,也就是在1步骤中获取的class;第二个是方法名,在构造方法中是"<init>",这个方式也可以获取其它的方法id.是通用的获取方法id;第三个 是签名,什么是签名呢?在构造方法里"()V"这是什么意思呢?第一个括号相当于参数 括号,括号中 是参数签名,在构造方法中没有参数,所以没有值,后面的V 是返回值签名,V代表是void 的签名。所以是"()V"。后面会慢慢讲到,因为调用其它的Java方法也会用到获取方法id的函数。
3、新建对象,调用en->NewObject(jclss clazz,jmethodID methodID,...);参数是可变的。但是第一个参数clazz依然是1步骤中获取的class;第二个参数 是2步骤中获取的构造方法id;之后可变的参数就是Java构造方法传入的参数,因为Java 构造方法参数也会不一样。在2步骤 的签名参数 就说明了在这里要传入的参数的数量、类型。因为2中的获取的构造方法 没有参数,所以这里只有两个参数。
C调用Java非静态方法
1、获取class(为了获取方法id)。2、获取方法id。3,获取Java 对象,也就是jobject,这里的jobject 可以自己创建就是C中new 对象;可以参数传递从Java中传对象。
以实现 public static String callGetName(User user)为例子:
Java_com_xiaoyunchengzhu_jni_jnicalljava_CalledUtil_callGetName(JNIEnv *env,jobject,jobject user){
//获取 user class
jclass userclass=env->FindClass("com/xiaoyunchengzhu/jni/jnicalljava/User");
if (userclass==NULL){
printLog("userclass is null");
}
//分别获取 getName 方法id 和 getAge 方法 id;
jmethodID getNameId=env->GetMethodID(userclass,"getName","()Ljava/lang/String;");
if (getNameId==NULL){
printLog("getNameid is null");//("getNameid is null");
}
//分别调用 getName 方法,和getAge 方法获取name 和age
jstring jname= (jstring) env->CallObjectMethod(user, getNameId);
if (jname==NULL){
printLog("jname is null");//print("jname is null");
}
return jname;
}
获取class 和 获取方法id,之前说明了;这里Java对象是传过来的,所以不用在C中new 对象(上面也讲了如何new 对象)。调用Java方法,使用env->CallObjectMethod(jobject object,jmethodID jmethodID,...);参数可变,第一个是传递的对象,第二个参数是方法id,后面的参数是 方法id 对应的 签名参数决定的参数数量和类型。
这里使用CallObjectMethod 函数是因为返回值是jstring ,jstring 类型 是对象类型,如果返回值是jint 类型,就调用CallIntMethod ,参数都是类似的。返回其它类型有相应的 函数去调用。如果没有返回值就是调用CallVoidMethod函数。 自己可以推断其它的返回类型需要调用的不同函数。
C调用Java静态方法
1、获取class 。2、获取静态方法id。3、调用方法
以C中调用 CallTest 类 中public static int sadd(int value1,int value2)方法为例,在
//获取 Calltest class
jclass calltestclass=env->FindClass("com/xiaoyunchengzhu/jni/jnicalljava/CallTest");
if (calltestclass==NULL){
printLog("calltestclass is null");
return NULL;
}
//获取 sadd 方法id
jmethodID saddID=env->GetStaticMethodID(calltestclass,"sAdd","(II)I");
if (saddID==NULL){
printLog("saddid is null");
return NULL;
}
int result=env->CallStaticIntMethod(calltestclass,saddID,value1,value2);
静态方法是属于类的,所以不用生成或者使用 jobject 对象,env->CallStaticIntMethod(jclass clazz,jmethodID methodID,...);参数原理和之前调用非静态的方法一样。
这里返回值是int ,所以调用CallStaticIntMethod 函数,如果返回值为jstring 等对象,就调用CallStaticObjectMethod 函数,没有返回值,调用CallStaticVoidMethod以此类推,其它的返回值,调用不同的函数。
C中调用Java非静态变量
1、获取class。2、获取非静态变量id,3需要对象,获取Java中传入,或者C中创建。4、得到变量值
//获取 user class
jclass userclass=env->FindClass("com/xiaoyunchengzhu/jni/jnicalljava/User");
if (userclass==NULL){
printLog("userclass is null");
}
jfieldID filedNameId=env->GetFieldID(userclass,"name","Ljava/lang/String;");
if (filedNameId==NULL){
printLog("filedNameId is null");
}
jstring name= (jstring) env->GetObjectField(user, filedNameId);
env->GetFieldID(jclass clazz,const char* name,const char* sig);共三个参数,第一个是 class,第二个是变量名,第三个是签名,签名和之前方法id 传入的签名一样,但是没有括号。因为一个变量只有一个类型。
env->GetObjectField(jobject object,jfieldID, fieldID);只有两个参数,一个是Java对象,一个是变量id.
因为 变量 类型为jstring 是对象类型,所以 调用GetObjectfield,如果变量类型为int 就会调用GetIntField, 其它的类型依次推断。
c中调用Java静态变量
1、获取class。2、获取静态变量id。3、得到变量值
//获取 Calltest class
jclass calltestclass=env->FindClass("com/xiaoyunchengzhu/jni/jnicalljava/CallTest");
if (calltestclass==NULL){
printLog("calltestclass is null");
return NULL;
}
//获取 sName 变量id
jfieldID snameId=env->GetStaticFieldID(calltestclass,"sName","Ljava/lang/String;");
if (snameId==NULL){
printLog("snameId is null");
return NULL;
}
jstring sname= (jstring) env->GetStaticObjectField(calltestclass, snameId);
静态变量 是属于类的,所以这里不需要 对象 。env->GetStaticFieldID(jclass clazz,const char* name,const char * sig);获取静态变量id ,三个参数和之前获取非静态变量方法的参数理解一样。最后获取 静态变量。env->GetStaticObjectField(jclass clazz,jfieldID ,fieldID);两个参数,一个是类,一个是 变量id。
变量类型的不同,jstring 为对象,所以 调用GetStaticObjectField ,如果返回值为 int 类型,则调用GetStaticIntField函数。依次类推其它的返回值所调用的不同的函数。
签名 讲解
在获取 非静态方法id,静态方法id,非静态变量,静态变量,都传入了一个签名的参数。这个签名是如何拼接的,这里会一一说明。
签名是对类型的描述,有方法签名,变量签名。描述对应规则为
Java类型 | 签名描述 |
byte | B |
int | I |
long | J |
char | C |
shor | S |
float | F |
double | D |
boolean | Z |
void | V |
引用对象 | L+包名类名(斜线代替点) |
方法签名:
Java代码中 public String getName() 方法 的签名为"()Ljava/lang/String;"
public void setName(String name) 方法签名为(Ljava/lang/String;)V
public int add(int value1,int value2) 方法签名为(II)I。
有一个规则,方法签名 括号中如果都是基本变量,可以直接写的,如果是引用对象,那么引用对象的签名 必须加分号。
如果方法为public String add(String value1,String value2),则签名为(Ljava/lang/String;Ljava/lang/String)Ljava/lang/String;
public String getUser(User user),签名为(Lcom/xiaoyunchengzhu/jni/jnicalljava/User;)Ljava/lang/String;
变量签名:
变量签名简单些,变量只有一个类型。
比如变量为int age; 签名就是I,
比如 变量为String name; 签名为 Ljava/lang/String
想详细的了解,学习,请 star 我的github:https://github.com/xiaoyunchengzhu/JniCallJavaDemo