Android 的 JNI

Java Native Interface

Android 作为一种嵌入式操作系统,有大量的驱动,硬件相关的功能必须在native层实现,另外,一些注重性能,功耗的功能用C/C++来实现也优于Java来实现。因此无论是应用级的开发还是系统级的开发都离不开JNI。Android 在编译,应用打包和安装,程序装载等各方面都对JNI进行了有力的支持。

Android 中,Java 层主要负责UI功能的实现,C/C++完成一些复杂的算法及和底层的交互功能。Java语言的执行,离不开虚拟机,因此当要在Java 代码中调用C/C++层的函数时,需要告诉虚拟机哪个方法代表本地的函数,以及在哪能找到这个函数,反之,也类似。但是也有区别:

Java调用 C/C++ 建立的是函数间的关联;

1.装载JNI 动态库

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

public static void loadLibrary(String libName)

loadLibrary() 方法的参数是动态库文件名称的一部分。Android JNI 动态库的名称必须以"lib"开头,这里传入的参数必须是去掉前缀lib以及后缀.so的中间部分。这是因为Java 希望代码可以跨平台使用,如:linux 下是.so, 而windows 下是.dll,所以传入的参数去掉了和系统相关的部分。

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

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

public class AudioEffect{

static{

System.loadLibrary("audioeffect_jni");

native_init();

 }

 }

2.定义native 方法

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

private static native final void init();

3.编写JNI动态库

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

jint JNI_OnLoad(JavaVM* vm, void*)

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

例:

static JNINativeMethod gMethods[]= {

{"enableOperator", "(IZ) Z", (void*) NATIVE_enable_operator},

{"disableOperator", "(IZ) Z", (void*) NATIVE_disable_operator},

 };

jnit JNI_OnLoad(JavaVM* vm, void* // reserved){

JNIEnv* env = NULL;

jint result = -1;

if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_Ok){

return -1;

 }

if(AndroidRuntime: :registerNativeMethods(env,"com/android/TestService", gMethods, NELEM(gMethods)) >= 0){

result = JNI_VERSION_1_4;

 }

return result;

 }

registerNativeMethods的函数原型是:

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

其中第二个参数className 是指java的全限定类名,但是名称中的"."要换成“/”, 这是JNI的规定。

第三个参数gMethods是JNINativeMethod类型的数组,类型定义如下:

typedef struct {

const char* name;// Java类中native方法的名称

const char* signature; // native 方法的参数签名,签名由参数➕返回值组成,参数必须用小括号括起来,没有参数时也要使用一对空括号。

void* fnPtr; // native方法对应的本地函数指针,这里只能是void类型的函数指针,调用时再根据参数做强制类型转换

 }JNINativeMethod;

4.复杂类型的参数签名格式是L +全限定类名+;如:String类的参数签名是“L/java/lang/String;”,对应JNI的类型为jstring,其余复杂的java类型对应的JNI类型都是jobject。

5.如何使用JNI类型

JNI类型不都是无符号的。

在JNIEnv中也定义了与数组相关的操作函数。如果需要知道数组的个数,可以调用GetArrayLength()函数。

原型是 jsize GetArrayLength(jarray array)

将JNI的数组类型转换为基本数组类型,每一种基本类型都有自己的转换,如int类型转换函数是:

jint* GetIntArrayElements(jintArray array, jboolean* isCopy)

返回的数组内存是在函数内分配的,用完后,必须是放掉,否则就会造成内存泄漏,int类型的释放函数为:

void ReleaseIntArraryElements(jintArray array, jint* elems, jint mode)

jstring 本质也是jobject类,使用前也要进行转换,使用完后同样要记得释放内存。

如果需要转换成utf-8的字符串,相关函数原型是:

jsize GetStringUTFLength(jstring string);

const char* GetStringUTFChars(jstring string, jboolean* isCopy);

void ReleaseStringUTFChars(jstring string, const char* utf);

需要转换成unicode的字符串

jsize GetStringLength(jstring string);

const char* GetStringChars(jstring string, jboolean* isCopy);

void ReleaseStringChars(jstring string, const char* utf);

如果一次只取一个数组的元素,可以使用下面的函数,也不用释放内存,更加方便

jobject GetObjectArrayElement(jobjectArray array, jsize index);

C/C++调用 Java 必须先得到Java对象的引用,才能调用该对象的方法。

无论哪种调用,两者在一个线程中运行,但是在各自打印出的log中,线程ID是不同的,因为JAVA线程的ID是从1开始的整数,C/C++使用的是一个与线程相关的数据结构的指针。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值