Android修炼系列(26),jni 看这篇就够了

4,806 阅读6分钟

前段时间写过一篇 来编译一个自己的 so 库吧,对于 jni 命名等知识点有一定的涉及,这里就不重复说了,本节的重点放在介绍 jni 的数据传递上,即 java 如何传递对象给 c++,而 c++ 又如何将数据封装成 java 所需的对象。

java 结构体传到 c++ 侧:

对象

写一个简单的类,需要从 java 层传递给 c++:

package com.test;

class Test {
    boolean isT;
}

直入主题,对于基本数据类型的属性,很方便:

JNIEXPORT jint JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
    jclass jc_Test = env->GetObjectClass(object);           /* object是native方法内,传递的Test */
    jfieldID jf_isT = env->GetFieldID(jc_Test, "isT", "Z"); /* argument #2:属性名称,argument #3:对应的jni类型 */
    Extra extra_;                                           /* 创建c++对象 Extra */
    extra_.param = env->GetBooleanField(object, jf_isT);    /* 获取java 对象的isT值,并赋值给c++对象param属性 */
    return 0;
}

其他基本类型使用方式类似,唯一需要注意的是long 和 boolean,对应 J 和 Z。数组 类型同理,对应 jbyteArray 和 [B

jni2.png

String 类型

写一个简单的类,需要从 java 层传递给 c++:

package com.test;

class Test {
    String str;
}

对于String属性,可以这样:

JNIEXPORT jint JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
    jclass jc_Test = env->GetObjectClass(object);
    jfieldID jf_str = env->GetFieldID(jc_Test, "str", "Ljava/lang/String;");
    auto jstr = (jstring) env->GetObjectField(object, jf_str);
    Extra extra_;
    extra_.param = = common::String::getString(env, jstr);    /* jstring需要转换成c++需要的string */
    env->DeleteLocalRef(jstr);                                /* 记得销毁 */
    return 0;
}

这是工具类的头文件:

#include <jni.h>
#include <string>

namespace common {
    class String {
    public:
        static std::string getString(JNIEnv *env, jstring jStr);
        static jstring getJString(JNIEnv *env, const char *str);
    };
}

关于jstring 和 std::string的转换,不像 java 那么方便,代码见下:

static const char *NAME = "java/lang/String";
jclass cls = env->FindClass(NAME);
jmethodID String::methodGetBytes = env->GetMethodID(clazz, "getBytes", "(Ljava/lang/String;)[B");

std::string String::getString(JNIEnv *env, jstring jStr) {
    if (jStr == nullptr)
        return "";

    jstring encoding = env->NewStringUTF("UTF-8");
    const auto stringJBytes = (jbyteArray) env->CallObjectMethod(jStr, methodGetBytes, encoding);

    auto length = (size_t) env->GetArrayLength(stringJBytes);
    jbyte* pBytes = env->GetByteArrayElements(stringJBytes, nullptr);

    std::string ret = std::string((char *)pBytes, length);
    env->ReleaseByteArrayElements(stringJBytes, pBytes, JNI_ABORT);

    env->DeleteLocalRef(stringJBytes);
    env->DeleteLocalRef(encoding);
    return ret;
}

std::string 转换 jstring 见下:

jstring j_str = common::String::getJString(env, str_.c_str()); /* str_ 为std::string 类型,j_str 为jstring 类型 */

static const char *NAME = "java/lang/String";
jclass cls = env->FindClass(NAME);
jmethodID String::methodConstructor = env->GetMethodID(clazz, "<init>", "([BLjava/lang/String;)V");

jstring String::getJString(JNIEnv *env, const char *str) {
    if (str == nullptr) {
        return nullptr;
    }
    int length = strlen(str);
    jbyteArray jBytes = env->NewByteArray(length);
    env->SetByteArrayRegion(jBytes, 0, length, (jbyte *) str);

    jstring encoding = env->NewStringUTF("UTF-8");
    auto result = (jstring) env->NewObject(clazz, methodConstructor, jBytes, encoding);

    env->DeleteLocalRef(jBytes);
    env->DeleteLocalRef(encoding);

    return result;
}

对象嵌套对象

写一个简单的类,需要从 java 层传递给 c++:

package com.test;

class Test {
    Delegate d; /* 全限定名 com.test.Delegate */
}

class Delegate {
    float i;
}

对于嵌套类,可以这样:

JNIEXPORT jint JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
    jclass jc_Test = env->GetObjectClass(object); /* 先获取 Test.class */
    jfieldID jf_delegate = env->GetFieldID(jc_Test, "d", "Lcom/test/Delegate;");
    jobject jDelegate = env->GetObjectField(object, jf_delegate);
    if (jDelegate != nullptr) {
        jclass clsDelegate = env->GetObjectClass(jDelegate); /* 再获取 Delegate.class */
        jfieldID jf_del_i = env->GetFieldID(clsDelegate, "i", "F");
        
        Extra extra_; /* 将float 值赋给c++*/
        extra_.param = env->GetFloatField(jDelegate, jf_del_i);
    }
    env->DeleteLocalRef(jDelegate);
    return 0;
}

List 对象列表

写一个简单的类,需要从 java 层传递给 c++:

package com.test;

class Test {
    ArrayList<Delegate> list;
}

class Delegate { /* 全限定名 com.test.Delegate */
    float i;
}

对于容器类,我们可以这样:

static const char* NAME_ArrayList = "java/util/ArrayList";

JNIEXPORT jint JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
    jclass clsArrayList = env->FindClass(NAME_ArrayList); /* 注意List 和 ArrayList 有区别 */
    jmethodID methodGet = env->GetMethodID(clsArrayList, "get", "(I)Ljava/lang/Object;");
    jmethodID methodSize = env->GetMethodID(clsArrayList, "size", "()I");
    
    jclass jc_Test = env->GetObjectClass(object);
    jfieldID jf_delegates = env->GetFieldID(jc_Test, "list", "Ljava/util/ArrayList;");
    jobject jDelegates = env->GetObjectField(object, jf_delegates); /* 获取list */
    
    if (jDelegates != nullptr) {
        jint size = env->CallIntMethod(jDelegates, methodSize);
        for (int i = 0; i < size; i++) {
            jobject jDelegate = env->CallObjectMethod(jDelegates, methodGet, i); /* 拿到list item元素 */
            jclass cls_delegate = env->GetObjectClass(jDelegate);
            jfieldID jf_delegate = env->GetFieldID(cls_delegate, "i", "F"); /* 拿到每个元素内的属性值 */
            
            Extra extra_; /* 将float 值赋给c++ */
            extra_.param = env->GetFloatField(jDelegate, jf_delegate);
            
            env->DeleteLocalRef(jDelegate);
        }
    }
    
    env->DeleteLocalRef(jDelegates);  
    return 0;
}

List 基本数据列表

写一个简单的类,需要从 java 层传递给 c++:

package com.test;

class Test {
    ArrayList<Integer> list;
}

对于List 内有基本类型的包装类,我们可以这样:

JNIEXPORT jint JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
    jclass clsArrayList = env->FindClass(NAME_ArrayList); /* 注意List 和 ArrayList 有区别 */
    jmethodID methodGet = env->GetMethodID(clsArrayList, "get", "(I)Ljava/lang/Object;");
    jmethodID methodSize = env->GetMethodID(clsArrayList, "size", "()I");
    
    jclass jc_Test = env->GetObjectClass(object);
    jfieldID jf_indexs = env->GetFieldID(jc_Test, "list", "Ljava/util/ArrayList;");
    jobject jIndexs = env->GetObjectField(object, jf_indexs); /* 获取list */
    
    if (jIndexs != nullptr) {
        jint size = env->CallIntMethod(jIndexs, methodSize);
        for (int i = 0; i < size; i++) {
            jint jIndex = (jint) env->CallObjectMethod(jIndexs, methodGet, i); /* 拿到list item元素 */
            
            Extra extra_; /* 将 int 值赋给c++ */
            extra_.param = jIndex;
        }
    }
    
    env->DeleteLocalRef(jIndexs);  
    return 0;
}

List 嵌套 List

写一个简单的类,需要从 java 层传递给 c++:

package com.test;

class Test {
    ArrayList<ArrayList<Delegate>> list;
}
class Delegate { /* 全限定名 com.test.Delegate */
    float i;
}

对于List 巧套 List,跟上面单纯的 List 一样,不过就是一层一层解析罢了,代码见下:

static const char* NAME_ArrayList = "java/util/ArrayList";

JNIEXPORT jint JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
    jclass clsArrayList = env->FindClass(NAME_ArrayList); /* 注意List 和 ArrayList 有区别 */
    jmethodID methodGet = env->GetMethodID(clsArrayList, "get", "(I)Ljava/lang/Object;");
    jmethodID methodSize = env->GetMethodID(clsArrayList, "size", "()I");
    
    jclass jc_Test = env->GetObjectClass(object);
    jfieldID jf_delegates = env->GetFieldID(jc_Test, "list", "Ljava/util/ArrayList;");
    jobject jDelegates = env->GetObjectField(object, jf_delegates); /* 获取外层list */
    
    if (jDelegates != nullptr) {
        jint size = env->CallIntMethod(jDelegates, methodSize);
        for (int i = 0; i < size; i++) {
            jobject jDelegate = env->CallObjectMethod(jDelegates, methodGet, i);
            jint s = env->CallIntMethod(jDelegate, methodSize);
            for (int j = 0; j < s; j++) {
                jobject j_inner_delegate = env->CallObjectMethod(jDelegate, methodGet, j); /* 获取内层 list*/
                jclass cls_inner_delegate = env->GetObjectClass(j_inner_delegate);
                jfieldID jf_inner_deledate = env->GetFieldID(cls_inner_delegate, "i", "F");
                jfloat i = env->GetFloatField(j_inner_delegate, jf_inner_deledate) /* 目标值 */
                
                env->DeleteLocalRef(j_inner_delegate);
            }
            env->DeleteLocalRef(jDelegate);
        }
    }
    
    env->DeleteLocalRef(jDelegates);  
    return 0;
}

c++ 数据吐给 java 结构体

对象

写一个简单的类,需要从 c++ 中取得数据并赋值给该结构体,提供给 java 侧使用:

package com.test;

class Test {
    float i;
}

其实与上面讲的非常类似,代码见下:

static const char* NAME_Test = "com/test/Test";

JNIEXPORT jobject JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
    jclass cls_test = env->FindClass(NAME_Test);
    jmethodID methodInit = env->GetMethodID(cls_test, "<init>", "()V"); /* 无参构造 */
    jobject j_test = env->NewObjectA(cls_test, methodInit, 0); /* java 结构体 Test 对象 */
    
    jfieldID jf_test = env->GetFieldID(cls_test, "i", "F");
    env->SetIntField(j_test, jf_test, 1.0f); /* 给结构体属性赋值 */

    return j_test;
}

List 列表

写一个简单的类,需要从 c++ 中取得数据并赋值给该结构体,提供给 java 侧使用:

package com.test;

class Test {
    ArrayList<Delegate> list;
}
class Delegate { /* 全限定名 com.test.Delegate */
    float i;
}

这是一个容器类,其实也差不多:

static const char* NAME_Test = "com/test/Test";
static const char* NAME_Delegate = "com/test/Delegate";
static const char* NAME_ArrayList = "java/util/ArrayList";

JNIEXPORT jobject JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
    jclass cls_test = env->FindClass(NAME_Test);
    jmethodID methodInit = env->GetMethodID(cls_test, "<init>", "()V"); /* 无参构造 */
    jobject j_test = env->NewObjectA(cls_test, methodInit, 0); /* java 结构体 Test 对象 */
    
    jclass clsArrayList = env->FindClass(NAME_ArrayList);
    jmethodID methodAdd = env->GetMethodID(clsArrayList, "add", "(Ljava/lang/Object;)Z");
    
    jfieldID jf_delegates = env->GetFieldID(cls_test, "list", "Ljava/util/ArrayList;");
    jmethodID methodInit_list = env->GetMethodID(clsArrayList, "<init>", "()V");
    jobject j_delegates = env->NewObject(clsArrayList, methodInit_list, "");
    env->SetObjectField(j_test, jf_delegates, j_delegates);
    
    for (auto iter : tests_) { /* test_ 是c++ 定义的容器,这里就不写了 */
        jclass cls_delegate = env->FindClass(NAME_Delegate);
        jmethodID methodInit_del = env->GetMethodID(cls_delegate, "<init>", "()V");
        jobject j_delegate = env->NewObject(cls_delegate, methodInit_del, ""); /* 创建内部的 Delegate 对象 */
        env->CallBooleanMethod(j_delegates, methodAdd, j_delegate); /* 添加到list容器内 */
        
        jfieldID jf_del = env->GetFieldID(cls_delegate, "i", "F");
        env->SetIntField(j_delegate, jf_del, 1.0f); /* 给结构体属性赋值 */  
        
        env->DeleteLocalRef(j_delegate);
    }
    
    env->DeleteLocalRef(j_delegates);
    return j_test;
}

其他的结构体就不介绍了,其实来来回回就这点东西,有一点需要注意的:

package com.test;

class Test {
    ArrayList<Integer> list;
}

当List 内为包装类时,我们也需要这样创建包装类:

    jclass cls_integer = env->FindClass("java/lang/Integer");
    jmethodID methodInteger = env->GetMethodID(cls_integer, "<init>", "(I)V");

数组

vector< char> 转成 byte[] ,可以这样:

    std::vector<char> rawData = c_rsp->GetRawdata(); /* c++ 方法,可以不管 */
    
    jfieldID jfRawDataID = env->GetFieldID(cls_batchRsp, "rawData", "[B");
    int size = rawData.size();
    jbyteArray j_raw_array = env->NewByteArray(size); /* 反给java 的结构体 byte[] */
    jbyte *j = new jbyte[size];
    std::memcpy(j, &rawData[0], size);
    
    env->SetByteArrayRegion(j_raw_array, 0, size, j);
    env->SetObjectField(j_batchRsp, jfRawDataID, j_raw_array);

byte[] 转成 char* data,可以直接这样:

    char *c_array = (char*) env->GetByteArrayElements(array, 0);
    int len_arr = env->GetArrayLength(array);

尾声

关于 jni 的结构体的传递,就介绍到这里了,实际开发里,肯定还会遇到各类问题,到时候多多google 就好了。