JNI原理分析
用法很比较简单。1.编写java文件,使用关键字native 2.编写头文件。3实现C++代码。4编译出SO 5.集成调用。
1.编写JAVA
创建Java文件:
package com.zx.testjni;
public class JNITest {
static{
System.loadLibrary("testjni");
}
public static native int add (int a,int b);
public static native String sayHello (String name);
}
2.编译头文件
到此目录中 按住shift 右击鼠标 选择 在此处打开窗口命令 m
输入javah 生成头文件
3.编写C/C++文件
新建jni目录
拷贝 头文件,新建 Android.mk 和Application.mk文件 和c文件
Android.mk内容:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := testjni
LOCAL_SRC_FILES := com_zx_testjni_JNITest.c
include $(BUILD_SHARED_LIBRARY)
Application.mk内容
APP_PLATFORM := android-17
头文件内容:
/* DO NOT EDIT THIS FILE - it is machine generated /
#include
/
#ifndef _Included_com_zx_testjni_JNITest
#define _Included_com_zx_testjni_JNITest
#ifdef __cplusplus
extern “C” {
#endif
/*
/*
#ifdef __cplusplus
}
#endif
#endif
C文件内容:
#include “com_zx_testjni_JNITest.h”
#include
#include
JNIEXPORT jint JNICALL Java_com_zx_testjni_JNITest_add
(JNIEnv *env, jclass object, jint a, jint b){
return a+b;
}
JNIEXPORT jstring JNICALL Java_com_zx_testjni_JNITest_sayHello
(JNIEnv *env, jclass object, jstring name){
// char str1[6] = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’};
// char str3[22];
// strcat( str3, str1);
// strcat( str3, name);
return (*env)->NewStringUTF(env,“hello i am jni”);
}
4编译SO文件
找到jni文件的目录,打开 cmd 输入 ndk-build ,如果没配置path 就如上图 输入全路径。
′f5刷新一下项目,会看到生成的文件
5调用
结果:
主要其实是C文件的编写是重点。 还是调用的原理是重点。
JNI是java native interface 。可以让Java代码调用到 native层方法。一般是C和C++。
生成头文件
比如我们有个源文件在:E:\com\zx\TestNative.java
第一种:直接cd到E:\目录 按住shift 点击右键 打开CMD 然后使用:javah com.zx.TestNative,其中javah后面的是需要生成头文件类的全路径(包名+类名),在当前目录就会生成com_zx_TestNative.h 头文件。
第二种:任意目录 打开CMD,使用如下命令:javah -classpath E:\ com.zx.TestNative 注意有空格。
其中-classpath后跟当前程序在磁盘上的位置,该位置只写到package com.zx;前一级目录就行了,后面是需要生成头文件类的全路径。就会在当前的目录生成com_zx_TestNative.h头文件。
头文件详解:
/* DO NOT EDIT THIS FILE - it is machine generated /
#include
/
#ifndef _Included_com_zx_astudyandroidclient_TestNative
#define _Included_com_zx_astudyandroidclient_TestNative
#ifdef __cplusplus
extern “C” {
#endif
/*
/*
#ifdef __cplusplus
}
#endif
#endif
编写 C++代码:
/* DO NOT EDIT THIS FILE - it is machine generated /
#include “com_zx_TestNative.h”
#include
// 必须的头文件
#include
using namespace std;
/
}
/*
静态配置的缺点:
1.需要编译所有生命了native函数的Java类,每个所生成的class文件都得用javah生成一个头文件。
2.javah生成的JNI层函数名特别长,书写起来很不方便。
3.初次调用native函数时要根据函数名字搜索对应的JNI层函数来建立关联关系,这样会影响运行效率。
动态注册:
1.java文件:
public class JNITest {
static{
Log.d("ZX", "loadLibrary===");
System.loadLibrary("testjni");
}
public static native int add (int a,int b);
public static native int reduce (int a,int b);
public static native String sayHello (String name);
}
C++文件:
#include “com_zx_testjni_JNITest.h”
JNIEXPORT jint JNICALL add
(JNIEnv *env, jclass object, jint a, jint b){
LOGE(“JNICALL add\n”);
return a+b;
}
JNIEXPORT jint JNICALL reduce
(JNIEnv *env, jclass object, jint a, jint b){
LOGE(“JNICALL reduce\n”);
return a-b;
}
JNIEXPORT jstring JNICALL sayHello
(JNIEnv *env, jclass object, jstring name){
LOGE(“JNICALL sayHello\n”);
return env->NewStringUTF(“I am for C++”);
}
static JNINativeMethod gMethods[] = {
{"add", "(II)I", (void *)add},
{"reduce", "(II)I", (void *)reduce},
{"sayHello", "(Ljava/lang/String;)Ljava/lang/String;", (void *)sayHello},
};
//此函数通过调用RegisterNatives方法来注册我们的函数
static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* gMethods,int methodsNum){
LOGE("registerNativeMethods %d",methodsNum);
jclass clazz;
//找到声明native方法的类
clazz = env->FindClass(className);
if(clazz == NULL){
LOGE("find class err\n");
return JNI_FALSE;
}
LOGE("start RegisterNatives\n");
//注册函数 参数:java类 所要注册的函数数组 注册函数的个数
int result=env->RegisterNatives(clazz,gMethods,methodsNum);
LOGI("RegisterNatives result %d",result);
if(result < 0){
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv* env){
LOGE("registerNatives-----");
const char* className = "com/zx/testjni/JNITest";
return registerNativeMethods(env,className,gMethods, sizeof(gMethods)/ sizeof(gMethods[0]));
}
//可以在该函数中进行动态注册JNI
JNIEXPORT jint JNI_OnLoad(JavaVM *vm,void *reserved){
LOGE(“jni_load JNI_OnLoad\n”);
JNIEnv* env = NULL;//定义JNI Env
jint result = -1;
/*JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint);
*/
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("GetEnv failed!");
return result;
}
LOGI("loading . . .");
LOGE("register--------");
/*开始注册
* 传入参数是JNI env
* 以registerNatives(env)为例说明
*/
if(registerNatives(env) != JNI_TRUE) {
LOGE("can't load registerNatives");
goto end;
}
result = JNI_VERSION_1_4;
end:
return result;
}
一定要注意:这里是有分号结尾的。
这里就是把Java的方法和native方法对应起来。
这个方法名要写对,否则找不到对应的 java 类中的方法。
由于增加了Android打印:所以需要再Android.mk文件增加引用
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS := -llog
LOCAL_MODULE := testjni
LOCAL_SRC_FILES := com_zx_testjni_JNITest.cpp
include $(BUILD_SHARED_LIBRARY)
原理分析
1首先分析头文件:
/* DO NOT EDIT THIS FILE - it is machine generated /
#include
/
#ifndef _Included_com_zx_testjni_JNITest
#define _Included_com_zx_testjni_JNITest
#ifdef __cplusplus
extern “C” {
#endif
/*
/*
#ifdef __cplusplus
}
#endif
#endif
这里的JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是被JNI调用的。这两个宏在不同的操作系统中,其定义是不一样的。
#define JNIIMPORT
#define JNIEXPORT attribute ((visibility (“default”)))
#define JNICALL
JNIEnv类实际代表了Java环境,通过这个JNIEnv* 指针,就可以对Java端的代码进行操作。例如,创建Java类的对象,调用Java对象的方法,获取Java对象的属性等等,JNIEnv的指针会被JNI传入到本地方法的实现两数中來对Java端的代码进行操作。
jclass : 为了能够在c/c++中使用java类JNI.h头文件中专门定义了jclass类型来表示java中的Class类
VM怎样使Native Method跑起来:
我们知道,当一个类第一次被使用到时,这个类的字节码会被加载到内存,并且只会回载一次。在这个被加载的字节码的入口维持着一个该类所有方法描述符的list,这些方法描述符包含这样一些信息:方法代码存于何处,它有哪些参数,方法的描述符(public之类)等等。
如果一个方法描述符内有native,这个描述符块将有一个指向该方法的实现的指针。这些实现在一些DLL文件内,但是它们会被操作系统加载到java程序的地址空间。当一个带有本地方法的类被加载时,其相关的DLL并未被加载,因此指向方法实现的指针并不会被设置。当本地方法被调用之前,这些DLL才会被加载,这是通过调用java.system.loadLibrary()实现的。