本地接口JNI的使用分析我们知道,在AndroidFramework中,其是由基于Java语言的Java层和基于CC++语言的CC++层所组成,那么他们之间是怎样
本地接口JNI的使用分析
我们知道,在Android Framework中,其是由基于Java语言的Java层和基于C/C++语言的C/C++层所组成,那么他们之间是怎样沟通的那?在Android中,其提供了一种中间的媒介或是桥梁,能很好的将Java层和C/C++层联系起来,使得他们互相协调,共同完成功能任务。在这两层之间充当桥梁的即是JNI了,我们称之为“Java本地接口”(Java Native Interface),它允许Java代码编写的应用程序能够与使用C/C++编写生成的库(.so/.dll)进行交互操作。
这就是本篇文章要介绍的JNI本地接口了,同时,在这里我会介绍下由谷歌提供的NDK工具,它是用来快速编写基于JNI机制的应用的一套工具集。好了,下面逐一介绍下。
JNI的原理:
如上图所示,JNI的工作机制是这样的,当我们准备好了Java的本地方法和对应的函数原型之后,Java虚拟机在加载动态库的时候先检查动态库中是否含有与本地方法同签名的方法,如果有的话,其会将Java本地方法和对应的函数原型进行映射,这样我们就可以在JNI中调用本地方法来访问底层驱动了。如果没有的话,则会抛出异常,停止工作。
JNI的使用:
接下来,介绍下JNI的使用,它的使用分为以下几个步骤:
1、编写一个Java本地方法
2、编译上面Java本地方法
A、使用Java编译器 即使用命令javac来编译 生成对应的class文件
B、运行当前的项目 会自动生成对应的class文件
依个人兴趣 选择即可 我使用的第二种 因为方便
3、生成C/C++头文件
在这里使用Java编译器命令javah来生成对应上面的class文件的C/C++头文件
我的系统是windows 所以可以cd到class文件的目录 使用javah编译生成需求的头文件
4、编写C/C++代码
A、将C/C++头文件代码复制到对应名称的.c文件(这里使用C编写)
B、按照头文件中的规则编写实现文件即可
5、生成动态共享库(.so)
A、使用C/C++开发工具 如visual c++ 或visual studio等工具来生成库文件
B、我们也可以使用辅助工具Cygwin来编译生成库文件
6、运行Android程序
在这里,我会举个简单的例子来详细的介绍JNI的使用流程,具体如下所示,我的项目目录:
运行结果如下:
当我点击界面上的按钮,会调用Java本地方法,然后通过JNI将其与JNI的本地函数进行沟通打印LOG日志:
好了,接下来开始详细的介绍。
首先,先编写Java的本地方法,这个本地方法主要是为JNI的本地函数生成提供原型,我的代码:
public class VerifyJNIMethod {
static {
System.loadLibrary("hello_jni");
}
public static native String helloJni();
}
代码是很简单的,就是简单的打印LOG日志,在这里使用了静态块加载方式static,用意是当Android系统启动时就将本地的.so库加载到Java虚拟机中,以便调用及时不致发生错误。
另外,对于Java本地方法的方法是只有声明没有实现的,而且必须使用关键字native来声明,否则编译器会报错,这个关键字主要是告诉编译器当前修饰的方法为只有声明的方法,并且是有C/C++语言来实现的。
其次,我们要编译上面的Java本地方法,可以使用编译器命令javac来编译(前提注意JDK的环境变量是否设定正确),当然也可以直接运行项目程序(这是的本地方法不要调用JNI,因为其不识别),会自动生成本地方法的class文件。
接下来,就是编译.class文件生成对应的C/C++头文件了,我们只需要使用编译器命令javah即可自动生成头文件,具体如下:
因为我的电脑是windows的,所以打开命令窗口,cd到我们的项目目录下,执行命令:javah -classpath bin/classes -d jni com.demo.verifyjni.jni.VerifyJNIMethod在项目根目录下新建一个jni目录来存放生成的头文件,当然后面编写的实现文件和Android.mk文件也放在这里吧!这是自动生成的头文件为com_demo_verifyjni_jni_VerifyJNIMethod.h(命名规范:包名_类名),这个名字有点过长,我们将其修改为VerifyJNIMethod.h使其与下面的实现文件名称一致,便于理解。
我的头文件如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_demo_verifyjni_jni_VerifyJNIMethod */
#ifndef _Included_com_demo_verifyjni_jni_VerifyJNIMethod
#define _Included_com_demo_verifyjni_jni_VerifyJNIMethod
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_demo_verifyjni_jni_VerifyJNIMethod
* Method: helloJni
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_demo_verifyjni_jni_VerifyJNIMethod_helloJni
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
如果是第一次看到这个文件,会认为很复杂特别是对C不熟悉的读者,但静下心来看其实很简单,分析如下:
在这里我们只需要关注函数原型部分,也就是JNIEXPORT jstring JNICALL Java_com_demo_verifyjni_jni_VerifyJNIMethod_helloJni
(JNIEnv *, jclass);
部分,这里的函数原型的规则是这样的:JNIEXPORT+JNI数据类型+JNICALL+Java_包名_类名_方法名,里面的JNICALL、JNIEXPORT都是JNI的关键字,表示此函数要被JNI调用的,另外方法里面的参数为共同参数,其中的JNIEnv *参数是JNI接口的指针,用来调用JNI表中各种JNI函数(非JNI的本地函数),来创建或调用Java对象用的指针类型。第二个参数jclass则为如果Java的本地方法为静态方法的话,那么就会出现这个参数,否则参数类型为jobject,jclass代表着JNI中的类与Java中的类的对应类型,而jobject则为对象类型。
好了,头文件已经编译成功了,接下来就是头文件的实现文件的编写了。实现文件的编写推荐根据头文件规范编写,具体如下:
#include
#include "VerifyJNIMethod.h"
JNIEXPORT jstring JNICALL Java_com_demo_verifyjni_jni_VerifyJNIMethod_helloJni
(JNIEnv *env, jclass obj)
{
return (*env)->NewStringUTF(env,"Hello-Jni!");
}
这个文件当中使用了函数NewStringUTF(),它则代表着JNI函数,用来将C的字符创转换为Java的字符串类型并返回。返回之后,就会在Android前段接收返回的内容并打印LOG日志。
注意:JNI提供了多种JNI函数,用来处理C/C++与Java之间的转化操作,具体看查看地址:http://java.sun.com/docs/books/jni
到这里,上面已经准备好了Java本地方法、对应的C/C++的头文件和实现文件,那么就剩下了怎么生成C的共享库文件了。这个也比较简单,我们可以使用辅助工具Cygwin(下载地址:http://www.cygwin.com)来编译生成库文件,但必须注意一点必须编写Android.mk文件并配置,这样才会变异通过,我的mk文件如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello_jni
LOCAL_SRC_FILES := VerifyJNIMethod.c
include $(BUILD_SHARED_LIBRARY)
对于这个文件会在下面的NDK中介绍。依我的习惯是将头文件、实现文件及mk文件放到项目外的jni目录下,然后打开Cygwin软件cd到目录:/cygdrive/jni/jni,执行命令$NDK/ndk-build回车即可生成需要的.so文件了,我的如下:
好了,最后就剩下了怎么在前端使用Java调用JNI的方法来打印LOG了,我的实现代码如下:
public class VerifyJNIActivity extends Activity {
private static String TAG = "VerifyJNIActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_verify_jni);
}
public void helloJNI(View v) {
String result = VerifyJNIMethod.helloJni();
log(result);
}
private void log(String log) {
Log.d(TAG, log);
}
}
代码简单至极,调用VerifyJNIMethod.helloJni()打印LOG日志即可,没当点击下按钮就会打印”Hello-Jni!”了,效果在上面已经列出了。接下来来介绍下谷歌提供的NDK工具来快捷开发基于JNI的C/C++的代码与Java交互了。
注意:在最后为了减小APK包的大小,我们会将根目录下的jni目录删除掉,它只是为演示而已。
/**
* 欢迎加入技术群:179914858
* 如果有任何问题请在评论中进行讨论学习
*/