基于CMake的JNI开发探索

实际项目中用到了JNI,在此对碰到的坑做一些总结。android studio2.2支持android.mk和CMake编译配置工具两种方式,谷歌官方推荐使用CMake并且做了很好的支持。

首先,需要配置ndk环境:
File->Settings->Appearance&Behavior->System Setings->Android SDK,选中SDK Tools标签页,选择CMake,LLDB,NDK进行安装如下图:
CMake:编译配置工具。
LLDB:调试C代码。
NDK:开发工具包。
这里写图片描述
下载完成后查看 File->Project Structure,是否配置ndk:
(如果没有就配置一下)
这里写图片描述

好了NDK环境配置完成,接下来重启IDE,新建一个jni工程:

需要勾选include c++ support,项目就可以进行ndk开发
这里写图片描述

C++ Standard
指定编译库的环境,其中Toolchain Default使用的是默认的CMake环境。建议选择C++ 11,表示支持C++ 11库。

Exceptions Support
如果选中复选框,则表示当前项目支持C++异常处理,建议勾选。

Runtime Type Information Support
同理,选中复选框,项目支持RTTI,建议勾选。

注:当然此处不勾选,也可以在项目中的gradle下添加支持
这里写图片描述

好了,项目新建完成,查看项目的结构:
cpp目录:用于存放c/c++文件。
CMakeLists.txt:CMake的配置文件。
这里写图片描述


直入主题,首先删掉了android studio给我们的hellow示例。新建了一个JNIManager单例类,用于存放native接口;定义了一个头文件native-lib.h(cpp目录下)。

public class JNIManager {

    static {
        System.loadLibrary("native-lib");
    }

    private static JNIManager mJNIManger = null;

    private JNIManager() {
    }

    public static JNIManager getInstance() {
        if (mJNIManger == null) {
            synchronized (JNIManager.class) {
                if (mJNIManger == null) {
                    mJNIManger = new JNIManager();
                }
            }
        }
        return mJNIManger;
    }

    public native int add(int x, int y);//java传入int值给C,C返回int值给java

    public static native String passString(String str);//传入String值,返回String值

    public static native int[] passIntArray(int[] intArray);//传入int数组,返回int数组

    public native void passOthers(char m_char, boolean m_boolean, byte m_byte, short m_short, long m_long, float m_float, double m_double);//传入其他基本数据类型,无返回值

}

其中,

static {
        System.loadLibrary("native-lib");
    }

表示导入native-lib的库文件,以便使用库中的api。其接口都用native声明(static修饰符可加可不加),在声明接口后,用快捷键可在cpp生成相应的函数,如下:

#include "native-lib.h"

JNIEXPORT jint JNICALL
Java_com_hecc_jnitest_JNIManager_add(JNIEnv *env, jobject instance, jint x, jint y) {

    return x + y;//返回x,y的和
}

JNIEXPORT jstring JNICALL
Java_com_hecc_jnitest_JNIManager_passString(JNIEnv *env, jclass type, jstring m_str) {

    char *str = (char *) env->GetStringUTFChars(m_str, 0);//将java中的String类型的值转化成C识别的char*类型的值
    // 对字符串进行(移位)处理
    int length = strlen(str);
    for (int i = 0; i < length; ++i) {
        *(str + i) += 1;
    }
    env->ReleaseStringUTFChars(m_str, str);//释放内存

    return env->NewStringUTF(str);//返回一个处理后的字符串
}

JNIEXPORT jintArray JNICALL
Java_com_hecc_jnitest_JNIManager_passIntArray(JNIEnv *env, jclass type, jintArray m_intArray) {


    jint *intArray = env->GetIntArrayElements(m_intArray, NULL);//获取数组的指针
    jsize length = env->GetArrayLength(m_intArray);//获取数组长度
    // 对数组进行(元素+10)处理
    for (int i = 0; i < length; i++) {
        *(intArray + i) += 10;
    }
    env->ReleaseIntArrayElements(m_intArray, intArray, 0);//释放内存

    return m_intArray;//返回一个处理后的数组
}

JNIEXPORT void JNICALL
Java_com_hecc_jnitest_JNIManager_passOthers(JNIEnv *env, jobject instance, jchar m_char,
                                            jboolean m_boolean, jbyte m_byte, jshort m_short,
                                            jlong m_long, jfloat m_float, jdouble m_double) {

    LOGI("m_char=%c,m_boolean=%d,m_byte=%d,m_short=%hd,m_long=%ld,m_float=%f,m_double=%lf",
         m_char, m_boolean, m_byte, m_short, m_long, m_float, m_double);

    LOGE("打印:LOGE");
}

简单说明下,ndk环境下的书写格式都是固定的。
C本地函数命名规则: Java_包名类名本地方法名
JNIEXPORT,JNICALL:是系统定义的宏,JNIEXPORT后面写函数的输出类型,JNICALL后面写函数名,实测这两个宏可写可不写。
JNIEnv *env: 是结构体JNINativeInterface的二级指针,重定义了大量的函数指针,这些函数指针在jni开发中很常用。可以理解为JNI的上下文环境,需要通过env调用各种接口。
jobject instance :调用本地函数的java对象,在此例中就是JNIManager的实例。

为了代码清晰,我把函数的声明放在头文件native-lib.h,如下:
注:extern “C”里写函数声明,这一步也是必须的,否则将报错:cant find method

#ifndef JNITEST_NATIVE_LIB_H
#define JNITEST_NATIVE_LIB_H

#include <jni.h>
#include <string>
#include <android/log.h>

#define  LOG_TAG    "System.out.fromC"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

extern "C" {

JNIEXPORT jint JNICALL
        Java_com_hecc_jnitest_JNIManager_add(JNIEnv *env, jobject instance, jint x, jint y);

JNIEXPORT jstring JNICALL
        Java_com_hecc_jnitest_JNIManager_passString(JNIEnv *env, jclass type, jstring str_);

JNIEXPORT jintArray JNICALL
        Java_com_hecc_jnitest_JNIManager_passIntArray(JNIEnv *env, jclass type, jintArray intArray_);

JNIEXPORT void JNICALL
              Java_com_hecc_jnitest_JNIManager_passOthers(JNIEnv *env, jobject instance, 
              jchar m_char, jboolean m_boolean, jbyte m_byte, jshort m_short, jlong m_long, 
              jfloat m_float, jdouble m_double);

}

#endif

然后在MainActivity写测试JNI的代码,如下:

public class MainActivity extends AppCompatActivity {


    JNIManager manager = JNIManager.getInstance();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        testJNI();

    }

    private void testJNI() {
        int sum = manager.add(3, 4);
        Log.i("MainActivity", "3+4=" + sum);

        String str = JNIManager.passString("abc");
        Log.i("MainActivity", "abc from C:" + str);

        int[] arr = {1, 2, 3};
        int[] newArr = manager.passIntArray(arr);
        printArr(arr);//打印原数组
        printArr(newArr);//打印JNI处理后的新数组
    }

    void printArr(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            Log.i("MainActivity", "元素" + i + "=" + arr[i]);
        }
    }
}

打印的结果如图:
print

查看结果正确,调用jni函数成功!值得注意的是,此处我打印了两次数组,发现原数组和新数组的结果一致。是不是很意外,其实不难理解,原数组传给C后(对照代码),获取原数组的指针和长度,然后是对其内存地址进行操作。然后返回值是操作后的原数组(所谓的新数组)。因此,两则结果一致就有理可据了,类似思想在JNI中会大量的用到。

可能大家会有疑问,如何在NDK环境中打印LOG?
答:需要调用系统库,在CMakeList.txt中配置相关log库(模板demo已配置好),然后在头文件中定义相关宏,如下:
log

上面C代码中是不是还有一个函数未测试,接下来在MainActivity中添加如下代码:

 char m_char = 'a';
 boolean m_boolean = true;
 byte m_byte = 1;
 short m_short = 2;
 long m_long = 30000000000L;
 float m_float = 1.2F;
 double m_double = 3.333D;
 manager.passOthers(m_char, m_boolean, m_byte, m_short, m_long, m_float, m_double);

log
发现效果还可以,以后就方便在C中打log了。(注:C的基本数据没有boolean类型,所以java中true对应C中是1,false对应C中是0

现在,我打算在java中写JNIBean类,存放各种数据类型;在C++中写一个CppBean.cpp和CppBean.h,用于和java数据进行交互。将java的实体类创建对象并赋值,传递给本地,再赋值给c++的映射的单例对象,大致的流程就是java–>ndk–>c/c++。
在cpp目录添加C/C++文件时,需要CMakeList.txt中进行配置,如下:
cmakelist.txt
你可能会觉得上图和自己的文件不一致,CMake语法中#是注释,所以我将原注释删除,写了中文注释。然后添加一行代码:src/main/cpp/CppBean.cpp。(对CMake语法不做说明)接着,点击按钮 Tools->Android->Sync Project with Gradle Files 进行同步。

JNIBean的代码,如下:

public class JNIBean {
    public static int jb_int;//静态变量

    public boolean   jb_boolean;
    public byte      jb_byte;
    public short     jb_short;
    public long      jb_long;
    public float     jb_float;
    public double    jb_double;

    public String    jb_str;
    public int[]     jb_intArr;
}

CppBean.h的代码,如下:

#ifndef JNITEST_CPPBEAN_H
#define JNITEST_CPPBEAN_H

#include <string>

using namespace std;

#define MCppBean  (CppBean::GetInstance())

typedef signed char Bool;
#define MPTrue          1
#define MPFalse         0

typedef unsigned char Byte;

class CppBean {
public:
    int getcpp_int() const;

    void setcpp_int(int jb_int);

    Bool getcpp_Bool() const;

    void setcpp_Bool(Bool jb_boolean);

    Byte getcpp_byte() const;

    void setcpp_byte(Byte jb_byte);

    short getcpp_short() const;

    void setcpp_short(short jb_short);

    long getcpp_long() const;

    void setcpp_long(long jb_long);

    float getcpp_float() const;

    void setcpp_float(float jb_float);

    double getcpp_double() const;

    void setcpp_double(double jb_double);

    string getcpp_str() const;

    void setcpp_str(char *str);

    int *getcpp_intArr();

    void setcpp_intArr(int *intArr, int length);


public:
    static CppBean &GetInstance() {
        static CppBean instance;
        return instance;
    }

    virtual ~CppBean();

private:
    CppBean();

    CppBean(const CppBean &);

    CppBean &operator=(const CppBean &);

private:

    int cpp_int;
    Bool cpp_boolean;
    Byte cpp_byte;
    short cpp_short;
    long cpp_long;
    float cpp_float;
    double cpp_double;

    string cpp_str;
    int cpp_intArr[];

};


#endif

CppBean.cpp的代码,如下:

#include "cppbean.h"

CppBean::CppBean() {
}

CppBean::~CppBean() {
}

int CppBean::getcpp_int() const {
    return cpp_int;
}

void CppBean::setcpp_int(int jb_int) {
    cpp_int = jb_int;
}

Bool CppBean::getcpp_Bool() const {
    return cpp_boolean;
}

void CppBean::setcpp_Bool(Bool jb_boolean) {
    cpp_boolean = jb_boolean;
}

Byte CppBean::getcpp_byte() const {
    return cpp_byte;
}

void CppBean::setcpp_byte(Byte jb_byte) {
    cpp_byte = jb_byte;
}

short CppBean::getcpp_short() const {
    return cpp_short;
}

void CppBean::setcpp_short(short jb_short) {
    cpp_short = jb_short;
}

long CppBean::getcpp_long() const {
    return cpp_long;
}

void CppBean::setcpp_long(long jb_long) {
    cpp_long = jb_long;
}

float CppBean::getcpp_float() const {
    return cpp_float;
}

void CppBean::setcpp_float(float jb_float) {
    cpp_float = jb_float;
}

double CppBean::getcpp_double() const {
    return cpp_double;
}

void CppBean::setcpp_double(double jb_double) {
    cpp_double = jb_double;
}

string CppBean::getcpp_str() const {
    return cpp_str;
}

void CppBean::setcpp_str(char *str) {
    cpp_str.assign(str);
}

int *CppBean::getcpp_intArr() {
    return cpp_intArr;
}

void CppBean::setcpp_intArr(int *intArr, int length) {
    memcpy(cpp_intArr, intArr, length * 4);
}

然后我们需要在JNI中添加接口:

public native void passData(JNIBean bean);

然后在native-lib.h中声明,并在native-lib.cpp中实现:

extern "C" {

        ...

JNIEXPORT void JNICALL
        Java_com_hecc_jnitest_JNIManager_passData(JNIEnv *env, jobject instance, jobject bean);
}
#include "native-lib.h"
#include "cppbean.h"//包含cppbean.h头文件

        ...

JNIEXPORT void JNICALL
Java_com_hecc_jnitest_JNIManager_passData(JNIEnv *env, jobject instance, jobject bean) {

    jclass cls_bean = env->GetObjectClass(bean);//获取字节码对象

    jfieldID ids_int = env->GetStaticFieldID(cls_bean, "jb_int", "I");//获取静态字段ID对象
    jint jb_int = env->GetStaticIntField(cls_bean, ids_int);//获取相应静态字段的int值
    MCppBean.setcpp_int(jb_int);//将int值赋给CppBean中的映射值

    jfieldID id_boolean = env->GetFieldID(cls_bean, "jb_boolean", "Z");//获取非静态字段ID对象
    jboolean jb_boolean = env->GetBooleanField(bean, id_boolean);//获取相应非静态字段的boolean值
    MCppBean.setcpp_Bool(jb_boolean);//将boolean值赋给CppBean中的映射值

    jbyte jb_byte = env->GetByteField(bean, env->GetFieldID(cls_bean, "jb_byte", "B"));
    MCppBean.setcpp_byte(jb_byte);

    MCppBean.setcpp_short(env->GetShortField(bean, env->GetFieldID(cls_bean, "jb_short", "S")));

    MCppBean.setcpp_long(env->GetLongField(bean, env->GetFieldID(cls_bean, "jb_long", "J")));

    MCppBean.setcpp_float(env->GetFloatField(bean, env->GetFieldID(cls_bean, "jb_float", "F")));

    MCppBean.setcpp_double(env->GetDoubleField(bean, env->GetFieldID(cls_bean, "jb_double", "D")));

    LOGI("MCppBean中:int字段值=%d,Bool字段值=%d,Byte字段值=%d,short字段值=%hd,long字段值=%ld,float字段值=%f,double字段值=%lf",
         MCppBean.getcpp_int(), MCppBean.getcpp_Bool(), MCppBean.getcpp_byte(),
         MCppBean.getcpp_short(), MCppBean.getcpp_long(),
         MCppBean.getcpp_float(), MCppBean.getcpp_double());

    //字符串
    jfieldID ids_str = env->GetFieldID(cls_bean, "jb_str", "Ljava/lang/String;");
    jstring jb_str = (jstring) env->GetObjectField(bean, ids_str);
    char *str = (char *) env->GetStringUTFChars(jb_str, 0);
    MCppBean.setcpp_str(str);
    env->ReleaseStringUTFChars(jb_str, str);
    LOGI("MCppBean中:string字段值=%s", MCppBean.getcpp_str().c_str());

    //数组
    jfieldID ids_intArr = env->GetFieldID(cls_bean, "jb_intArr", "[I");
    jintArray jb_intArr = (jintArray) env->GetObjectField(bean, ids_intArr);
    jint *intArray = env->GetIntArrayElements(jb_intArr, NULL);
    jsize length = env->GetArrayLength(jb_intArr);
    MCppBean.setcpp_intArr(intArray, length);
    env->ReleaseIntArrayElements(jb_intArr, intArray, 0);
    for (int i = 0; i < length; i++) {
        LOGI("数组元素%d:%d", i, *(MCppBean.getcpp_intArr() + i));
    }
}

在NDK中基本数据类型转换的写法是固定的:
1.获取javabean的字节码对象
除了通过env->GetObjectClass(object对象)方法,还可通过env->FindClass(“com/hecc/jnitest/JNIManager”)获取,形参填写javabean的全类名。

2.获取字段ID对象
java中字段分为静态和非静态两种,对应的api也有两种:
jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig);
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig);
形参1:正是步骤一的字节码对象。形参2:方法名。形参3:字段描述符,简单说每个基本数据类型都有一个固定的标记作识别(参考下表)。
这里写图片描述
(注:另外数组类型的简写,则用”[“加上如表所示的对应类型的简写形式进行表示就可以了,
比如:[I 表示 int []。[L全类名; 表示类类型数组。另外,引用类型(除基本类型的数组外)的标示最后都有个”;”)

3.获取字段值
字段值
获取字段值的api也分静态和非静态(本地类型和java类型查上表):
本地基本数据类型 GetStatic(java类型)Field(jclass clazz, jfieldID fieldID);
本地基本数据类型 Get(java类型)Field(jobject obj, jfieldID fieldID);
形参1:若是静态,为字节码对象;非静态,为实例对象。形参2:正是步骤二的字段ID对象。

对于字符串和数组的获取稍微复杂点,所以单独列出。
字符串:jstring其实是_jobject的子类,所以用GetObjectField()获取jstring,而c/c++并不识别jstring类型,别担心,ndk提供了相关api—-GetStringUTFChars(jstring string, jboolean* isCopy)来转换成char *类型。
数组(int数组为例):jintArray也是_jobject的子类,同时ndk提供了相关api来获取数组首地—- GetIntArrayElements(jintArray array, jboolean* isCopy)和数组个数—-GetArrayLength(jarray array)。

在MainActivity添加如下代码,进行测试:

  ...
private void testJNI() {

     ...

        JNIBean bean = new JNIBean();
        JNIBean.jb_int = 11;
        bean.jb_boolean = false;
        bean.jb_byte = 22;
        bean.jb_short = 33;
        bean.jb_long = 30000000000L;
        bean.jb_float = 12.34f;
        bean.jb_double = 56.789d;
        bean.jb_str = "我是测试代码";
        bean.jb_intArr = new int[]{1, 23, 456, 78, 9};
        manager.passData(bean);
    }

运行,打印结果如图:
result


有人可能会有这样的需求:如何将内部类,类类型数组的数据传递给本地?
ndk中数据的转换方式是相当固定的,如果理解了上述的实例,对于类似的需要并不难实现。
首先,在JNIBean 中添加内部类和类类型数组。

public class JNIBean {

    ...

    public JNIChildBean jb_ChildBean;
    public JNIChildBean[] jb_arr_ChildBean;

    public class JNIChildBean {
        public int jcb_int;
    }
}

在native-lib中添加如下代码:

JNIEXPORT void JNICALL
Java_com_hecc_jnitest_JNIManager_passData(JNIEnv *env, jobject instance, jobject bean) {

    ...

    //内部类
    jfieldID ids_child = env->GetFieldID(cls_bean, "jb_ChildBean", "Lcom/hecc/jnitest/JNIBean$JNIChildBean;");//内部类用$符号,而不是/符号。
    jobject jb_child = env->GetObjectField(bean, ids_child);
    jclass cls_child = env->GetObjectClass(jb_child);
    jint jcb_int = env->GetIntField(jb_child, env->GetFieldID(cls_child, "jcb_int", "I"));
    LOGI("jcb_int=%d", jcb_int);

    //类类型数组
    jfieldID ids_arr_child = env->GetFieldID(cls_bean, "jb_arr_ChildBean",
                                     "[Lcom/hecc/jnitest/JNIBean$JNIChildBean;");
    jobjectArray jb_arr_child = (jobjectArray) env->GetObjectField(bean, ids_arr_child);
    jsize size = env->GetArrayLength(jb_arr_child);
    for (int i = 0; i < size; ++i) {
        jobject jab_child = env->GetObjectArrayElement(jb_arr_child, i);//获取每个数组的元素
        jint jacb_int = env->GetIntField(jab_child, 
                                env->GetFieldID(env->GetObjectClass(jab_child), "jcb_int", "I"));
        LOGI("jacb_int=%d",jacb_int);
    }
}

然后在MainActivity在添加如下代码,进行测试:

public class MainActivity extends AppCompatActivity {

  ...

    private void testJNI() {

     ...

        bean.jb_ChildBean = bean.new JNIChildBean();
        bean.jb_ChildBean.jcb_int = 1010;

        bean.jb_arr_ChildBean = new JNIBean.JNIChildBean[5];
        for (int i = 0; i < 5; i++) {
            JNIBean.JNIChildBean childBean = bean.new JNIChildBean();
            childBean.jcb_int = i + 2000;
            bean.jb_arr_ChildBean[i] = childBean;
        }

        manager.passData(bean);
    }
}

打印结果如图:
这里写图片描述


以上示例,都是以java–>c/c++的形式,实际需求中可能需要c/c++–>java或则java–>c/c++–>java的形式传递。那么现在就以java–>c/c++–>java,我打算在界面上写个按钮,点击按钮开启一个线程调用本地方法,ndk再调用c++的函数处理逻辑(这里让线程睡了3秒,模拟耗时操作),然后c++通过ndk回调java的函数,并打印日志。事件逻辑可能有点复杂,下面按照先后流程贴代码:

在MainActivity在添加如下代码:

public class MainActivity extends AppCompatActivity {

    ...

    //按钮点击事件
    public void test(View v) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                manager.test();
            }
        }).start();
    }
}

在JNIManager在添加如下代码:

public class JNIManager {

   ...

    public native void test();

    //c++调用java的函数
    public void printFromC(int sec) {
        Log.i("JNIManager", "hello from c after " + sec + "s");
    }
}

我打算先创建c++的类,并在CMakeLists.txt中配置
text.h文件:

#ifndef JNITEST_TEST_H
#define JNITEST_TEST_H

extern void runTest();

#endif

text.cpp文件:

#include "test.h"
#include <unistd.h>
#include "native-lib.h"//包含native-lib.h头文件

void runTest() {
    int sec = 3;
    ::sleep(sec); // 睡3秒
    call_printFromC(sec);//需要在native-lib.h中声明
}

这里写图片描述
配置完成后点击同步按钮。

此时在native-lib.h中声明函数:

...

extern "C" {

    ...

JNIEXPORT void JNICALL
        Java_com_hecc_jnitest_JNIManager_test(JNIEnv *env, jobject instance);
}

extern void call_printFromC(int sec);//回调函数

最后是native-lib.cpp的代码:

#include "native-lib.h"
#include "cppbean.h"
#include "test.h"//包含test.h头文件

static JavaVM *m_JavaVM;//java虚拟机
static jclass m_jcls_JNI;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *java_vm, void *reserved) {
    m_JavaVM = java_vm;

    JNIEnv *jni_env = 0;

    //获取JavaVM的JNIEnv 
    if (m_JavaVM->GetEnv((void **) (&jni_env), JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    jclass jcls_JNI = jni_env->FindClass("com/hecc/jnitest/JNIManager");
    m_jcls_JNI = (jclass) jni_env->NewGlobalRef((jobject) jcls_JNI);//全局变量,方便调用
    return JNI_VERSION_1_4;
}

...


JNIEXPORT void JNICALL
Java_com_hecc_jnitest_JNIManager_test(JNIEnv *env, jobject instance) {

    runTest();//调用test.h的方法
}

void call_printFromC(int sec) {
    JNIEnv *env;
    m_JavaVM->AttachCurrentThread(&env, NULL);//获取当前线程的JNIEnv 
    jmethodID methodID = env->GetMethodID(m_jcls_JNI, "printFromC", "(I)V");
    jobject obj_manager = env->AllocObject(m_jcls_JNI);//获取实例对象
    env->CallVoidMethod(obj_manager,methodID,sec);
}

众所周知,ndk环境下调用api离不开JNIEnv指针,但是如何在c/c++函数中获取JNIEnv呢?
答:要获取JNIEnv之前,先了解JavaVM ,它代表java的虚拟机,所有的工作都是从获取虚拟机的接口开始的。在加载动态链接库的时候,JVM会调用JNI_OnLoad(JavaVM* jvm, void* reserved)(如果定义了该函数),第一个参数会传入JavaVM指针。然后,通过JVM的AttachCurrentThread(JNIEnv** p_env, void* thr_args)的函数获取JNIEnv。
获取到JNIEnv后,想要调用其他api就方便了。对于ndk回调java函数的格式也是相当固定的:

1.获取函数ID对象
java中函数分为静态和非静态,对应的api:
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig);
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig);
形参1:字节码对象。形参2:方法名。形参3:函数描述符,具体格式是(形参类型)返回值类型,另外形参可以写任意个数,没有返回值用V(表示void型)表示。

2.执行回调函数
也分静态和非静态:
本地基本数据类型 CallStatic(返回值类型)Method(jclass clazz, jmethodID methodID, …)
本地基本数据类型 Call(返回值类型)Method(jobject obj, jmethodID methodID, …)
形参1:若是静态,为字节码对象;非静态,为实例对象。形参2:正是步骤一的函数ID对象。其他形参:这里填写的就是java函数对应的参数值,可以写任意个,没有就不用写。

回调java函数完成,点击test按钮,运行查看结果(等待3秒):
这里写图片描述


这个时候,来了一个变态需求,JeanBean里有一个静态的int型二维数组,通过JNI给其赋值。

public class JNIBean {
...
    public static int[][] jb_intArr2;
}

来看看具体的实现过程:

...

 void call_printFromC(int sec) {
    int pInt[2][3] = {{1, 2, 3}, {4, 5, 6}};//模拟数据

    JNIEnv *env;
    m_JavaVM->AttachCurrentThread(&env, NULL);

    processJNIBean(env, pInt);//处理数据

    jmethodID methodID = env->GetMethodID(m_jcls_JNI, "printFromC", "(I)V");
    jobject newManager = env->AllocObject(m_jcls_JNI);
    env->CallVoidMethod(newManager, methodID, sec);
}

void processJNIBean(JNIEnv *env, int pInt[2][3]) {
    jclass cls_JB = env->FindClass("com/hecc/jnitest/JNIBean");
    jobjectArray _infoArr2 = env->NewObjectArray(2, env->FindClass("[I"), NULL);//创建一个长度为2,元素为int[]的jobjectArray 
    for (int i = 0; i < 2; ++i) {
        jintArray _infoArr = env->NewIntArray(3);//创建长度为3的jintArray 数组
        env->SetIntArrayRegion(_infoArr, 0, 3, pInt[i]);//给jintArray赋值 
        env->SetObjectArrayElement(_infoArr2, i, _infoArr);//给jobjectArray赋值
    }
    jfieldID id_infoArr2 = env->GetStaticFieldID(cls_JB, "jb_intArr2", "[[I"); //静态二维数组ID对象
    env->SetStaticObjectField(cls_JB, id_infoArr2, _infoArr2);//设置java端数组数据
}

千万不要忘了在native-lib.h声明函数:

static void processJNIBean(JNIEnv *env,int pInt[2][3]);

然后打印数组元素:

public class JNIManager {

   ...

    //c++调用java的函数
    public void printFromC(int sec) {

        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 3; j++) {
                Log.i("JNIManager","元素["+i+"]["+j+"]="+JNIBean.jb_intArr2[i][j]);
            }
        }
    }
}

这里写图片描述
赋值成功,和模拟的数据一样。


如果对ndk 的api不熟悉,可以查看官方文档:

http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html

吾日三省吾身:
代码写完了,功能实现了,稳定性够不够?网络是不是有优化的空间?线程是否安全?内存耗费是否可以更小一些,释放干净没有?各种平台的适配做得够不够好?

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值