附:相关代码路径
/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