Android JNI引见

Android JNI介绍附:相关代码路径/frameworks/base/media/java/android/media/MediaScanner.java/framework

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?代码能够与用其它编程语言(如?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

<!--EndFragment-->