JNI

作者: 78848d676612 | 来源:发表于2016-12-18 12:08 被阅读55次

    What is JNI

    JNI是Java Native Interface的缩写,主要是提供了一系列API,让你能在其它语言中写Java。

    What JNI can bring us

    JNI最大的好处就是,额,Java你懂的,跑在JVM里面,虽然有着一处编译,到处运行的优势(,方便啊),但是它的效率。。。至少相对于c和C艹来说,比较低下,而且,正是由于这个能一处编译,到处运行的原因,Java极容易被反编译。Java中一般用的加密方式就是混淆了,然而其实并没有太大的作用。你还是开源吧。。。因为不开源也会被反编译的。。。
    PS:并没有贬低Java的意思,个人还是挺喜欢用Java的

    然后,相反的,JNI由于是用C或者C艹写,效率很高,可以用来处理一些底层的东西,比如解码或者TCP/IP有关的。编译过后跟C(艹)编译的结果是一样的,在Android里面是.so文件。然后,因为是C(艹),所以需要针对不同的平台,不同的处理器进行编译。所以,使用JNI,你需要在编译的时候生成许多个平台的版本,否则,Java跨平台这个优点相当于直接被废了。还有就是JNI的调试会非常蛋疼。

    How to use JNI

    Hello World

    我用的Android Studio,有各种一键生成(x),要看手撸的话,网上应该能搜到,本文主要是介绍那些遇到的坑。

    AS生成的main.cpp长这样:

    #include <jni.h>
    #include <string>
    
    extern "C"
    jstring
    Java_com_helloworld_jnidemo_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    

    分析一下:

    • 几个include,其中jni.h是JNI必需的,其他的可以添加C(艹)中的,比如stdio.h什么的
    • extern C,这个我也不是特别理解,自我修养里面说是声明为C语言,然而删掉过后就炸了
    • jstring,返回值类型
    • Java_com_helloworld_jnidemo_MainActivity_stringFromJNI,Java_包名类名方法名,这是函数声明的规范
    • JNIEnv *env, jobject /* this */,JNIEnv里面有巨量的函数,后面就知道了,jobject就是this
    • std::string hello = "Hello from C++";,C艹
    • env->NewStringUTF(hello.c_str()),这儿就出现了env的其中一个函数,这个函数会经常在后面用到,char*转String,没错,他们不一样!

    然后我自己写了一个HelloWordl和求和的函数:

    extern "C"
    jstring
    Java_com_helloworld_jnidemo_MainActivity_helloworld(JNIEnv *env, jobject /* this */) {
        return env->NewStringUTF("Hello World");
    }
    
    extern "C"
    jint
    Java_com_helloworld_jnidemo_MainActivity_sum(JNIEnv *env, jobject /* this */, jint a, jint b) {
        return a + b;
    }
    

    Java中该这样写:

    static {
        System.loadLibrary("native-lib");
    }
    
    public native String stringFromJNI();
    
    public native String helloworld();
    

    其中,System.loadLibrary("native-lib");这句是加载库,static语句块中的内容只会被执行一次。native-lib为库的名称,声明方法时使用native关键字。

    CMakeLists.txt:

    add_library( 
                native-lib
                SHARED
                src/main/cpp/native-lib.cpp )
    find_library( 
                log-lib
                log )
    target_link_libraries(
                        native-lib
                        ${log-lib} )
    

    其中,native-lib可以随便改,对应System.loadLibrary("native-lib");里面的。但是有个玄学问题,不能改成test。。。被坑了。。。
    src/main/cpp/native-lib.cpp里面的文件名可以随便改,只要与你写的文件对应。

    好的,JNI入门了的样子。

    Learn More

    写出来了Hello World,该继续深入研究了。在继续之前,我们还应该了解一下jstringjint这些是啥,这儿有个表,展示了JNI和Java里面的属性的关系:

    • jint --> int
    • jbyte --> byte
    • jshort --> short
    • jlong --> long
    • jfloat --> float
    • jdouble --> double
    • jchar --> char
    • jboolean --> boolean
    • jclass --> java.lang.Class
    • jstring --> java.lang.String
    • jarray --> Array
    • jxxxArray --> xxx[]
    • jobject --> Object
    • ...

    注意最后一个,一切皆为对象。

    使用JNI,你应该实现Java的基本功能:

    • new对象
    • call方法
    • 获取属性

    学会了以上三个操作,就可以用JNI代替Java中70%以上的操作了。让我们一个一个来看。

    new对象 & Call方法

    没错,new对象就是通过调用构造方法实现的。

    extern "C"
    jobject
    Java_com_helloworld_asdf_MainActivity_newObject(JNIEnv *env, jobject /* this */) {
        jclass clazz = env->FindClass("java/lang/Object");
        jmethodID init = env->GetMethodID(clazz, "<init>", "()V");
        jobject result = env->NewObject(clazz, init);
        return result;
    }
    

    步骤:

    • 找到class,用/代替.,FindClass的参数为所在包名
    • 找到对应构造方法
    • 调用newObject,传入class和构造方法id。

    再看看一般的方法调用:

    extern "C"
    jint
    Java_com_helloworld_asdf_MainActivity_stringLen(JNIEnv *env, jobject /* this */, jstring str) {
        jclass clazz = env->GetObjectClass(str);
        jmethodID lenId = env->GetMethodID(clazz, "length", "()I");
        jint result = env->CallIntMethod(str, lenId);
        return result;
    }
    

    GetObjectClass可以直接从object中拿到class。

    调用方法用CallxxxMethod,xxx为返回值类型。CallxxxMethod的第一个参数是jobject,不是jclass,这个与NewObject不同。前面有jxxxArray,然而并没有CallxxxArrayMethod哎,该怎么办呢?一切都是对象,用CallObjectMethod再强转就可以了。
    比如:

    extern "C"
    jstring
    Java_com_helloworld_asdf_MainActivity_toString(JNIEnv *env, jobject /* this */, jobject object) {
        jclass clazz = env->GetObjectClass(object);
        jmethodID lenId = env->GetMethodID(clazz, "toString", "()Ljava/lang/String;");
        jstring result = (jstring) env->CallObjectMethod(object, lenId);
        return result;
    }
    

    方法签名:
    简直有毒,反人类

    • construction --> <init>
    • void --> V
    • boolean --> Z
    • byte --> B
    • char --> C
    • short --> S
    • int --> I
    • long --> J
    • float --> F
    • double --> D
    • x[] --> [x
    • x[][] --> [[x
    • java.lang.String --> L/java/lang/String;

    总结一下:

    • 每个基本类型都有对应的签名,基本法
    • 数组用[
    • 构造方法规定为<init>
    • 其它类为L类;,注意:分号不能丢,分号不能丢,分号不能丢

    获取Field

    extern "C"
    jint
    Java_com_helloworld_asdf_MainActivity_getX(JNIEnv *env, jobject /* this */, jobject test) {
        jclass clazz = env->GetObjectClass(test);
        jfieldID lenId = env->GetFieldID(clazz, "x", "I");
        jint result = env->GetIntField(test, lenId);
        return result;
    }
    

    static

    static的属性和方法与普通的有一些区别,例如CallStaticObjectMethod的第一个参数是jclass。这些在熟悉了上面的操作过后都没有太大的问题了。

    分享一点经验

    • 一切都是object
    • Java里的String和C(艹)里的是不一样的,要记得NewStringUTF,被坑过
    • L/java/lang/String;
    • java/util/Listjava/util/ArrayList是不一样的。。。要看清方法的参数。。。

    相关文章

      网友评论

          本文标题:JNI

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