连接Java和C/C++层的关键--Android的JNI

JNI的作用

    JNI是Java Native Interface(Java本地接口)的缩写。是从Java继承而来的,在Android中JNI的作用大大增强了。Android作为一种嵌入式操作系统,有大量和驱动、硬件相关的功能必须在native层实现,另外一些注重性能、功耗的功能使用C/C++来实现也优于Java来实现。因此,在Android开发中,无论是应用开发,还是系统开发都离不开JNI。

    Android中,Java层主要负责UI功能的实现,而C/C++层则完成一些复杂的算法以及和底层交互的功能,因此,Java层和C/C++层交互比较频繁,这就用到了JNI。

    Java语音的执行,离不开虚拟机。因此,当需要在Java中调用C/C++层的函数时,需要告诉虚拟机哪个方法代表本地的函数,以及在哪里能找到这个函数,反之类似。但是这两者之间还是有区别的,从Java到C/C++建立的是函数间的关联;而从C/C++到Java,必须先得到Java对象的引用,才能调用该对象的方法。这是因为Java是“纯”面向对象的语言,所以从C/C++到Java,必须和对象打交道,而不想C/C++中对象和函数可以混用。

    注意,无论从Java到C/C++,还是从C/C++到Java,两者是在一个线程中运行的。

JNI用法

从Java到C/C++
    1、装在JNI动态库

        为了使用JNI,在调用本地方法前必须把C/C++代码所在的动态库装载到进程的内存空间中。装载库文件调用的是System类的loadLibrary()方法,如下:

public static void loadLibrary(String libName)

        参数是动态库文件名称的一部分。Android JNI动态库的名称必须以“lib”开头,这里传入的参数是去掉前缀“lib”和后缀“.so”的中间部分。由于不同平台动态库的后缀时不一样的,Java希望代码能够跨平台使用,所以参数去掉了和系统相关的部分。

        调用loadLibrary()方法不需要指定库文件的路径,Android会在几个系统目录下查找动态库。

        为了保证调用native方法前所需要的动态库已经加载,loadLibrary()的调用位置一般放在类的static代码块中,这样进程初始化时就能执行装载语句了。例如,AudioEffect类装载JNI库的代码如下:

public class AudioEffect {
    static {
        System.loadLibrary("audioeffect_jni");
        native_init();
    }
    ......
}

    2、定义native方法

    在Java类中定义native方法很简单,在方法前加上“native”关键字就可以了,例如:

    // ---------------------------------------------------------
    // Native methods called from the Java side
    // --------------------

    private static native final void native_init();

    在native方法中,可以使用任何类型作为参数,包括基本对象类型、数组类型、负责对象等。

    3、编写JNI动态库

    JNI动态库中定义了一个“JNI_OnLoad()”的函数,这个函数在动态库加载后会被系统调用,用于完成JNI函数的注册。函数原型如下:

jint JNI_OnLoad(JavaVM* vm, void*)

    在该函数中最重要的就是调用registerNativeMethods()函数,完成动态库中JNI函数的注册。所谓注册,就是通过一张表把Java类中定义的native方法和本地C函数联系起来,这样Dalvik虚拟机在解析Java类中的native方法时就能查找到对应的C函数。

    下面看一个简单的例子:(media\jni\audioeffect\android_media_AudioEffect.cpp)

jint JNI_OnLoad(JavaVM* vm, void* reserved __unused)
{
......

    if (register_android_media_AudioEffect(env) < 0) {
        ALOGE("ERROR: AudioEffect native registration failed\n");
        goto bail;
    }
......
}
int register_android_media_AudioEffect(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
}
// Dalvik VM type signatures
static const JNINativeMethod gMethods[] = {
    {"native_init",          "()V",      (void *)android_media_AudioEffect_native_init},
    {"native_setup",         "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;II[I[Ljava/lang/Object;Ljava/lang/String;)I",
                                         (void *)android_media_AudioEffect_native_setup},
......
    {"native_setParameter",  "(I[BI[B)I",  (void *)android_media_AudioEffect_native_setParameter},
......
};
static const char* const kClassPathName = "android/media/audiofx/AudioEffect";

    registerNativeMtehods()函数的原型是:

int registerNativeMethods(JNIEnv* env, const char* classname, const JNINativeMethod* gMethods, int numMethods);

    第二个参数classname是指Java类的权限定类名,但是名称中的“.”要换成“/”。例如上面例子。第三个参数是JNINativeMethod类型的数组,类型定义如下:

typedef struct {
    const char* name;
    const char* signature;
    void* fnPtr;
} JNINativeMethod;

    类型定义中的成员变量name是指Java类中native方法的名称;signature是native方法的参数签名;fnPtr是指native方法对应的本地函数指针。

    本地函数的原型要求如下:

返回类型 函数名(JNIEnv *env, jobject obj, ......);

    第一个参数是JNI环境,第二个参数是指调用类的Java对象,后面省略号和具体方法的参数一致。具体例子如下:

static jint
android_media_AudioEffect_native_setEnabled(JNIEnv *env, jobject thiz, jboolean enabled)
{
    ......
}

    4、参数签名

    native方法的参数签名使用了一些缩写符号来代表参数类型,签名有参数加返回值组成,参数必须用小括号括起来,没有参数时使用一对空括号。如“(I)V”,表示方法有一个整形参数,无返回值;“([IZ)I”,表示方法有两个参数,第一个参数是整形数组,第二个参数是布尔型,返回类型是整形。

    参数签名中数组的表示方法是 在基本类型符号前加上“[”。

    复杂类型的参数签名格式是“L”加上“全限定类名”再加上“;”,例如

Ljava/lang/String;

从C/C++到Java
1、生产Java对象

    在JNIEnv中生成一个Java对象可以使用函数NewObject(),函数原型如下:

jobject NewObject(jclass clazz, jmethodID methodID, ...)

    clazz是指Java类对象(不是类的实例对象),可以通过函数FindClass()得到;参数methodID是指Java类的构造函数。

    FindClass()函数原型如下:

jclass FindClass(const char* name);

    name是指Java类的名称,如: “/java/lang/String”。jclass用来表示Java类。

    调用一个Java对象的方法或者存取一个Java对象的域变量前,要先知道对应的Id。取得方法Id和域变量Id的函数原型如下:

jmethodID GetMethodID(jclass clazz, const char* name, const char* sig);
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig);

    参数name是指Java类的方法或成员变量名称;sig是指参数类型签名。

    如果要获得一个Java类的构造函数,使用函数GetMethodID时,传入的参数name必须是“<init>”才行。

2、调用Java类的方法

    env->CallStaticVoidMethod(
        callbackInfo->audioEffect_class,
        fields.midPostNativeEvent,
        callbackInfo->audioEffect_ref, event, arg1, arg2, obj);

3、存取Java类的域变量

    env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage);

JNI环境

待续。。。

ART带来的JNI变化

待续。。。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值