美文网首页
JNI 编程

JNI 编程

作者: 有没有口罩给我一个 | 来源:发表于2020-05-17 12:12 被阅读0次
    jni.png

    1、什么是JNI? 为什么需要JNI?

    • JNI是一种本地编程接口。它允许运行在JAVA虚拟机中的JAVA代码和用其他编程语言,诸如C语言、C++、汇编,写的应用和库之间的交互操作,Java调用C/C++在Java语言里面本来就有的,并非Android自创的。JNI就是Java调用C++的规范。当然,一般的Java程序使用的JNI标准可能和android不一样,Android的JNI更简单。
    • 因为在我们的实际项目需求中,需要Java代码与C/C++代码进行交互,那么通过JNI可以实现Java代码与C/C++代码的交互。

    2、如何使用JNI编程?

    2.1 、JNI的命名规则
    extern "C" JNIEXPORT jstring JNICALL Java_com_hellondk_NDKTool_test(JNIEnv *env, jclass clazz)
    
    • jstring 是返回值类型;
    • Java_com_hellondk 是包名前缀必须是Java_开头;
    • NDKTool是类名,即声明Native方法的类名;
    • test是方法名;
    • JNIEnv方法参数是必须加的,表示JNI环境;
    • jclass 方法参数是必须加的,当然这个参数如果声明的Native方法是非静态的那么这个参数就是jobject,这个参数表示在本地方法中声明的对象引用,如果是静态方法就是Class对象;
    • 其中JNIEXPORTJNICALL是JNI固定保留的关键字不要修改;
    2.2、 如何实现JNI?

    2.2.1、JNI开发流程的步骤:

    • 在Java中先声明一个native方法;
    • 注册Native方法,即使用JNI实现在Java中声明的方法,有两种实现方式;
    • 编译动态库,即在CmakeLists.txt文件中配置编译生成动态库;

    实现Hello JNI

    在Java中先声明一个native方法;
    public class NDKTool {
    static {
        System.loadLibrary("native-lib");
    }
    public static native String test();
    }
    
    注册Native方法,即使用JNI实现在Java中声明的方法,有两种实现方式;
    extern "C" JNIEXPORT jstring JNICALL Java_com_hellondk_myapplication_NDKTool_test(JNIEnv *env, jclass clazz) {
    return env->NewStringUTF("Java_com_hellondk_myapplication_NDKTool_test");
    }
    
    编译动态库,即在CmakeLists.txt文件中配置编译生成动态库;
    cmake_minimum_required(VERSION 3.4.1)
    include_directories(${CMAKE_SOURCE_DIR}/inc)
    add_library(native-lib SHARED native-lib.cpp)
    find_library(log-lib log)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}")
    target_link_libraries( native-lib)
    

    3、JNI数据类型

    Java类 本地类型 描述
    boolean jboolean C/C++8位整型
    byte jbyte C/C++带符号的8位整型
    char jchar C/C++无符号的16位整型
    short jshort C/C++带符号的16位整型
    int jint C/C++带符号的32位整型
    long jlong C/C++带符号的64位整型
    float jfloat C/C++32位浮点型
    double jdouble C/C++64位浮点型
    Object jobject 任何Java对象,或者没有对应java类型的对象
    Class jclass Class对象
    String jstring 字符串对象
    Object[] jobjectArray 任何对象的数组
    boolean[] jbooleanArray 布尔型数组
    byte[] jbyteArray 比特型数组
    char[] jcharArray 字符型数组
    short[] jshortArray 短整型数组
    int[] jintArray 整型数组
    long[] jlongArray 长整型数组
    float[] jfloatArray 浮点型数组
    double[] jdoubleArray 双浮点型数组

    4、在JNI反射调

    4.1、基本数据类型的签名采用一系列大写字母来表示, 如下表所示:
    Java类型 签名
    boolean Z
    short S
    float F
    byte B
    int I
    double D
    char C
    long J
    void V
    引用类型 L + 全限定名 + ;
    数组 [+类型签名

    在C/C++中反射创建Java的对象,调用Java的方法

    #NDKTool
    class NDKTool{
       public native void cReflectJava(ReflectBean bean);
    }
    
    #JNI
    extern "C" JNIEXPORT void JNICALL Java_com_helondk_myapplication_NDKTool_cReflectJava(
        JNIEnv *env,
        jobject thiz, //非静态方法,就是方法定义本身对象NDKTool的对象
        jobject bean) {
    
    
    //NDKTool
    jclass pJclass = env->GetObjectClass(thiz);
    jmethodID methodId = env->GetMethodID(pJclass, "printf", "()V");
    env->CallVoidMethod(thiz, methodId);
    
    jclass beanCls = env->GetObjectClass(bean);
    
    
    //反射setName方法
    jmethodID setMethodId = env->GetMethodID(beanCls, "setName", "(Ljava/lang/String;)V");
    jstring pJstring = env->NewStringUTF("999999");
    //调用setName方法
    env->CallVoidMethod(bean, setMethodId, pJstring);
    
    jmethodID getMethodId = env->GetMethodID(beanCls, "getName", "()Ljava/lang/String;");
    //调用getName方法
    jobject getMethodRes = env->CallObjectMethod(bean, getMethodId);
    //将jbject转换为jstring
    jstring pJobject = static_cast<jstring>(getMethodRes);
    
    const char *utfChars = env->GetStringUTFChars(pJobject, NULL);
    LOGE("get返回值:%s", utfChars);
    
    
    // 静态方法
    jmethodID staticMethodId = env->GetStaticMethodID(beanCls, "setAddress",
                                                      "()Ljava/lang/String;");
    jobject staticMethodVal = env->CallStaticObjectMethod(beanCls, staticMethodId);
    jstring staticMethodValStr = static_cast<jstring>(staticMethodVal);
    const char *chars = env->GetStringUTFChars(staticMethodValStr, NULL);
    LOGE("静态方法返回:%s", chars)
    
    
    //对象Field
    jfieldID pJfieldId = env->GetFieldID(beanCls, "name", "Ljava/lang/String;");
    jstring filedStr = env->NewStringUTF("reflect filed");
    env->SetObjectField(bean, pJfieldId, filedStr);
    
    //静态Filed
    jfieldID staticFieldId = env->GetStaticFieldID(beanCls, "address", "Ljava/lang/String;");
    jstring staticFiledStr = env->NewStringUTF("static reflect filed");
    env->SetStaticObjectField(beanCls, staticFieldId, staticFiledStr);
    
    
    env->DeleteLocalRef(beanCls);
    env->DeleteLocalRef(pJclass);
    env->DeleteLocalRef(pJstring);
    }
    

    一般在JNI中,如果你想调用Java的方法或者修改Java对象的属性值,你只能通过反射,比如:音频播放进度回调;

    5、JNI引用

    在 JNI 规范中定义了三种引用:局部引用(Local Reference)、全局引用(Global Reference)、弱全局引用(Weak Global Reference)。

    5.1 、局部引用

    大多数JNI函数会创建局部引用。NewObject/FindClass/NewStringUTF 等等都是局部引用。局部引用只有在创建它的本地方法返回前有效,本地方法返回后,局部引用会被自动释放。因此无法跨线程、跨方法使用。

    5.1.1、释放一个局部引用有两种方式:
    • 本地方法执行完毕后VM自动释放;
    • 通过DeleteLocalRef手动释放;

    VM会自动释放局部引用,为什么还需要手动释放呢?因为局部引用会阻止它所引用的对象被GC回收。

    5.2 、 全局引用

    全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效 。由 NewGlobalRef 函数创建。

    5.3、弱全局引用
    • 与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止GC回收它所指向的VM内部的对象 。
    • 在对Class进行弱引用是非常合适(FindClass),因为Class一般直到程序进程结束才会卸载。
    • 在使用弱引用时,必须先检查缓存过的弱引用是指向活动的对象,还是指向一个已经被GC的对象;

    6、JNI中的JNI_OnLoad方法

    当我们调用System.loadLibrary()函数时, 内部就会去查找so中的 JNI_OnLoad 函数,如果存在此函数则调用。JNI_OnLoad会,告诉 VM 此 native 组件使用的 JNI 的版本。对应了Java版本,android中只支持JNI_VERSION_1_2 、JNI_VERSION_1_4、JNI_VERSION_1_6。

    jint JNI_OnLoad(JavaVM* vm, void* reserved){
    return JNI_VERSION_1_4;
    }
    

    7、动态注册

    在此之前我们一直在jni中使用的Java_com_hellondk_NDKTool_test来进行与java方法的匹配,这种方式我们称之为静态注册。而动态注册则意味着方法名可以不用这么长了,在android aosp源码中就大量的使用了动态注册的形式。

       #Java
      public static native void dynomicRegisterTest(int a);
      #C++
    JavaVM *_vm;
    //需要动态注册的方法数组
    static const JNINativeMethod methods[] = {
        {"dynomicRegisterTest",  "(I)V", (void *) dynomicRegisterTest},
        {"dynomicRegisterTest2", "(I)I", (int *) dynomicRegisterTest2},
        {"dynomicRegisterTest3", "(I)I", (int *) dynomicRegisterTest3}
    };
    //需要动态注册native方法的类名
    static const char *className = "com/youbesun/myapplication/NDKTool";
    
    JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    _vm = vm;
    
    //获得JNIEnv(线程相关的)
    JNIEnv *env = JNI_OK;
    int r = vm->GetEnv((void **) &env, JNI_VERSION_1_6);
    //小于0失败,等于0成功
    if (r != JNI_OK) {
        return JNI_ERR;
    }
    jclass registerClass = env->FindClass(className);
    
    //注册Native
    //参数3:方法数量
    env->RegisterNatives(registerClass, methods, sizeof(methods) / sizeof(JNINativeMethod));
    
    
    env->DeleteLocalRef(registerClass);
    
    return JNI_VERSION_1_6;
    }
    

    8、native线程调用Java

    native调用java需要使用JNIEnv这个结构体,而JNIEnv是由Jvm传入与线程相关的变量。
    但是可以通过JavaVM的AttachCurrentThread方法来获取到当前线程中的JNIEnv指针。

    //线程================

    struct Context {
    jobject cls;//注意你要穿全局变量
    };
    void *threadTask(void *args) {
    JNIEnv *env;
    jint i = _vm->AttachCurrentThread(&env, 0);
    if (i != 0) {
        return 0;
    }
    Context *context = static_cast<Context *>(args);
    jclass cls = env->GetObjectClass(context->cls);
    
    jmethodID methodId = env->GetMethodID(cls, "printf", "()V");
    env->CallVoidMethod(context->cls, methodId);
    delete (context);
    context = 0;
     _vm->DetachCurrentThread();
    return 0;
    }
    
    extern "C" JNIEXPORT void JNICALL Java_com_hellondk_NDKTool_testThread(JNIEnv *env, jobject cls) {
    pthread_t pid;//线程Id,句柄
    
    
    Context *context = new Context;
    context->cls = env->NewGlobalRef(cls);//注意你要穿全局变量
    //启动线程
    pthread_create(&pid, 0, threadTask, context);
    }
    

    总结

    • 对于JNI特别是做音视频开发的,JNI是必不可少的,因为需要和C/C++交互,比如你想用FFmpeg开发音视频,但是FFmpeg只提供了C/C++代码,所以这时候你就需要去编译FFmpeg,使用FFmpeg生成的动态库导入我们的项目,然后在使用JNI去调用FFmpeg提供的功能,所以JNI只启动类似于中介者的方式去组织JavaFFmpeg交互;
    • 最后JNIAndroid NDK是没有关系的,Android NDK 就是一套工具集合,允许你使用C/C++语言来实现应用程序的部分功能。

    参考文章

    JNI/NDK 开发指南

    相关文章

      网友评论

          本文标题:JNI 编程

          本文链接:https://www.haomeiwen.com/subject/rbvjohtx.html