前言
JNI是什么?JNI是Java Native Interface的缩写,即为java的本地调用,他其实就是一套规范,当你想要用java和其他语言进行调用的时候(c,c++)。你必须要遵守这个规范,就像我们网络传输的http协议规范一样。
说到JNI,NDK也会一起被提起,偶尔会把这两个名词搞混。那么NDK又是什么呢?
NDK其实就是一个工具库,只不过它是帮助我们进行c和c++开发使用的。就像我们Java要使用JDK,Android要使用SDK是一个道理的。
如何去实现JNI的调用?
接下来,我们就一起来实现一个见到的JNI调用的Demo吧。
1 首先我们当然是要新建一个项目了,我这里是基于Android Studio3.2去建立项目的:
这里需要注意,如果我们没有下载ndk,需要去先去下载ndk,具体通过File ->Setting 搜索SDK,点击SDK Tools,找到列表中的NDK进行安装。
安装完成NDK后需要同时进行环境变量配置。
2 配置完成后我们就先写一个JNI的调用吧:
public class JNIUtil {
public static native String hello();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.jni);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
JNIUtil.hello();
}
});
}
上面我们简单定义了一个JNI的调用,并在首页点击TextView进行了调用;
3 通过javah命令生成头文件
通过AS的Terminal中,进入到项目所在的Java目录中:
进入了自己的项目java目录下生成了头文件。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_myapplication_JNIUtil */
#ifndef _Included_com_example_myapplication_JNIUtil
#define _Included_com_example_myapplication_JNIUtil
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_myapplication_JNIUtil
* Method: hello
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_myapplication_JNIUtil_hello
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
看到生成的头文件,首先引入了jni.h,包含了一些宏定义和本地方法的结构踢。我们重点来看看方法名的格式,JNIEXPORT 和JNICALL 都是jni.h中定义的宏,JNIEnv *表示指向JNI环境的指针,通过他来访问JNI提供的接口方法,jclass也是jni.h中定义的,类型是object,实际是不确定类型的指针。这里用来接收Java中的this。实际编写中遵循Java_包名类名方法名就好了。所以我们有时候修改调用JNI方法的类的包名,可能会导致调用不到相关方法哦。
4 实现JNI方法:
方法我们已经创建好了,同时我们也已经自动生成了头文件,接下来我们就要去实现JNI的方法了;这里我们就用c语言写一个简单的无参函数,现在主项目的根目录下创建一个jni文件夹。我这边是在app目录下,即和build文件夹同级别的目录;然后我们需要在jni目录下创建Android.mk和demo.c文件
Android.mk是一个makefile的配置文件,我们把c生成so文件过程中的一些配置信息在Android.mk文件中进行配置,配置如下所示:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := jni-demo
LOCAL_SRC_FILES := demo.c
include $(BUILD_SHARED_LIBRARY)
LOCAL_MODULE 表示最终生成的动态库名称为libjni-demo.so,LOCAL_SRC_FILES 表示编译文件的原名称则为 demo.c文件。
接下来,我们就要创建会被调用到方法的demo.c文件了。
#include<jni.h>
jstring Java_com_example_myapplication_JNIUtil_hello(JNIEnv *env,jobject thiz){
return (*env)->NewStringUTF(env,"hello,I am a Str from jni libs!");
}
这里需要注意的是,指定调用者的路径一定要正确,我这边对应的是com_example_myapplication_JNIUtil_hello,com_example_myapplication代表对应包名,JNIUtil为类名,hello为方法名。
创建Application.mk文件:
APP_ABI := all
APP_PLATFORM := android-21
APP_ABI 代表需要生成的.so平台文件,all代表生成所有平台,当然也可以选择性生成。例如:armeabi-v7a arm64-v8a ,对应的平台可自行查阅资料。
APP_PLATFORM 定义使用的ndk库函数版本号。
这样我们编译前需要准备的工作已经全部完成。
5 使用ndk-build生成so库
切到我们的jni的目录下,ndk-build命令生成相关动态库。
在app目录下胜场了libs的文件:
我们在main目录下新建jniLibs目录,同时将刚刚生成的libs下的文件转移到jniLibs下面:
6 加载so文件,运行项目查看JNI的调用
在JNIUtil中动态加载对应的so文件,同时我们运行项目之后,项目运行起来之后点击相对应的textView。正常应会返回"hello,I am a Str from jni libs!"进行显示:
如上图即正常调用显示,那么通过ndk-build+Android.mk+Application.mk方式我们已经成功实现。
而在Android Studio2.2之后,我们可以更加方便地通过CMake方式来创建动态库。下一篇文章继续进行简单实践。
初识安卓JNI开发,用Cmake实现JNi的调用(二)