天天看点

JNI和NDK编程-JNI的开发流程1.在Java中声明native方法。2.使用javah命令导出JNI的头文件3.实现JNI方法

Java JNI的本意时Java Native Interface(Java本地接口),它是为了方便Java调用C、C++等本地代码所封装的一层接口。我们都知道,Java的有点是跨平台,但是作为有点的同事,其在和本地交互的时候出现了短板。Java的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成,于是Java提供了JNI专门用于和本地代码交互,这一就增强Java语言的本地交互能力。通过Java JNI,用户可以调用C、C++所编写的本地代码。

NDK是Android所提供的一个工具集合,通过NDK可以在Android中更加方便地通过JNI来访问本地代码,比如C或者C++。NDK还提供了交叉编译器,开发人员只需要简单地修改mk文件就可以生成特定CPU平台地动态库。使用NDK有如下好处: (1)提高代码的安全性。由于so库反编译比较困难,因此NDK提高了Android程序的安全性。 (2)可以很方便地使用目前已有地C/C++实现的动态库可以很方便地在其他平台上使用。 (3)便于平台间地移植。通过 C/C++实现地动态库可以很方便地在其他平台使用。 (4)提高程序在某些特定情形下地执行效率,但并不能明显提示Android程序的性能。

由于JNI和NDK比较适合,JNI和NDK开发用到的动态库的格式是以.so为后缀的文件,下面统一简称为so库。另外,由于JNI和NDK主要用于底层和嵌入式开发,在Android的应用开发中使用较少,加上他们本身更加侧重于C和C++方面的变成,因此本章只介绍JNI和NDK的基础知识,其他更加深入的知识点如果感兴趣的话可以专门查看JNI和NDK的书籍。

下面进入本节正题,JNI开发流程有如下几部,首先需要在Java中声明native方法,接着用C或者C++实现native方法,然后就可以编译运行了。

1.在Java中声明native方法。

创建一个类,这里叫做JniTest.java,代码如下所示。

public class JniTest {
    static {
        System.loadLibrary("jin-test");
    }

    public native String get();
    public native void set(String str);
}
           

可以看到上面的代码中,声明了两个native方法:get和set(String),这两个就是需要在JNI中实现的方法。在JniTest的头部有一个加载动态库的过程,其中jni_test是so库的标识,so库完整的名称为libjni-test.so,这是加载so库的规范。

2.使用javah命令导出JNI的头文件

我这里使用的是Android Studio,生成.h文件的方法如下:

打开AS的terminal,进入到main/java路径,键入以下命令生成.h文件

javah study.chenj.testlibrary.JniTest
           

在AS中可以看到生成的.h文件“study_chenj_testlibrary_JniTest.h”,内容如下所示。

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

#ifndef _Included_study_chenj_testlibrary_JniTest
#define _Included_study_chenj_testlibrary_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     study_chenj_testlibrary_JniTest
 * Method:    get
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_study_chenj_testlibrary_JniTest_get
  (JNIEnv *, jclass);

/*
 * Class:     study_chenj_testlibrary_JniTest
 * Method:    set
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_study_chenj_testlibrary_JniTest_set
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif
           

上面的代码需要做一下说明,首先函数名的格式遵循如下规则:Java_包名_类名_方法名。比如JniTest中的set方法,到这里就变成了JNIEXPORT void JNICALL Java_study_chenj_testlibrary_JniTest_set(JNIEnv *, jclass, jstring),其中study.chenj.testlibrary是包名,JniTest是类名,jstring是代表的是set方法的String类型的参数。关于java和JNI的数据类型直接的对应关系会在下面进行介绍,这里只需要知道java的String对应与JNI的jstring即可。JNIEXPORT、JNICALL、JNIEnv和jonject都是JNI标准中所定义的类型或者宏,他们的含义如下: (1)JNIEnv*:表示一个指向JNI环境的指针,可以通过它来访问JNI提供的接口方法; (2)jobject:表示java对象中的this; (3)JNIEXPORT和JNICALL:它是JNI中所定义的宏,可以在jni.h这个头文件中查找到。

下面的宏定义是必需的,它指定extern “C”内部的函数采用C语言的命名风格来编译。否则当JNI采用C++来实现时,由于C和C++编译过程中对函数的命名风格不同,这将导致JNI在链接时无法根据函数名查找到具体函数,那么JNI调用就无法完成。更多的细节实际上是有关于C和C++编译时的一些问题,这里就不再展开了。

#ifdef __cplusplus
extern "C" {
#endif
           

3.实现JNI方法

JNI方法是指Java中声明的native方法,这里可以选择用C++或者C来实现,他们的实现过程是类似的,只有少量的区别,下面是分别用C++和C来实现JNI方法。首先,在工程的主目录下创建一个子项目,名称随意,这里选择jni作为目录的名称,然后将之前通过javah生成的头名叫 “study_chenj_testlibrary_JniTest.h”复制到jni目录下,接着创建test.cpp和test.c两个文件,他们的实现如下所示。

//test.cpp
#include "study_chenj_testlibrary_JniTest.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_study_chenj_testlibrary_JniTest_get(JNIEnv *, jobject thiz){
    printf("invoke get in c++ \n");
    return env->NewStringUTF("Hello form JNI!");
}

JNIEXPORT void JNICALL Java_study_chenj_testlibrary_JniTest_set(JNIEnv *, jobject thiz, jclass, jstring string){
    printf("invoke set in c++ \n");
    char *str = (char *)env->GetStringUTFChars(string,NULL);
    printf("%s",str);
    env->ReleaseStringUTFChars(string, str);
}

//test.c
#include "study_chenj_testlibrary_JniTest.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_study_chenj_testlibrary_JniTest_get(JNIEnv *, jobject thiz){
     printf("invoke get in c \n");
     return (*env)->NewStringUTF(env, "Hello form JNI!");
 }
 
 JNIEXPORT void JNICALL Java_study_chenj_testlibrary_JniTest_set(JNIEnv *, jobject thiz, jclass, jstring){
     printf("invoke set in c \n");
     char *str = (char *)(*env)->GetStringUTFChars(env, string,NULL);
     printf("%s",str);
     (*env)->ReleaseStringUTFChars(env, string, str);
 }
           

可以发现,test.cpp和test.c的实现很类似,但是他们对env的操作方式有所不同,因此用C++和C来实现JNI方法,他们的区别主要集中在对env的操作上,其他都是类似的,如下所示。

C++:env->NewStringUTF("Hello form JNI!")
C:  (*env)->NewStringUTF(env, "Hello form JNI!")