JNI基础(二)

JNI 开发流程主要分为以下 6 步:

  • 编写声明了 native 方法的 Java 类
  • 将 Java 源代码编译成 class 字节码文件
  • 用 javah -jni 命令生成.h头文件(javah 是 jdk 自带的一个命令,-jni 参数表示将 class 中用native 声明的函数生成 JNI 规则的函数)
  • 用本地代码实现.h头文件中的函数
  • 将本地代码编译成动态库(Windows:\*.dll,linux/unix:\*.so,mac os x:\*.jnilib)
  • 拷贝动态库至 java.library.path 本地库搜索目录下,并运行 Java 程序

通过上面的介绍,相信大家对 JNI 及开发流程有了一个整体的认识,下面通过一个 HelloWorld 的示例,再深入了解 JNI 开发的各个环节及注意事项。

HelloWorld

注意:这个案例用命令行的方式介绍开发流程,这样大家对 JNI 开发流程的印象会更加深刻。

第一步,新建一个 JniBridge.java 源文件

package com.sample.jni;
public class JniBridge {

    public static String callJava(String param){
        System.out.println("java:" + param);
        return "receive call java";
    }

    public native static String callNative(String param);
}

在spring的自定义类文件静态调用

@Component
public class ApplicationStartedListener implements ApplicationListener<ApplicationStartedEvent> {
    static {
        System.load("/root/libJniBridge.so");
    }
    @Override
    public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {

        String text = JniBridge.callNative("call native");
        System.out.println("java:" + text);
    }
}

第二步,用idea打出springboot对应项目的jar包

第三步,用 javah -jni 命令,根据class字节码文件生成.h头文件(-jni 参数是可选的)

javah -classpath ./  -jni com.sample.jni.JniBridge

第四步,用本地代码实现.h头文件中的函数

com.sample.jni.JniBridge.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com__sample_jni_JniBridge */

#ifndef _Included_com__sample_jni_JniBridge
#define _Included_com__sample_jni_JniBridge
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com__sample_jni_JniBridge
 * Method:    callNative
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com__sample_jni_JniBridge_callNative
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

com_sample_jni_JniBridge.c

#include "com__sample_jni_JniBridge.h"
#include <pthread.h>

pthread_t a_thread;
static JavaVM *javaVM;
static jmethodID global_mid = NULL;
static jclass global_ref = NULL;

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
    printf("JNI_OnLoad startup ..  \\n");
    javaVM = vm;
    JNIEnv *env = NULL;
    jint result;

    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) == JNI_OK) {
        printf("Catch JNI_VERSION_1_6  \\n");
        result = JNI_VERSION_1_6;
    }
    else if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) == JNI_OK) {
        printf("Catch JNI_VERSION_1_4  \\n");
        result = JNI_VERSION_1_4;
    }
    else {
        printf("Catch JNI_VERSION_1_2  \\n");
        result = JNI_VERSION_1_2;
    }

        jclass classLoaderClass = (*env)->FindClass(env, "com/hexin/ifind/sample/jni/JniBridge");
        if (classLoaderClass) {
            printf("load has find JniBridge  asd\\n");
        global_ref = (*env)->NewGlobalRef(env, classLoaderClass);
                global_mid = (*env)->GetStaticMethodID(env, classLoaderClass,"callJava", "()Ljava/lang/String;");
                if( global_mid == NULL ){
                    printf("load not find global_mid  \\n");
                }else{
                    printf("load find global_mid  \\n");
                }

        }else{
            printf("load not find JniBridge  \\n");
        }

    return result;
}

void *thread_function(){
  printf("thread_function is running \\n");
  while(1){
    sleep(3);
    printf("thread_function is running \\n");

        int isAttached = 0;
        JNIEnv* pEnv;

        if ((*javaVM)->GetEnv(javaVM,(void**)&pEnv, JNI_VERSION_1_6) == JNI_OK){
            printf("JNI_VERSION is 1_6  \\n");
        }
        else if ((*javaVM)->GetEnv(javaVM,(void**)&pEnv, JNI_VERSION_1_4) == JNI_OK){
            printf("JNI_VERSION is 1_4  \\n");
        }
        else if ((*javaVM)->GetEnv(javaVM,(void**)&pEnv, JNI_VERSION_1_4) == JNI_OK)
        {
            printf("JNI_VERSION is 1_2  \\n");
        }
        else if ((*javaVM)->AttachCurrentThread(javaVM,(void**)&pEnv, NULL) == JNI_OK)
        {
            printf("thread_function is system load \\n");
            isAttached = 1;
        }else{
            printf("javaVM->Env Error \\n");
        }

        jmethodID methodId = (*pEnv)->GetStaticMethodID(pEnv,global_ref, "callJava", "()Ljava/lang/String;");
        if (methodId == NULL)
        {
            const char msg;
            (*pEnv)->ThrowNew(pEnv, global_ref,msg);
            printf("thread_function is 3 \\n");
                return -3;
        }

        const char *c_str = NULL;
         char buff[128] = {0};
         sprintf(buff, "Hello world%s \\n", c_str);
         jstring strDataContent = (*pEnv)->NewStringUTF(pEnv, buff);
        (*pEnv)->CallStaticVoidMethod(pEnv,global_ref, methodId, strDataContent);

        if (isAttached)
        {
                (*javaVM)->DetachCurrentThread(javaVM);
        }


  }
}

JNIEXPORT jstring JNICALL
Java_com__sample_jni_JniBridge_callNative(JNIEnv *env, jobject j_object, jstring j_str) {
   long status  = (*env)->GetJavaVM(env,&javaVM);


  int res;
  res = pthread_create(&a_thread, NULL, thread_function, NULL);

  const char *c_str = NULL;

  c_str = (*env)->GetStringUTFChars(env, j_str, NULL);
  printf("Native: %s \\n", c_str);

  char buff[128] = {0};
  sprintf(buff, "Hello world %s \\n", c_str);
  return (*env)->NewStringUTF(env, buff);
}

第五步,将 C/C++ 代码编译成本地动态库文件动态库文件名命名规则:lib+动态库文件名+后缀(操作系统不一样,后缀名也不一样)如:

  • Mac OS X : libJniBridge.jnilib
  • Windows :libJniBridge.dll(不需要 lib 前缀)
  • Linux/Unix:libJniBridge.so(案例实际)

    c编译生成so库
    gcc -I /usr/lib/jvm/default-jvm/include -I /usr/lib/jvm/default-jvm/include/linux/ -fPIC -shared com_sample_jni_JniBridge.c -o libJniBridge.so

    c++编译生成so库
    g++ -I /usr/lib/jvm/default-jvm/include -I /usr/lib/jvm/default-jvm/include/linux/ -fPIC -shared JniBridge.cpp -lpthread -std=c++11 -o libJniBridge.so

过程中遇到在子线程FindClass失败
 有时候会在子线程去调用Java类,但是在我们创建的子线程(通过pthread_create创建)中调用FindClass查找非系统类时会失败(查找系统类不会失败),返回值为NULL,为什么尼?这是因为通过AttachCurrentThread附加到虚拟机的线程在查找类时只会通过系统类加载器进行查找,不会通过应用类加载器进行查找,因此可以加载系统类,但是不能加载非系统类,如自己在java层定义的类会返回NULL。
 

参考:

JNI 提示  |  Android NDK  |  Android Developers

使用内存性能分析器查看应用的内存使用情况  |  Android 开发者  |  Android Developers

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值