附:相关代码路径
/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
相关推荐
android jni开发入门介绍文档,适合NDK开发初学者阅读。
android jni 实现 RSA 3DES AES MD5 BASE64 加密,基于openssl
在jni中获取android 设备的mac地址
对android中使用JNI过程做了详细的介绍,还对JNI基础做了一定的介绍
Android JNI Android JNI 用C函数写本地库读写文件,底层调用小例子用C函数写本地库读写文件,底层调用小例子
android JNI C 调用Java
android jni 屏幕截图
Android JNI中C++层与Java层的对象交互实例代码详细介绍。Android JNI中C++层与Java层的对象交互实例代码详细介绍。Android JNI中C++层与Java层的对象交互实例代码详细介绍。Android JNI中C++层与Java层的对象交互...
Android JNI 断点调试C++,一个简单测试,方便初学者入门NDK环境搭建和java调用c++并调试
Android 串口读写程序 JNI代码程序
这是Android的NDK开发之Android JNI调用流程程一个demo。下载下来可以直接在android studio上运行。
android JNI学习三的最后代码实例
该Demo是我在实际项目中使用到的JNI常用技术总结和提炼,是另一个Android JNI开发培训课件对应的Demo
android jni详细介绍,包括如下文档: android_jni_javah_使用方法.doc android_jni操作指南.pdf Android技术之JNI和HAL.pdf Android通过JNI调用驱动程序(完全解析实例).doc 由浅入深,让你了解JNI如何在android里使用,...
通过JNI接口静态注册的native方法去创建线程,同时提供native回调Java的方法。通过这个框架可以去实现线程监听某一个状态,然后回调Java的方法(如发消息去通知顶层,实现显示)
这是我自己写的android jni里面抛出异常 的demo
android jni helloworld DEMO
Android上编写Native jni调用,java调用c ,c调用java。
Android NDK JNI 经典实例Android NDK JNI 经典实例Android NDK JNI 经典实例Android NDK JNI 经典实例Android NDK JNI 经典实例Android NDK JNI 经典实例Android NDK JNI 经典实例