当前位置: 代码迷 >> Android >> Android JNI引见
  详细解决方案

Android JNI引见

热度:67   发布时间:2016-05-01 11:57:22.0
Android JNI介绍

附:相关代码路径

/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?代码能够与用其它编程语言(如?CC++?和汇编语言)编写的应用程序和库进行交互操作。

二、为什么推出JNI

1)Java世界虚拟机使用Native语言编写的,虚拟机运行在具体的平台上,虚拟机本身无法做到与平台无关,jni可以对java层屏蔽不同平台操作的差异,这样就能实现java本身平台无关特性

2)、适应已经用Native语言实现的技术。

3)、一些效率的问题需要Native语言实现。

AndroidJNI就是一座将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?{

?

//javanative函数的名字如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)jfieldIDjmethodID介绍和使用

我们知道,成员变量和成员函数都是由类定义的,他们都是类的属性,所以在JNI规则中用jfieldIDjmethodID来标示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类中方法scanFileID

????????????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类型后对应类型的字长,如jcharNative语言中时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

//javaprocessFile中有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创建一个GlobalRefmClient,这样就避免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都是用CC++编写的,这两种语言中没有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

<!--EndFragment-->
  相关解决方案