前一章说到了基本类型转化和数组等通用类型通用 交互的转换。那么间接的说明了 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方法 用来输出验证对象。CallTest 作为 被C 调用的类。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(); } }
(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); }
convertUtil.h文件 是自定义的文件,目前只有一个 打印log 的函数,我们可以自己封装一些其它函数在里面。(4)实现头文件声明的函数。// // 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* );
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 文件(5) 在Java中使用 native 方法。#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; }
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