Java Native Interface,即 Java本地接口,相当于桥梁作用,一种协议;
即在 Java代码 里调用 C、C++等语言的代码 或 C、C++代码调用 Java 代码(互相调用)
Android系统架构中上层(框架层+应用层)JAVA通过JNI调用底层(Linux Kernel层)C;
JNI是 Java 调用 Native 语言的一种特性,是属于 Java 的,与 Android 无直接关系
实际使用中,Java需要与本地代码(Native code)进行交互,因为Java具备跨平台特点,所以Java与本地代码交互能力弱。可采用JNI特性增强Java与本地代码交互能力
Native Development Kit,是 Android的一个工具开发包;NDK是属于 Android 的,与Java并无直接关系
快速开发C、 C++的动态库,并自动将so和应用一起打包成 APK,即可通过 NDK在 Android中 使用 JNI与本地代码(如C、C++)交互
使Android开发的功能需在本地代码(C/C++)实现
JNI是实现的目的(java与本地语言交互的接口/协议),NDK是Android中实现JNI的工具(Android工具开发包)
a. 在Gradle的 local.properties中添加配置
ndk.dir=/Users/Carson_Ho/Library/Android/sdk/ndk-bundle
b.在Gradle的 gradle.properties中添加配置
#兼容老的Ndk
android.useDeprecatedNdk=true
c.在Gradle的build.gradle添加ndk节点
ndk {
//.so文件 Linux下动态链接库(同windows下dll文件),二进制文件,多用于NDK开发.用户拿到动态库和头文件说明,就可以使用动态库中function
moduleName "hello_jni"//对应本地代码文件,生成.so文件:lib+moduleName.so
//abiFilters "x86","armeabi", "armeabi-v7a"//CPU类型
}
package com.sdu.chy.chytest.ndkTest
/**
* Java调用对应的C代码
*/
public class JNI {
//加载JNI生成so库
static {
System.loadLibrary("hello_jni");
}
//定义Native方法,调用C代码对应方法
public native String sayHello();
}
(1)包目录下javac生成.class类文件
ndkTest danding$ javac JNI.java
(2)外部(java)目录下javah生成.h文件
java danding$ javah -jni com.sdu.chy.chytest.ndkTest.JNI
(3)将.h文件移到jni文件夹下
需在Android项目中调用的本地代码hello.c
#include
#include
#include
//类名:Java类型+本地类型 对应关系
//C函数命名格式:Java_全类名_方法名
//JNIEnv*:代表了Java环境,通过这个JNIEnv* 指针,就可以对Java端的代码进行操作。
//jobject:代表native方法的实例(调用者),这里是JNI.ini
JNIEXPORT jstring JNICALL Java_com_sdu_chy_chytest_ndkTest_JNI_sayHello(JNIEnv* env,jobject jobj){
char* text = "I am from C";
return (*env)->NewStringUTF(env,text);
}
注:
作用:
指定源码编译的配置信息,如工作目录,编译模块的名称,参与编译的文件等
使用:
Android.mk(src/main/jni)
LOCAL_PATH := $(call my-dir)
// 设置工作目录,而my-dir则会返回Android.mk文件所在的目录
include $(CLEAR_VARS)
// 清除几乎所有以LOCAL——PATH开头的变量(不包括LOCAL_PATH)
LOCAL_MODULE := hello_jni
// 设置模块的名称,即编译出来.so文件名
// 注,要和上述步骤中build.gradle中NDK节点设置的名字相同
LOCAL_SRC_FILES := hello.c \
// 指定参与模块编译的C/C++源文件名
include $(BUILD_SHARED_LIBRARY)
// 指定生成的静态库或者共享库在运行时依赖的共享库模块列表。
作用:配置编译平台相关内容
使用:
Application.mk(src/main/jni)
APP_MODULES := hello_jni
APP_ABI := all
// 最常用的APP_ABI字段:指定需要基于哪些CPU平台的.so文件
// 常见的平台有armeabi x86 mips,其中移动设备主要是armeabi平台
// 默认情况下,Android平台会生成所有平台的.so文件,即同APP_ABI := armeabi x86 mips
// 指定CPU平台类型后,就只会生成该平台的.so文件,即上述语句只会生成armeabi平台的.so文件
sourceSets {
main {
jni.srcDirs = []
jniLibs.srcDirs = ['libs','src/main/libs']
//生成.so文件位置'src/main/libs'
}
}
编译成功后,在src/main/会多了两个文件夹libs & obj,其中libs下存放的是.so库文件
/Users/danding/Documents/chy_workspace/app/src/main/java/com/sdu/chy/chytest/ndkTest/JniTestActivity.java
public class JniTestActivity extends AppCompatActivity {
private TextView JniTextView;
private Button JniScheduleBtn;
private JniClickListener jniClickListener = new JniClickListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jni_test);
initView();
}
public void initView(){
JniTextView = (TextView)findViewById(R.id.jni_text_view);
JniScheduleBtn = (Button) findViewById(R.id.jni_btn_schedule);
JniScheduleBtn.setOnClickListener(jniClickListener);
}
public class JniClickListener implements View.OnClickListener{
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.jni_btn_schedule:
JniTextView.setText(new JNI().sayHello());//调用
break;
}
}
}
}
先由Java得到本地方法的声明,然后再通过JNI实现该声明方法
静态注册就是根据函数名来遍历Java和JNI函数之间的关联,而且要求JNI层函数的名字必须遵循特定的格式。
步骤1:首先在Java代码中声明native函数
public class JniDemo1{
static {
System.loadLibrary("samplelib_jni");
}
private native void nativeMethod();
}
步骤2:通过javah来生成native函数的.h文件
javah -d ./jni/ -classpath /Users/YOUR_NAME/Library/Android/sdk/platforms/android-21/android.jar:../../build/intermediates/classes/debug/ com.gebilaolitou.jnidemo.JniDemo1
然后就会得到一个JNI的.h文件,里面包含这几个native函数的声明
观察一下文件名以及函数名。其实JNI方法名的规范就出来了:
返回值 + Java前缀+全路径类名+方法名+参数1JNIEnv+参数2jobject+其他参数
步骤3:编写代码在.h文件中实现方法
先通过JNI重载JNI_OnLoad()实现本地方法,然后直接在Java中调用本地方法。
通过RegisterNatives方法把C/C++中的方法映射到Java中的native方法,而无需遵循特定的方法命名格式。
步骤一:加载so库
public class JniDemo1{
static {
System.loadLibrary("samplelib_jni");
}
}
步骤二:在JNI中的实现
jint JNI_OnLoad(JavaVM* vm, void* reserved)
步骤三:在这个函数里面去动态的注册native方法
#include
#include "Log4Android.h"
#include
#include
using namespace std;
#ifdef __cplusplus
extern "C" {
#endif
static const char *className = "com/gebilaolitou/jnidemo/JNIDemo2";
static void sayHello(JNIEnv *env, jobject, jlong handle) {
LOGI("JNI", "native: say hello ###");
}
static JNINativeMethod gJni_Methods_table[] = {
{"sayHello", "(J)V", (void*)sayHello},
};
static int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
LOGI("JNI","Registering %s natives\n", className);
clazz = (env)->FindClass( className);
if (clazz == NULL) {
LOGE("JNI","Native registration unable to find class '%s'\n", className);
return -1;
}
int result = 0;
if ((env)->RegisterNatives(clazz, gJni_Methods_table, numMethods) < 0) {
LOGE("JNI","RegisterNatives failed for '%s'\n", className);
result = -1;
}
(env)->DeleteLocalRef(clazz);
return result;
}
jint JNI_OnLoad(JavaVM* vm, void* reserved){
LOGI("JNI", "enter jni_onload");
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
jniRegisterNativeMethods(env, className, gJni_Methods_table, sizeof(gJni_Methods_table) / sizeof(JNINativeMethod));
return JNI_VERSION_1_4;
}
#ifdef __cplusplus
}
#endif
注:
1)JNINativeMethod
定义
JNI允许我们提供一个函数映射表,注册给Java虚拟机,这样JVM就可以用函数映射表来调用相应的函数。这样就可以不必通过函数名来查找需要调用的函数了。Java与JNI通过JNINativeMethod的结构来建立联系,它被定义在jni.h中
结构
typedef struct {
const char* name; //Java中函数名
const char* signature; //Java中参数和返回值
void* fnPtr; //指向C函数的函数指针
} JNINativeMethod;
绑定
在jniRegisterNativeMethods内,通过调用RegisterNatives函数将注册函数的Java类,以及注册函数的数组,以及个数注册在一起,这样就实现了绑定。
2)JNI中的签名
原因:
即将参数类型和返回值类型的组合。如果拥有一个该函数的签名信息和这个函数的函数名,我们就可以顺序的找到对应的Java层中的函数了。(防止Java函数重载找不到对应实现方法)
规范
(参数1类型标示;参数2类型标示;参数3类型标示…)返回值类型标示
为了能够在C/C++中调用Java中的类,jni.h的头文件专门定义了jclass类型表示Java中Class类。JNIEnv中有3个函数可以获取jclass。
1.jclass jcl_string=env->FindClass("java/lang/String");//通过类的名称
2.jclass GetObjectClass(jobject obj);//通过对象实例来获取jclass,相当于Java中的getClass()函数
3.jclass getSuperClass(jclass obj);//通过jclass可以获取其父类的jclass对象
所以为了在C/C++获取Java层的属性和方法,JNI在jni.h头文件中定义了jfieldID和jmethodID这两种类型来分别代表Java端的属性和方法。在访问或者设置Java某个属性\方法的时候,首先就要现在本地代码中取得代表该Java类的属性的jfieldID\jmethodID,然后才能在本地代码中进行Java属性的操作.
方法:一般是使用JNIEnv来进行操作
GetFieldID/GetMethodID:获取某个属性/某个方法
GetStaticFieldID/GetStaticMethodID:获取某个静态属性/静态方法
具体实现
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
jobject NewObject(jclass clazz, jmethodID methodID, ...)
当出现一些用java语言无法处理的任务时,开发人员就可以利用JNI技术来完成。一般来说下面几种情况需要用到JNI技术:
一、 开发时,需要调用java语言不支持的依赖于操作系统平台的特性的一些功能。例如:需要调用当前的Unix系统的某个功能,而java不支持这个功能,就需要用到JNI技术来实现。
二、 开发时,为了整合一些以前的非java语言开发的某些系统。例如,需要用到开发早期实现的一些C或C++语言开发的一些功能或系统,将这些功能整合到当前的系统或新的版本中。
三、 开发时,为了节省程序的运行时间,必须采用一些低级或中级语言。例如为了创建一个省时的应用,不得不采用汇编语言,然后采用java语言通过JNI技术调用这个低级语言的应用。
例如:
美图秀秀处理图片:用java获取图片文件,再用C通过颜色矩阵(RGBA)对图片进行处理