附:相关代码路径
/frameworks/base/media/java/android/media/MediaScanner.java
/frameworks/base/media/jni/android_media_MediaScanner.cpp
/frameworks/base/media/jni/android_media_MediaPlayer.cpp
/franmeworks/base/core/jni/AndroidRunTime.cpp
/dalvik/libnativehelper/JNIHelp.cpp
一、什么是JNI
Java Native Interface (JNI)标准是 java 平台的一部分,它允许 Java 代码和其他语言写的代码进行交互。JNI 是本地编程接口,它使得在 Java 虚拟机 (VM) 内部运行的 Java 代码能够与用其它编程语言(如 C、C++ 和汇编语言)编写的应用程序和库进行交互操作。
二、为什么推出JNI
1)、Java世界虚拟机使用Native语言编写的,虚拟机运行在具体的平台上,虚拟机本身无法做到与平台无关,jni可以对java层屏蔽不同平台操作的差异,这样就能实现java本身平台无关特性
2)、适应已经用Native语言实现的技术。
3)、一些效率的问题需要Native语言实现。
Android中JNI就是一座将Native层和java层联系在一起的桥梁。(本文将参考《深入理解Android 卷一》 以MediaScanner为例)
三、什么时候使用JNI
1)、Java API 可能不支某些平台相关的功能。比如,应用程序执行中要使用 Java API 不
支持的文件类型,而如果使用跨进程操作方式,即繁琐又低效
2)、避免进程间低效的数据拷贝操作
3)、多进程的派生:耗时、耗资源(内存)
4)、 用本地代码或汇编代码重写 Java 中低效方法
注意:
当 Java 程序集成了本地代码,它将丢掉 Java 的一些好处。
首先,脱离 Java 后,可移植性问题你要自己解决,且需重新在其他平台编译链接本地库。
第二,要小心处理 JNI 编程中各方面问题和来自 C/C++语言本身的细节性问题,处理不当,应用将崩溃。
一般性原则:做好应用程序架构,使 native methods 定义在尽可能少的几个类里。
四、Android JNI使用流程解析
以MediaScanner为例
1)、载入*.so库文件
VM 去载入 Android 的/system/lib/libmedia_jni.so 档案。载入*.so 之后,Java类与*.so 档案就汇合起来,一起执行了。
-->MediaScanner.java
......
static {
System.loadLibrary("media_jni");
native_init();
}
......
private static native final void native_init();
......
<--
2)、如何撰写*.so 的入口函数
JNI_OnLoad()与 JNI_OnUnload()函数的用途,当 Android 的 VM(Virtual Machine)执行到 System.loadLibrary()函数时,首先会去执行 C 组件里的 JNI_OnLoad()函数。它的用途有二:
(1)告诉 VM 此 C 组件使用那一个 JNI 版本。如果你的*.so 档没有提供 JNI_OnLoad()函数,VM 会默认该*.so 档是使用最老的 JNI 1.1 版本。由于新版的 JNI 做了许多扩充,如果需要使用JNI 的新版功能,例如 JNI 1.4 的 java.nio.ByteBuffer,就必须藉由 JNI_OnLoad()函数来告知VM。
(2)由于 VM 执行到 System.loadLibrary()函数时,就会立即先呼叫 JNI_OnLoad(),所以
C 组件的开发者可以藉由 JNI_OnLoad()来进行 C 组件内的初期值之设定(Initialization) 。
例如,在 Android 的/system/lib/libmedia_jni.so 档案里,就提供了 JNI_OnLoad()函数。
由于VM 通常是多执行绪(Multi-threading)的执行环境。每一个执行绪在呼叫JNI_OnLoad()时,所传递进来的 JNIEnv 指标值都是不同的。为了配合这种多执行绪的环境,C组件开发者在撰写本地函数时,可藉由 JNIEnv 指标值之不同而避免执行绪的资料冲突问题,才能确保所写的本地函数能安全地在 Android 的多执行绪 VM 里安全地执行。基于这个理由,当在呼叫 C 组件的函数时,都会将 JNIEnv 指标值传递给它。
-->android_media_MediaPlayer.cpp
......
extern int register_android_media_MediaScanner(JNIEnv *env);
......
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL);
......
if (register_android_media_MediaScanner(env) < 0) {
ALOGE("ERROR: MediaScanner native registration failed\n");
goto bail;
}
......
<--
3)、JNI注册,使java native函数和JNI函数一 一对应(动态注册)
JNI中记录java native函数和JNI函数一 一对应的结构
typedef struct {
//java中native函数的名字如”native_init”,不带函数路径
const char* name;
//java函数的签名信息,含参数和返回值
const char* signature;
//JNI层对应函数的指针,是void类型
void * fnPtr;
} JNINativeMethod;
如(2)中所述,在调用*.so入口函数 JNI_OnLoad时有调用register_android_media_MediaScanner(env)等方法。
-->andorid_media_MediaScanner.cpp
......
static JNINativeMethod gMethods[] = {
......
{
"native_init",
"()V",
(void *)android_media_MediaScanner_native_init
},
......
}
......
int register_android_media_MediaScanner(JNIEnv *env)
{
return AndroidRuntime::registerNativeMethods(env,
kClassMediaScanner, gMethods, NELEM(gMethods));
}
......
<--
-->AndroidRunTime.cpp
......
/*
* Register native methods using JNI.
*/
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
......
<--
向VM(即 AndroidRuntime)登记 gMethods[]表格所含的本地函数了。简而言之,registerNativeMethods()函数的用途有二:(1)更有效率去找到函数。(2)可在执行期间进行抽换。由于 gMethods[]是一个<名称,函数指针>对照表,在程序执行时,可多次呼叫 registerNativeMethods()函数来更换本地函数之指针,而达到弹性抽换本地函数之目的。
--->JNIHelp.cpp
......
static jclass findClass(C_JNIEnv* env, const char* className) {
JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
return (*env)->FindClass(e, className);
}
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
LOGV("Registering %s natives", className);
scoped_local_ref<jclass> c(env, findClass(env, className)); //没有看明白
if (c.get() == NULL) {
LOGE("Native registration unable to find class '%s', aborting", className);
abort();
}
if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
LOGE("RegisterNatives failed for '%s', aborting", className);
abort();
}
return 0;
}
......
<---
看似很繁琐,其实动态注册只用了两个函数完成。
(*env)->FindClass(e, className);
(*env)->RegisterNatives(e, c.get(), gMethods, numMethods)
如果想要完成动态注册,就必须实现JNI_OnLoad函数
这 JNI_OnLoad()呼叫 register_android_media_MeidaScnaner(env)函数时,就将 env 指标值传递过去。如此,在 register_android_media_MeidaScnaner()函数就能藉由该指标值而区
别不同的执行绪,,以便化解资料冲突的问题。
例如,在 register_android_media_MeidaScnaner()函数里,可撰写下述指令:
if ((*env)->MonitorEnter(env, obj) != JNI_OK) {
}
查看是否已经有其他执行绪进入此物件,如果没有,此执行绪就进入该物件里执行了 。
还有,也可撰写下述指令:
if ((*env)->MonitorExit(env, obj) != JNI_OK) {
}
查看是否此执行绪正在此物件内执行,如果是,此执行绪就会立即离开。
五、JNI内容简单介绍
1)、JNIEnv是一个与线程相关的变量
2)、native函数转换成JNI函数时,虚拟机会传入JNIEnv变量,如果后台线程主动回调java层函数是在JNI_OnLoad函数中Java VM会产生JNIEnv变量,在线程退出时,会释放对应的资源
插入内容:JNI签名
Java中有函数重载,区别在于函数的参数,JNI则是通过函数返回值和函数参数合成一个签名信息与java中的函数对应。
如下:签名标示表
类型标识 |
Java类型 |
类型标识 |
Java类型 |
Z |
boolean |
F |
float |
B |
byte |
D |
double |
C |
char |
L/java/lang/String; |
String |
S |
short |
[I |
Int[] |
I |
int |
[L/java/lang/Object; |
Object[] |
J |
long |
注意:Java类型是一个数组,则标识中会有一个”[”,引用类型最后都有一个’;’。
例如:
函数签名 |
Java函数 |
“()Ljava/lang/String;” |
String f() |
“(ILjava/lang/Class)J” |
long f(int i, Class class) |
“([B)V” |
void f(byte[] byte) |
附:用javap -s -p xxx(编译生成的.class文件)可以查看xxx中函数对应的签名。
1)、jfieldID和jmethodID介绍和使用
我们知道,成员变量和成员函数都是由类定义的,他们都是类的属性,所以在JNI规则中用jfieldID和jmethodID来标示java类成员和函数。JNI中可以通过如下方式操作。
jfieldID fid = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;");
cls:代表java类
"s":成员变量的名字
"Ljava/lang/String;":变量的签名信息
这时,通过在对象上调用下述方法获得成员的值:
jstr = (*env)->GetObjectField(env, obj, fid);
通过在对象上调用下述方法改变成员的值:
(*env)->SetObjectField(env, obj, fid, field);
此外 JNI 还提供Get/SetIntField,Get/SetFloatField等访问不同类型成员。
同样,也提供了static变量的访问方法, 即Get/SetStatic<ReturnValue Type>Field。
jmethodID fid = (*env)->GetMethodID(env, cls, "s", "Ljava/lang/String;");
Java 中有三类方法:实例方法、静态方法和构造方法。示例:
Java代码: class InstanceMethodCall { private native void nativeMethod(); private void callback() { System.out.println("In Java"); } public static void main(String args[]) { InstanceMethodCall c = new InstanceMethodCall(); c.nativeMethod(); } static { System.loadLibrary("InstanceMethodCall"); } } JNI代码 JNIEXPORT void JNICALL Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj) { jclass cls = (*env)->GetObjectClass(env, obj); jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "()V"); if (mid == NULL) { return; /* method not found */ } printf("In C\n"); (*env)->CallVoidMethod(env, obj, mid); }
输出: In C In Java |
A)、回调 Java 方法分两步:
• 首先通过 GetMethodID 在给定类中查询方法. 查询基于方法名称和签名
• 本地方法调用 CallVoidMethod,该方法表明被调 Java 方法的返回值为 void
从 JNI 调用实例方法命名规则:Call<Return Value Type>Method
-->android_media_MediaScanner.cpp::MyMediaScannerClient
......
static const char* const kClassMediaScannerClient =
"android/media/MediaScannerClient";
......
//先找到android/media/MediaScannerClient类在JNI中对应的jclass实例
jclass mediaScannerClientInterface =
env->FindClass(kClassMediaScannerClient);
if (mediaScannerClientInterface == NULL) {
ALOGE("Class %s not found", kClassMediaScannerClient);
} else {
// 获得MediaScannerClient类中方法scanFile的ID
mScanFileMethodID = env->GetMethodID(
mediaScannerClientInterface,
"scanFile",
"(Ljava/lang/String;JJZZ)V");
......
}
......
<--
如上在MyMediaScannerClient会缓存mScanFileMethodID,这是关于程序运行效率的一个问题。(避免每次都去做查询ID的操作)
B)、同实例方法,回调 Java 静态方法分两步:
• 首先通过 GetStaticMethodID 在给定类中查找方法
• 通过 CallStatic<ReturnValueType>Method 调用
静态方法与实例方法的不同,前者传入参数为 jclass,后者为 jobject
C)、调用被子类覆盖的父类方法: JNI 支持用 CallNonvirtual<Type>Method 满足这类需求:
• GetMethodID 获得 method ID
• 调用 CallNonvirtualVoidMethod, CallNonvirtualBooleanMethod
上述,等价于如下 Java 语言的方式:
super.f();
D)、CallNonvirtualVoidMethod 可以调用构造函数
六、JNI数据类型转换
1)、java基本类型和JNI基本类型转换
Java |
Native类型 |
符号属性 |
字长 |
boolean |
jboolean |
无符号 |
8位 |
byte |
jbyte |
无符号 |
8位 |
char |
jchar |
无符号 |
16位 |
short |
jshort |
有符号 |
16位 |
int |
jint |
有符号 |
32位 |
long |
jlong |
有符号 |
64位 |
float |
jfloat |
有符号 |
32位 |
double |
jdouble |
有符号 |
64位 |
(java基本类型和JNI基本类型转换关系表)
注意:转换成Native类型后对应类型的字长,如jchar在Native语言中时16位,占两个字节,和普通的char占一个字节是不一样的。
2)、引用数据类型的转换
Java引用类型 |
Native类型 |
Java引用类型 |
Native类型 |
All objects |
jobject |
char[] |
jcharArray |
java.lang.Class 实例 |
jclass |
short[] |
jshortArray |
java.lang.String 实例 |
jstring |
int[] |
jintArray |
Object[] |
jobjectArray |
long[] |
jlongArray |
boolean[] |
jbooleanArray |
float[] |
jfloatArray |
byte[] |
jbyteArray |
double[] |
jdoubleArray |
java.lang.Throwable实例 |
jthrowable |
看MediaScanner中的processFile
//java层processFile中有3个参数
private native void processFile(String path, String mimeType, MediaScannerClient client);
//JNI层对应的函数响应
static voidandroid_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path,
jstring mimeType, jobject client) {
......
}
如果对象都用jobject,就好比Native层的void *类型,对“码农”来讲,他们是透明的,既然是透明的,该如何操作呢?注意上述JNI对应函数中JNIEnv *env,和jobject thiz(调用processFile函数的对象)参数。
3)、JNI局部变量,全局变量和垃圾回收
Java 中有许多引用的概念,我们只关心 GlobalRef 和 LocalRef 两种。JNI 编程很复杂,
建议不要引入更多复杂的东西,正确、高效的实现功能就可以了。比如对引用来说,最好不要在 JNI 中考虑:虚引用和影子引用等复杂的东西。
GlobalRef: 当你需要在 JNI 层维护一个 Java 对象的引用,而避免该对象被垃圾回收时,使用 NewGlobalRef 告诉 VM 不要回收此对象,当本地代码最终结束该对象的引用时,用
DeleteGlobalRef 释放之。
LocalRef: 每个被创建的 Java 对象,首先会被加入一个 LocalRef Table,这个 Table 大
小是有限的,当超出限制,VM 会报 LocalRef Overflow Exception,然后崩溃. 这个问题是 JNI 编程中经常碰到的问题,请引起高度警惕,在 JNI 中及时通过 DeleteLocalRef 释放对象的 LocalRef. 又,JNI 中提供了一套函数:Push/PopLocalFrame,因为 LocalRefTable 大小是固定的,这套函数只是执行类似函数调用时,执行的压栈操作,在 LocalRefTable 中预留一部分供当前函数使用,当你在 JNI 中产生大量对象时,虚拟机仍然会因LocalRef Overflow Exception 崩溃,所以使用该套函数你要对 LocalRef 使用量有准确估计。
下面来看具体代码
-->android_media_MediaScanner.cpp
......
class MyMediaScannerClient : public MediaScannerClient
{
public:
MyMediaScannerClient(JNIEnv *env, jobject client)
: mEnv(env),
// 用NewGlobalRef创建一个GlobalRef的mClient,这样就避免mClient被回收
mClient(env->NewGlobalRef(client)),
mScanFileMethodID(0),
mHandleStringTagMethodID(0),
mSetMimeTypeMethodID(0)
......
virtual ~MyMediaScannerClient()
{
ALOGV("MyMediaScannerClient destructor");
//析构函数中调用DeleteGlobalRef释放全局变量
mEnv->DeleteGlobalRef(mClient);
}
......
<--
注意:
a)、当写 native method 的实现时,要认真处理循环中产生的 LocalRef. VM 规范中规定每个本地方法至少要支持 16 个 LocalRef 供自由使用并在本地方法返回后回收. 本地方法绝对不能滥用 GlobalRef 和 WeakGlobalRef,因为此类型引用不会被自动回收。工具函数,对 LocalRef 的使用更要提起警惕,因为该类函数调用上下文不确定,而且会被重复调用,每个代码路径都要保证不存在 LocalRef 泄露。
b)、Push/PopLocalFrame 常被用来管理 LocalRef. 在进入本地方法时,调用一次PushLocalFrame,并在本地方法结束时调用 PopLocalFrame. 此对方法执行效率非常高,建议使用这对方法。
你只要对当前上下文内使用的对象数量有准确估计,建议使用这对方法,在这对方法间,不必调用 DeleteLocalRef,只要该上下文结尾处调用 PopLocalFrame 会一次性释放所有LocalRef。一定保证该上下文出口只有一个,或每个 return 语句都做严格检查是否调用了PopLocalFrame。忘记调用 PopLocalFrame 可能会使 VM 崩溃。
4)、JNI对象比较
有两个对象,用如下方法比较相容性:
(*env)->IsSameObject(env, obj1, obj2)
如果相容,返回 JNI_TRUE, 否则返回 JNI_FALSE。
与 NULL 的比较,LocalRef 与 GlobalRef 语义显然, 前提是释放了两个引用,程序员重新为相应变量做了 NULL 初始化。
但对于 Weak Global Ref 来说,需要使用下述代码判定:
(*env)->IsSameObject(env, wobj, NULL)
因为,对于一个 Weak Global Ref 来说可能指向已经被 GC 的无效对象。
七、数据类型的传递
注意:代码中如果env->是C++语言,如果是(*env)->是C语言
1)、基本类型的传递
Java的基本类型和对应的JNI类型传递时没有问题的。
2)、String参数的传递
......
private native String getLine(String prompt); //java定义的native方法
......
......
JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject this,
jstring prompt); //JNI中与native方法对应的JNI方法
......
如上,在JNI函数中是不能直接使用jstring prompt,编译会报错,因为JNI都是用C和C++编写的,这两种语言中没有jstring类型,所以使用的过程中必须要做一些处理。
......
char *str;
str = env->GetStringUTFChars(prompt, false); //将 jstring 类型变成一个 char*类型
......
(*env)->ReleaseStringUTFChars(env, prompt, str);//使用完后要记得释放内存
......
返回的时候,要生成一个 jstring 类型的对象,也必须通过如下命令
jstring rtstr = env->NewStringUTF(tmpstr);
3)、数组类型的传递
和 String 一样,JNI 为 Java 基本类型的数组提供了 j*Array 类型,比如 int[]对应的就是jintArray。来看一个传递 int 数组的例子,
JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj,jintArray arr){ jint *carr; carr = env->GetIntArrayElements(arr, false); //分配内存空间 if(carr == NULL) { return 0; } jint sum = 0; for(int i=0; i<10; i++) { sum += carr[i]; } env->ReleaseIntArrayElements(arr, carr, 0); //释放内存空间 return sum; }
|
这个例子中的 GetIntArrayElements 和 ReleaseIntArrayElements 函数就是 JNI 提供用
于处理 int 数组的函数。如果试图用 arr 的方式去访问 jintArray 类型,毫无疑问会出错。JNI
还提供了另一对函数 GetIntArrayRegion 和 ReleaseIntArrayRegion 访问 int 数组,就不介绍
了,对于其他基本类型的数组,方法类似。
4)、二维数组和 String 数组
在 JNI 中,二维数组和 String 数组都被视为 object 数组,因为数组和 String 被视为 object。用一个例子来说明,这次是一个二维 int 数组,作为返回值。
JNIEXPORT jobjectArray JNICALL Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size){
jobjectArray result;//因为要返回值,所以需要新建一个 jobjectArray 对象。
jclass intArrCls = env->FindClass("[I"); result = env->NewObjectArray(size, intArrCls, NULL);// 为 result 分配空间。
for (int i = 0; i < size; i++) { jint tmp[256]; jintArray iarr = env->NewIntArray(size);//是为一维 int 数组 iarr 分配空间。 for(int j = 0; j < size; j++) { tmp[j] = i + j; }
env->SetIntArrayRegion(iarr, 0, size, tmp);//是为 iarr 赋值。 env->SetObjectArrayElement(result, i, iarr);//是为 result 的第 i 个元素赋值。 env->DeleteLocalRef(iarr);//释放局部对象的引用 } return result; } |
jclass intArrCls = env->FindClass("[I");
是创建一个 jclass 的引用,因为 result 的元素是一维 int 数组的引用,所以 intArrCls必须是一维 int 数组的引用,这一点是如何保证的呢?注意 FindClass 的参数" [I",JNI 就是通
过它来确定引用的类型的,I 表示是 int 类型,[标识是数组。对于其他的类型,都有相应的表
示方法,详细见JNI签名。
八、JNI异常处理
JNI中也有异常,不过它和C++,java中的异常不一样。如果JNI层出现异常,它不会中断本地函数,直到返回java层,由java虚拟机抛出异常。虽然JNI层不会抛出异常,但是在异常产生的时候它会做一些资源清理的工作,所以,如果在JNI层的函数出现异常时,调用JNIEnv异常函数外的其他函数会导致程序死掉。
示例代码
-->android_media_MediaScanner.cpp
......
virtual status_t scanFile(const char* path, long long lastModified,
long long fileSize, bool isDirectory, bool noMedia)
{
ALOGV("scanFile: path(%s), time(%lld), size(%lld) and isDir(%d)",
path, lastModified, fileSize, isDirectory);
jstring pathStr;
//调用失败后直接返回,不要再让程序做任何事情
if ((pathStr = mEnv->NewStringUTF(path)) == NULL) {
mEnv->ExceptionClear();
return NO_MEMORY;
}
mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
fileSize, isDirectory, noMedia);
mEnv->DeleteLocalRef(pathStr);
return checkAndClearExceptionFromCallback(mEnv, "scanFile");
}
......
<--
异常Demo
java
class CatchThrow { private native void doit()throws IllegalArgumentException; private void callback() throws NullPointerException { throw new NullPointerException("CatchThrow.callback"); } public static void main(String args[]) { CatchThrow c = new CatchThrow(); try { c.doit(); } catch (Exception e) { System.out.println("In Java:\n\t" + e); } } static { System.loadLibrary("CatchThrow"); } }
|
C
JNIEXPORT void JNICALL Java_CatchThrow_doit(JNIEnv *env, jobject obj) { jthrowable exc; jclass cls = (*env)->GetObjectClass(env, obj); jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "()V"); if (mid == NULL) { return; } (*env)->CallVoidMethod(env, obj, mid); //判断是否有异常发生 exc = (*env)->ExceptionOccurred(env); if (exc) { /* We don't do much with the exception, except that we print a debug message for it, clear it, and throw a new exception. */ jclass newExcCls; //描述异常 (*env)->ExceptionDescribe(env); //清除异常 (*env)->ExceptionClear(env); newExcCls = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); if (newExcCls == NULL) { /* Unable to find the exception class, give up. */ return; } //向java层抛出异常 (*env)->ThrowNew(env, newExcCls, "thrown from C code"); } } |
结果
java.lang.NullPointerException: at CatchThrow.callback(CatchThrow.java) at CatchThrow.doit(Native Method) at CatchThrow.main(CatchThrow.java) In Java: java.lang.IllegalArgumentException: thrown from C code |
九、文档描述
本文结合《深入理解Android》《jni详解》等文章对jni技术做了简单的剖析,这些对学习android jni层会有不错的帮助,在后续还会对文档修改完善。
本地JNI Demo调试步骤(Linux):
@以下步骤都是在同一个目录下
1)创建Hello.java
Hello.java
public class Hello.java {
private native void print();
static {
System.loadLibrary(“Hello”);
}
public static void main(String[] args) {
new Hello().print();
}
}
2) JNI静态注册
$javac Hello.java (生成Hello.class)
$javah -jni Hello (生成Hello.h)
Hello.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */
#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Hello
* Method: print
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Hello_print
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
3)创建Hello.c
Hello.c
#include<stdio.h>
#include "Hello.h" /*注意要引入Hello.h头文件*/
JNIEXPORT void JNICALL //静态注册native方法
Java_Hello_print(JNIEnv *env, jobject obj)
{
printf("Hello World!\n");
}
4)编译成*.so库文件
$gcc Hello.c -fPIC -shared -o libHello.so //注意这里(linux中库文件都是以lib开头的)
5)设置Lib库文件环境变量
运行前,必须保证连接器,能找到待装载的库,不然,将抛如下异常:
java.lang.UnsatisfiedLinkError: no HelloWorld in library path
at java.lang.Runtime.loadLibrary(Runtime.java)
at java.lang.System.loadLibrary(System.java)
$LD_LIBRARY_PATH=.
$export LD_LIBRARY_PATH
6)运行
$java Hello