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()函数时,首先会去执行 组件里的 JNI_OnLoad()函数。它的用途有二:

 

(1)告诉 VM 此 组件使用那一个 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(),所以

 

组件的开发者可以藉由 JNI_OnLoad()来进行 组件内的初期值之设定(Initialization) 

 

例如,在 Android /system/lib/libmedia_jni.so 档案里,就提供了 JNI_OnLoad()函数。

 

由于VM 通常是多执行绪(Multi-threading)的执行环境。每一个执行绪在呼叫JNI_OnLoad(),所传递进来的 JNIEnv 指标值都是不同的。为了配合这种多执行绪的环境,C组件开发者在撰写本地函数时,可藉由 JNIEnv 指标值之不同而避免执行绪的资料冲突问题,才能确保所写的本地函数能安全地在 Android 的多执行绪 VM 里安全地执行。基于这个理由,当在呼叫 组件的函数时,都会将 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 的第 个元素赋值。

      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-->

你可能感兴趣的:(android)