前一章说到了基本类型转化和数组等通用类型通用 交互的转换。那么间接的说明了 JNI 调用Java的方法, 参数为基本类型数据和基本类型数组的方式;那么这一篇就是介绍 JNI如何调用Java 的对象的方法,成员变量,还有类方法,静态变量等。


          Cmake, 加入c++头文件的方式

在开发中通常把 C++头文件和源文件分开的方式。首先创建cppHeader 文件夹 来放C++头文件,和cppSouce文件夹来放源文件,当然,也可以使用其它的文件夹,或者命名不同。

           

c通过jni调用java的接口 jni c调用java_java

           打开CmakeLists.txt 文件配置添加 


include_directories(src/main/cpp/cppHeader) 到文件中。

c通过jni调用java的接口 jni c调用java_java_02

这样可以保证我们的头文件 在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