JNI
Java JNI的本意是Java Native Interface(Java本地接口),它是为了方便Java调用C/C++等本地代码所封装的一层接口。
NDK
NDK是Android所提供的一个工具集合,通过NDK可以在Android中更加方便地通过JNI来访问本地代码。NDK还提供了交叉编译器,开发人员只需简单修改mk文件就可以生成特定CPU平台的动态库。
windows环境下,通过AndroidStudio实现JNI调用实例
1.首先需要下载NDK,可以手动下载,也可以通过AndroidStudio自动下载。
无论如何下载,需要在AndroidStudio中配置好NDK地址。
2.建立一个带native的类
public class MyJni {
public native String get();
public native void set(String str);
}
3.通过Javac,创建class文件
指令如下:javac 被编译文件位置
javac E:\QiangPiao\app\src\main\java\com\king\qiangpiao\jni\MyJni.java
4.通过Javah,创建.h头文件
首先在app/src/main的目录下创建jni文件夹,与java同级。
指令如下:javah -d 头文件存放位置 -classpath 被编译.class文件位置
javah -d E:\QiangPiao\app\src\main\jni -classpath E:\QiangPiao\app\src\main\java\ com.king.qiangpiao.jni.MyJni
5.在Jni目录下创建c/c++文件,实现Java文件中native的具体方法
#include"com_king_qiangpiao_jni_MyJni.h"
#include<stdio.h>
JNIEXPORT jstring JNICALL Java_com_king_qiangpiao_jni_MyJni_get(JNIEnv *env, jobject thiz){
printf("invoke get from C++\n");
return (env)->NewStringUTF("Hello from JNI !");
}
JNIEXPORT void JNICALL Java_com_king_qiangpiao_jni_MyJni_set(JNIEnv *env, jobject thiz, jstring string){
printf("invoke set from C++\n");
char* str = (char*)(env)->GetStringUTFChars(string,NULL);
printf("%s\n",str);
(env)->ReleaseStringUTFChars(string,str);
}
6.在Jni目录下编写Android.mk和Application.mk文件
Android.mk
LOCAL_MODULE :=编译出来名字
LOCAL_SRC_FILES :=被编译的文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := my_jni
LOCAL_SRC_FILES := test.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk
编译产生so库的cpu平台,有armeabi , x86 , mips
APP_ABI := armeabi
7.生成so库
进入ndk-build.cmd的目录,就是第一步配置NDK的位置
指令如下:ndk-build -C jni目录地址
ndk-build -C E:\QiangPiao\app\src\main\jni
工程中会多一个app/src/main/libs其中对应平台有对应so库。
8.在Android的使用
public class MainActivity extends AppCompatActivity {
private Button btn;
private static MyJni jniTest;
static{
try {
//Android.mk文件中的LOCAL_MODULE属性指定的值相同
System.loadLibrary("my_jni");
jniTest = new MyJni();
}catch (Exception e){
e.printStackTrace();
}
}
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.jni_btn);
mTextView = (TextView) findViewById(R.id.textView);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTextView.setText(jniTest.get());
jniTest.set("hello world from JniTestApp");
}
});
}
}
在app中的build.gradle中添加
sourceSets {
main {
jni.srcDirs = []
jniLibs.srcDirs = ['src/main/libs']
}
}
提示
在gradle.properties中加上下面配置
android.useDeprecatedNdk=true
android.deprecatedNdkCompileLease=1515317190556
按这个流程下来,可以实现Android项目中通过jni调用c/c++文件。深入的理解和应用需要具体深入学习。
注册JNI函数的两种方式
静态注册
就是上面的整个流程
- 先创建Java类,声明Native方法,编译成.class文件。
- 使用Javah命令生成C/C++的头文件,使用Javah命令生成一个以.h为后缀的头文件。
- 创建.h对应的源文件,然后实现对应的native方法
动态注册
我们知道Java Native函数和JNI函数时一一对应的,JNI中就有一个叫JNINativeMethod的结构体来保存这个对应关系,实现动态注册方就需要用到这个结构体。举个例子,你就一下子明白了:
声明native方法还是一样的:
public class JavaHello {
public static native String hello();
}
创建jni目录,然后在该目录创建hello.c文件,如下:
//
// Created by DevilWwj on 16/8/28.
//
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>
/**
* 定义native方法
*/
JNIEXPORT jstring JNICALL native_hello(JNIEnv *env, jclass clazz)
{
printf("hello in c native code./n");
return (*env)->NewStringUTF(env, "hello world returned.");
}
// 指定要注册的类
#define JNIREG_CLASS "com/devilwwj/library/JavaHello"
// 定义一个JNINativeMethod数组,其中的成员就是Java代码中对应的native方法
static JNINativeMethod gMethods[] = {
{ "hello", "()Ljava/lang/String;", (void*)native_hello},
};
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods) {
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
/***
* 注册native方法
*/
static int registerNatives(JNIEnv* env) {
if (!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
}
/**
* 如果要实现动态注册,这个方法一定要实现
* 动态注册工作在这里进行
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
if ((*vm)-> GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
if (!registerNatives(env)) { //注册
return -1;
}
result = JNI_VERSION_1_4;
return result;
}
先仔细看一下上面的代码,看起来好像多了一些代码,稍微解释下,如果要实现动态注册就必须实现JNI_OnLoad方法,这个是JNI的一个入口函数,我们在Java层通过System.loadLibrary加载完动态库后,紧接着就会去查找一个叫JNI_OnLoad的方法。如果有,就会调用它,而动态注册的工作就是在这里完成的。在这里我们会去拿到JNI中一个很重要的结构体JNIEnv,env指向的就是这个结构体,通过env指针可以找到指定类名的类,并且调用JNIEnv的RegisterNatives方法来完成注册native方法和JNI函数的对应关系。
我们在上面看到声明了一个JNINativeMethod数组,这个数组就是用来定义我们在Java代码中声明的native方法,我们可以在jni.h文件中查看这个结构体的声明:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
结构体成员变量分别对应的是Java中的native方法的名字,如本文的hello;Java函数的签名信息、JNI层对应函数的函数指针。
以上就是动态注册JNI函数的方法,上面只是一个简单的例子,如果你还想再实现一个native方法,只需要在JNINativeMethod数组中添加一个元素,然后实现对应的JNI层函数即可,下次我们加载动态库时就会动态的将你声明的方法注册到JNI环境中,而不需要你做其他任何操作。
两种注册方式来自https://blog.csdn.net/wwj_748/article/details/52347341