完整的JNI 例子和总结

一 环境的搭建

下载DNK,在配置sdk地方配置ndk 的本地路径,或者在build.gradle 的配置文件中配置ndk 的路径,通过同步也可以配置ndk 环境,NDK 开发中涉及调试,确保

在SDK tools 中安装,在开始开发的时候,一定要把开发环境&调试环境设置好,我在开发的时候遇到环境没法办法调试,网上能找到的办法都试了一遍,还是没有办法解决,后来干脆重新创建个新项目,这个项目在创建的时候选择C++ project ,然后将之前的项目直接拷贝进去, 可以debug了,可能还是工程环境的原因导致。

还有个原因我的开发机器是华为机器,后来换成三星或者魅族的手机都可以调试了

跑起来调试也是个超级开心的事情。

二,CMakeLists.txt 配置

现在涉及JNI 开发已经不用原来的android.mk 方式配置了,大部分都是用使用CMakeLists.txt配置,什么都不说了,直接上配置的修改

# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

#
# 指定so生成到libs目录

add_definitions(-std=c++11)
FILE(GLOB Source_SRC source/*.cpp)
add_library( # Sets the name of the library.
        source

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        #        com_example_diff_BsDiffUtils.c
        #
        ${Source_SRC}
        )
#
## Searches for a specified prebuilt library and stores the path as a
## variable. Because CMake includes system libraries in the search path by
## default, you only need to specify the name of the public NDK library
## you want to add. CMake verifies that the library exists before
## completing its build.
#
find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

## Specifies libraries CMake should link to your target library. You
## can link multiple libraries, such as libraries you define in this
## build script, prebuilt third-party libraries, or system libraries.
#
target_link_libraries( # Specifies the target library.
        source

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

1,cmake_minimum_required(VERSION 3.4.1)

配置版本号,基本都是这样配置

2,FILE(GLOB Source_SRC source/*.cpp)

这个地方是配置C/C++ 源码的存放路径,这个地方是配置在source 文件夹下,对外开放的链接别名是Source_SRC。其中GLOB是全部文件的意思,只支持单个文件夹的源码依赖形式,如果source 文件夹中有含有子文件夹,CMAKE 语法中需要改成GLOB_RESOURCE,但是没有设置成功,

异常java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList]

后续需要花点时间调研这个问题。

 

三 对应java 对象创建

import java.util.ArrayList;
import java.util.HashMap;

public class  NativeValue {
    public boolean boolValue;
    public int intValue;
    public double doubleValue;
    public String stringValue;
    public ArrayList arrayValue;
    public HashMap<String, NativeValue> mapValue;
    public String valueType;


    public NativeValue() {

    }

    public NativeValue(HashMap<String, NativeValue> mapValue) {
        this.setMapValue(mapValue);
    }

    public NativeValue(String stringValue) {
        this.setStringValue(stringValue);
    }

    public NativeValue(double doubleValue) {
        this.setDoubleValue(doubleValue);

    }

    public NativeValue(ArrayList arrayValue) {
        this.setArrayValue(arrayValue);
    }


    public NativeValue(int intValue) {
        this.setIntValue(intValue);
    }

    public NativeValue(boolean boolValue) {
        this.setBoolValue(boolValue);
    }

    public String getStringValue() {
        return this.stringValue;
    }

    public boolean getBoolValue() {
        return this.boolValue;
    }

    public double getDoubleValue() {
        return this.doubleValue;
    }

    public int getIntValue() {
        return this.intValue;
    }

    public String getValueType() {
        return this.valueType;
    }

    public HashMap<String, NativeValue> getMapValue() {
        return this.mapValue;
    }

    public ArrayList<NativeValue> getArrayValue() {
        return this.arrayValue;
    }

    public void setMapValue(HashMap<String, NativeValue> mapValue) {
        this.mapValue = mapValue;
        this.valueType = NativeValueType.TypeMap;
    }

    public void setStringValue(String stringValue) {
        this.stringValue = stringValue;
        this.valueType = NativeValueType.TypeString;
    }

    public void setArrayValue(ArrayList arrayValue) {
        this.arrayValue = arrayValue;
        this.valueType = NativeValueType.TypeArray;
    }

    public void setDoubleValue(double doubleValue) {
        this.doubleValue = doubleValue;
        this.valueType = NativeValueType.TypeDouble;
    }

    public void setIntValue(int intValue) {
        this.intValue = intValue;
        this.valueType = NativeValueType.TypeInt;
    }

    public void setBoolValue(boolean boolValue) {
        this.boolValue = boolValue;
        this.valueType =NativeValueType.TypeBool;
    }


}
public class NativeValueType {
    public static final String TypeString = "TypeString";

    public static final String TypeMap = "TypeMap";

    public static final String TypeInt = "TypeInt";

    public static final String TypeDouble = "TypeDouble";

    public static final String TypeBool = "TypeBool";

    public static final String TypeObject = "TypeObject";

    public static final String TypeArray = "TypeArray";

    public static final String TypeNull = "TypeNull";

}

这个地方是包含了大部分类型数据,创建cpp 和 hpp 两个文件,hpp 文件是用来声明方法的

那么先看下NativeValue 中如何对String 类型数据进行传递的,首先室java->C++

CValue convertToCValueFromJavaObject(JNIEnv *env, jclass clazz, jobject obj) {
    CValue value; //Cvalue 是个C++类,我么这里就是讲NatvieValue转化成CValue类型
    jclass valueInfo = env->GetObjectClass(obj);
    jmethodID m_HashMap = env->GetMethodID(valueInfo, "getMapValue", "()Ljava/util/HashMap;");// 获取NavtiveValue 中的获取map 的方法
    jmethodID m_string = env->GetMethodID(valueInfo, "getStringValue", "()Ljava/lang/String;");// 获取NativeValue 中的获取String 的方法
    jobject hashMapObj = (jobject) env->CallObjectMethod(obj, m_HashMap);
    jstring stringObj = (jstring) env->CallObjectMethod(obj, m_string);
    jfieldID f_List = env->GetFieldID(valueInfo, "arrayValue", "Ljava/util/ArrayList;"); 
// 获取 NativeValue 中arrayValue 属性,这个地方不同于其他资料中的取ArrayList 的方式,
// 这里是将整个对象jobject传递进来,然后获取jobject 中的arrayValue 中的值
    jobject arrayObj = env->GetObjectField(obj, f_List);
    jfieldID f_double = env->GetFieldID(valueInfo, "doubleValue", "D"); 
// 获取double 属性
    jdouble doubleObj = (jdouble) env->GetDoubleField(obj, f_double);
    jfieldID f_int = env->GetFieldID(valueInfo, "intValue", "I");
//获取int 属性
    jint intObj = (jdouble) env->GetIntField(obj, f_int);
    jfieldID f_bool = env->GetFieldID(valueInfo, "boolValue", "Z");
//获取bool 属性
    jboolean boolObj = (jboolean) env->GetBooleanField(obj, f_bool);

    if (hashMapObj != NULL) {
        std::map<std::string, CValue> map = convertToCMapFromJavaJSONObj(env, clazz,
                                                                                  hashMapObj);
        value = CValue(map);
        return value;
    } else if (stringObj != NULL) {
        std::string stringValue = convertToCValueFromJavaString(env, clazz, stringObj);
        value = CValue(stringValue);
        return value;
    } else if (arrayObj != NULL) {
        std::vector<CValue> array = convertToCValueMapFromJavaArray(env, clazz, arrayObj);
        value = CValue(array);
        return value;
    } else if (doubleObj != NULL) {
        value = CValue(doubleObj);
        return value;
    } else if (intObj != NULL) {
        value = CValue(intObj);
        return value;
    } else if (boolObj != NULL) {
        value = CValue();
        value.setBoolValue(boolObj);
        return value;
    }
    return CValue();
}

我们先看下java-》C++ 转换中对hashMap类型转换的方法 convertToCMapFromJavaJSONObj 的具体实现

std::map<std::string, CValue>
convertToCValueMapFromJavaJSONObj(JNIEnv *env, jclass clazz, jobject hashMapObj) {
    std::map<std::string, CValue> dataMap;
    // 获取HashMap类entrySet()方法ID
    jclass hashMapClass = env->FindClass("java/util/HashMap");
    jmethodID entrySetMID = env->GetMethodID(hashMapClass, "entrySet", "()Ljava/util/Set;");
    // 调用entrySet()方法获取Set对象
    jobject setObj = env->CallObjectMethod(hashMapObj, entrySetMID);
    // 调用size()方法获取HashMap键值对数量
    // 获取Set类中iterator()方法ID
    jclass setClass = env->FindClass("java/util/Set");
    jmethodID iteratorMID = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
    // 调用iterator()方法获取Iterator对象
    jobject iteratorObj = env->CallObjectMethod(setObj, iteratorMID);
    // 获取Iterator类中hasNext()方法ID,用于while循环判断HashMap中是否还有数据
    jclass iteratorClass = env->FindClass("java/util/Iterator");
    jmethodID hasNextMID = env->GetMethodID(iteratorClass, "hasNext", "()Z");
    // 获取Iterator类中next()方法ID,用于读取HashMap中的每一条数据
    jmethodID nextMID = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
    // 获取Map.Entry类中getKey()和getValue()的方法ID,注意:内部类使用$符号表示
    jclass entryClass = env->FindClass("java/util/Map$Entry");
    jmethodID getKeyMID = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
    jmethodID getValueMID = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");
    // 循环检测HashMap中是否还有数据
    while (env->CallBooleanMethod(iteratorObj, hasNextMID)) {
        // 读取一条数据
        jobject entryObj = env->CallObjectMethod(iteratorObj, nextMID);
        jstring mapKey = (jstring) env->CallObjectMethod(entryObj, getKeyMID);
        if (mapKey == NULL)   // HashMap允许null类型
            continue;
        const char *jstrChar = (char *) env->GetStringUTFChars(mapKey, 0);
        std::string keyStr = jstrChar;
        jobject valueObj = env->CallObjectMethod(entryObj, getValueMID);
//      这个地方室递归调用转化对象的方法,可以处理map 中value 存在嵌套的场景
        CValue value = convertToCValueFromJavaObject(env, clazz, valueObj);
        dataMap.insert(pair<std::string, CValue>(keyStr, value));
    }
    return dataMap;
}

我们再看下java-> C++ 中 ArrayList 是如何转化的 ,对应方法是 convertToCValueMapFromJavaArray

std::vector<CValue>
convertToCValueMapFromJavaArray(JNIEnv *env, jclass clazz, jobject arrayObj) {
    std::vector<CValue> vector;
    jclass cArrayList = env->GetObjectClass(arrayObj);
    jmethodID arrayList_get = env->GetMethodID(cArrayList, "get", "(I)Ljava/lang/Object;");
    jmethodID arrayList_size = env->GetMethodID(cArrayList, "size", "()I");
    jint len = env->CallIntMethod(arrayObj, arrayList_size);
    for (int i = 0; i < len; ++i) {
        jobject obj_value = env->CallObjectMethod(arrayObj, arrayList_get, i);
        // 这个地方递归调用,支持ArrayList 中存放NativeValue 类似的数据
        CValue value = convertToCValueFromJavaObject(env, clazz, obj_value);
        vector.push_back(value);
    }
    return vector;

}

再看下java->C++ 中 String 类型的数据转化,具体实现方法看下 convertToCValueFromJavaString

std::string convertToCValueFromJavaString(JNIEnv *env, jclass clazz, jstring jstringObj) {
    const char *jstrChar = (char *) env->GetStringUTFChars(jstringObj, 0);
    std::string str = jstrChar;
    return str;
}

上面讲完了java->C++ 的过程,接下来我们讲下 C++ -> java 过程的实现方式

 

jobject convertToJavaObjectFromCValue(JNIEnv *env, CValue value) {
    // 这里是转向natvie class 对应的路径
    jclass c_value = env->FindClass("com/**/**/**/ValueNative");
    jfieldID jfValueType = env->GetFieldID(c_value, "valueType", "Ljava/lang/String;");
    jobject joInfo = env->AllocObject(c_value);
    // 生成String 对象,根据valueType去区分要处理那种类型的数据
    if (CValue.valueType == TypeString) {
        std::string str = value.getStringValue();
        const char *resultStr = str.c_str();
        jfieldID jfs = env->GetFieldID(c_value, "stringValue", "Ljava/lang/String;");
        jstring jResultStr = env->NewStringUTF(resultStr);
        env->SetObjectField(joInfo, jfs, jResultStr);
        std::string typeStr = "MorTypeString";
        const char *typeChar = typeStr.c_str();
        jstring jTypeStr = env->NewStringUTF(typeChar);
        env->SetObjectField(joInfo, jfValueType, jTypeStr);
        return joInfo;
    }
        // 生成map 对象
    else if (value.valueType == TypeMap) {
        std::map<std::string, CValue> map = value.getMapValue();
        jclass hashMapClass = env->FindClass("java/util/HashMap");
        //这个是创建无参数的构造方法
        jmethodID hashMapInit = env->GetMethodID(hashMapClass, "<init>", "(I)V");
        jobject hashMapObj = env->NewObject(hashMapClass, hashMapInit, map.size());
        jmethodID hashMapOut = env->GetMethodID(hashMapClass, "put",
                                                "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
        std::map<std::string, MorValue>::const_iterator it;
        for (it = map.begin(); it != map.end(); ++it) {
            std::string str = it->first;
            const char *chardata = str.c_str();
            jstring string = env->NewStringUTF(chardata);;
            CValue value = (CValue) it->second;
            // 这个地方室采用递归方式
            jobject info = convertToJavaObjectFromCValue(env, value);
            env->CallObjectMethod(hashMapObj, hashMapOut, string, info);
            jfieldID jfl = env->GetFieldID(c_value, "mapValue", "Ljava/util/HashMap;");
            env->SetObjectField(joInfo, jfl, hashMapObj);
            std::string typeStr = "MorTypeMap";
            const char *typeChar = typeStr.c_str();
            jstring jTypeStr = env->NewStringUTF(typeChar);
            env->SetObjectField(joInfo, jfValueType, jTypeStr);
        }
        return joInfo;

    } else if (value.valueType == TypeInt) {
        jfieldID jfl = env->GetFieldID(c_value, "intValue", "I");
        env->SetIntField(joInfo, jfl, value.getIntValue());
        std::string typeStr = "MorTypeInt";
        const char *typeChar = typeStr.c_str();
        jstring jTypeStr = env->NewStringUTF(typeChar);
        env->SetObjectField(joInfo, jfValueType, jTypeStr);
        return joInfo;
    } else if (value.valueType == TypeDouble) {
        jfieldID jfd = env->GetFieldID(c_value, "doubleValue", "D");
        env->SetDoubleField(joInfo, jfd, value.getDoubleValue());
        std::string typeStr = "TypeDouble";
        const char *typeChar = typeStr.c_str();
        jstring jTypeStr = env->NewStringUTF(typeChar);
        env->SetObjectField(joInfo, jfValueType, jTypeStr);
        return joInfo;
    } else if (value.valueType == TypeBool) {
        jfieldID jfd = env->GetFieldID(c_value, "boolValue", "Z");
        env->SetBooleanField(joInfo, jfd, value.getBoolValue());
        std::string typeStr = "TypeBool";
        const char *typeChar = typeStr.c_str();
        jstring jTypeStr = env->NewStringUTF(typeChar);
        env->SetObjectField(joInfo, jfValueType, jTypeStr);
        return joInfo;
    } else if (value.valueType == TypeArray) {
        std::vector<CValue> array = value.getArrayValue();
        jclass list_jcs = env->FindClass("java/util/ArrayList");
        //获取ArrayList构造函数id
        jmethodID list_init = env->GetMethodID(list_jcs, "<init>", "()V");
        //创建一个ArrayList对象
        jobject list_obj = env->NewObject(list_jcs, list_init);
        //获取ArrayList对象的add()的methodID
        jmethodID list_add = env->GetMethodID(list_jcs, "add",
                                              "(Ljava/lang/Object;)Z");
        jfieldID jArrayList = env->GetFieldID(c_value, "arrayValue", "Ljava/util/ArrayList;");
        for (int i = 0; i < array.size(); ++i) {
             // 这个地方是递归,支持ArrayList 中的元素还是本身类型的
            jobject info = convertToJavaObjectFromCValue(env, (CValue) array[i]);
            env->CallBooleanMethod(list_obj, list_add, info);
        }
        env->SetObjectField(joInfo, jArrayList, list_obj);
        std::string typeStr = "TypeArray";
        const char *typeChar = typeStr.c_str();
        jstring jTypeStr = env->NewStringUTF(typeChar);
        env->SetObjectField(joInfo, jfValueType, jTypeStr);
        return joInfo;

    }
    env->DeleteLocalRef(c_value);
    return joInfo;
}

我们顺便看下我们声明的  hpp 文件中的声明形式

#include <jni.h>
#include "CValue.hpp"
#include <string>
#include <map>

using namespace std;
using namespace VMSpace;
extern "C" {
JNIEXPORT jobject JNICALL
Java_com_**_**_**_JNINative_JNIConvertToAndroidObjectFromCValue
        (JNIEnv *, jclass, jstring);
 
JNIEXPORT void JNICALL
Java_com_**_**_**_JNINative_JNIConvertToCValueFromAndroidObject
        (JNIEnv *, jclass, jobject);
// java->c++
std::map<std::string, CValue>
convertToCValueMapFromJavaJSONObj(JNIEnv *, jclass clazz, jobject obj);
std::string convertToMorValueFromJavaString(JNIEnv *, jclass clazz, jstring jstring);
CValue convertToCValueFromJavaObject(JNIEnv *, jclass clazz, jobject obj);
std::vector<CValue> convertToCValueMapFromJavaArray(JNIEnv *, jclass clazz, jobject obj);
// c++ -> java
jobject convertToJavaObjectFromCValue(JNIEnv *, CValue value);


};

开发中涉及C++ 技术栈,主要涉及以下几点

1  jstring 和 C++ String 的转化

C++ String 可以与const char *a 相等,所以涉及到jstring 和 char 之前的转化

char -> jstring 

采用 env->newStringUTF

 char->std::string

 std::string str = value.getStringValue();
 const char *resultStr = str.c_str();

jstring ->char const char *jstrChar = (char *) env->GetStringUTFChars(jstringObj, 0);

 

参考资料:

1  http://gityuan.com/2016/05/28/android-jni/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值