博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android Jni调用浅述
阅读量:6159 次
发布时间:2019-06-21

本文共 13853 字,大约阅读时间需要 46 分钟。

转自:

 

1 简述

    JNI是Java Native Interface的缩写,中文为JAVA本地调用。从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。

    由于的应用层的类都是以Java写的,这些Java类编译为Dex型式的字节码之后,必须依靠Dalvik虚拟机来运行,在Android中Dalvik虚拟机扮演很重要的角色.而Android中间件是由C/C++写的,这些C/C++写的组件并不是在Dalvik虚拟机上运行的。那么应用层上的Java代码又是如何与C/C++写的组件之间又是如何沟通的?

2 载入.so文件

System.loadLibrary(*.so文件);

在java代码中,可以通过loadLibrary要求VM装载so文件,java代码一般如下形式:

1 public class jnitest {  2     static {  3         System.loadLibrary("jnitest");  4     }  5     //...  6 }

 

上述代码运行时将会在/system/lib/目录下查找libjnitest.so文件,将载入VM,这样,java代码和C组件之间就构成了联系,接下来就可以通过一些方法可以相互调用了.

 

3 JNI_OnLoad与JNI_OnUnload

    在Android中,当程序在java层运行System.loadLibrary("jnitest");这行代码后,程序会去载入libjnitest.so文件,与此同时,产生一个"Load"事件,这个事件触发后,程序默认会在载入的.so文件的函数列表中查找JNI_OnLoad函数并执行,与"Load"事件相对,当载入的.so文件被卸载时,“Unload”事件被触发,此时,程序默认会去在载入的.so文件的函数列表中查找JNI_OnUnload函数并执行,然后卸载.so文件。需要注意的是,JNI_OnLoad与JNI_OnUnload这两个函数在.so组件中并不是强制要求的,用户也可以不去实现,java代码一样可以调用到C组件中的函数,在接下来的章节中会讲到这点.

    之所以在C组件中去实现这两个函数(特别是JNI_OnLoad函数),往往是做一个初始化工作或“善后”工作。可以这样认为,将JNI_ONLoad看成是.so组件的初始化函数,当其第一次被装载时被执行(window下的dll文件也可类似的机制,在_DLL_Main()函数中,通过一个swith case语句来识别当前是载入还是卸载)。将JNI_OnUnload函数看成是析构函数,当其被卸载时被调用。

    由此看来,就不难明白为什么很多jni C组件中会实现JNI_OnLoad这个函数了。 一般情况下,在C组件中的JNI_OnLoad函数用来实现给VM注册接口,以方便VM可以快速的找到Java代码需要调用的C函数。(此外,JNI_OnLoad函数还有另外一个功能,那就是告诉VM此C组件使用那一个JNI版本,如果未实现JNI_OnLoad函数,则默认是JNI 1.1版本)。

4 显式注册native方法

4.1 显式注册的作用:

    应用层的Java类别通过VM而调用到native函数。一般是通过VM去寻找*.so里的native函数。如果需要连续呼叫很多次,每次都需要寻找一遍,会多花许多时间。此时,C组件开发者可以将本地函数向VM进行注册,以便能加快后续调用native函数的效率.可以这么想象一下,假设VM内部一个native函数链表,初始时是空的,在未显式注册之前此native函数链表是空的,每次java调用native函数之前会首先在此链表中查找需要查找需要调用的native函数,如果找到就直接使用,如果未找到,得再通过载入的.so文件中的函数列表中去查找,且每次java调用native函数都是进行这样的流程,因此,效率就自然会下降,为了克服这样现象,我们可以通过在.so文件载入初始化时,即JNI_OnLoad函数中,先行将native函数注册到VM的native函数链表中去,这样一来,后续每次java调用native函数时都会在VM中的native函数链表中找到对应的函数,从而加快速度.

注:在Android 源码开发环境下,大多采用显式注册native方法.

4.2 在Android源码开发模式下有两种方法可以实现显示注册native方法:

方法一: 使用JNIHelp.h头文件中定义的jniRegisterNativeMethods来实现.

如~/WORKING_DIRECTORY/frameworks/base/services/jni/com_android_server_location_GpsLocationProvider.cpp:

注:此文件同级目录中的其它cpp文件大多采用此种方法进行native方法显式注册.

1 static JNINativeMethod sMethods[] = {   2      /* name, signature, funcPtr */   3     {
"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native}, 4 {
"native_is_supported", "()Z", (void*)android_location_GpsLocationProvider_is_supported}, 5 {
"native_init", "()Z", (void*)android_location_GpsLocationProvider_init}, 6 {
"native_cleanup", "()V", (void*)android_location_GpsLocationProvider_cleanup}, 7 {
"native_set_position_mode", "(IIIII)Z", (void*)android_location_GpsLocationProvider_set_position_mode}, 8 {
"native_start", "()Z", (void*)android_location_GpsLocationProvider_start}, 9 {
"native_stop", "()Z", (void*)android_location_GpsLocationProvider_stop}, 10 {
"native_delete_aiding_data", "(I)V", (void*)android_location_GpsLocationProvider_delete_aiding_data}, 11 {
"native_read_sv_status", "([I[F[F[F[I)I", (void*)android_location_GpsLocationProvider_read_sv_status}, 12 {
"native_read_nmea", "([BI)I", (void*)android_location_GpsLocationProvider_read_nmea}, 13 {
"native_inject_time", "(JJI)V", (void*)android_location_GpsLocationProvider_inject_time}, 14 {
"native_inject_location", "(DDF)V", (void*)android_location_GpsLocationProvider_inject_location}, 15 {
"native_supports_xtra", "()Z", (void*)android_location_GpsLocationProvider_supports_xtra}, 16 {
"native_inject_xtra_data", "([BI)V", (void*)android_location_GpsLocationProvider_inject_xtra_data}, 17 {
"native_agps_data_conn_open", "(Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_agps_data_conn_open}, 18 {
"native_agps_data_conn_closed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_closed}, 19 {
"native_agps_data_conn_failed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_failed}, 20 {
"native_agps_set_id","(ILjava/lang/String;)V",(void*)android_location_GpsLocationProvider_agps_set_id}, 21 {
"native_agps_set_ref_location_cellid","(IIIII)V",(void*)android_location_GpsLocationProvider_agps_set_reference_location_cellid}, 22 {
"native_set_agps_server", "(ILjava/lang/String;I)V", (void*)android_location_GpsLocationProvider_set_agps_server}, 23 {
"native_send_ni_response", "(II)V", (void*)android_location_GpsLocationProvider_send_ni_response}, 24 {
"native_agps_ni_message", "([BI)V", (void *)android_location_GpsLocationProvider_agps_send_ni_message}, 25 {
"native_get_internal_state", "()Ljava/lang/String;", (void*)android_location_GpsLocationProvider_get_internal_state}, 26 {
"native_update_network_state", "(ZIZZLjava/lang/String;Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_update_network_state }, 27 }; 28 29 int register_android_server_location_GpsLocationProvider(JNIEnv* env) 30 { 31 return jniRegisterNativeMethods(env, "com/android/server/location/GpsLocationProvider", sMethods, NELEM(sMethods)); 32 }

 

其中jniRegisterNativeMethods和NELEM都是在头文件JNIHelp.h定义的,得: 

1 #include "JNIHelp.h"

 

在Android.mk文件中得加上:

1 LOCAL_SHARED_LIBRARIES +=libnativehelper

 

 

方法二:使用AndroidRuntime::registerNativeMethods

如~/WORKING_DIRECTORY/frameworks/base/media/jni/android_media_MediaPlayer.cpp:

注:当前目录下其它cpp文件大多采用此种方法进行显式native注册.

 
1 static JNINativeMethod gMethods[] = {   2     {
"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaPlayer_setDataSource}, 3 4 { 5 "_setDataSource", 6 "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V", 7 (void *)android_media_MediaPlayer_setDataSourceAndHeaders 8 }, 9 10 {
"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD}, 11 {
"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface}, 12 {
"prepare", "()V", (void *)android_media_MediaPlayer_prepare}, 13 {
"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync}, 14 {
"_start", "()V", (void *)android_media_MediaPlayer_start}, 15 {
"_stop", "()V", (void *)android_media_MediaPlayer_stop}, 16 {
"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth}, 17 {
"getVideoHeight", "()I", (void *)android_media_MediaPlayer_getVideoHeight}, 18 {
"seekTo", "(I)V", (void *)android_media_MediaPlayer_seekTo}, 19 //... 20 } 21 // This function only registers the native methods 22 static int register_android_media_MediaPlayer(JNIEnv *env) 23 { 24 return AndroidRuntime::registerNativeMethods(env, 25 "android/media/MediaPlayer", gMethods, NELEM(gMethods)); 26 }

 

其中AndroidRuntime::registerNativeMethods是在头文件android_runtime/AndroidRuntime.h中定义,使用时得:

 

1 #include "android_runtime/AndroidRuntime.h"
 

且得在Android.mk文件中加上:

 

 

LOCAL_SHARED_LIBRARIES += \      libandroid_runtime
 

有关函数命名,请参考博客内另一篇文章:。

 

注:以上两种显式注册native方法都只是适用于Android源码开发环境下,至于在Android NDK环境下如何显式注册native方法,暂时还没有研究过,且此两种方法目前NDK还不支持,NDK开发模式下一般采用隐式注册native函数,即接下来要讲的内容.

 

5 隐式注册native方法

前面第3节已经讲到,JNI_OnLoad和JNI_OnUnload函数并不是强制要求实现的,在这种情况下,就相当于在载入.so文件时,没有了初始化函数,既然没有了初始化函数,那显式注册native方法也行不通了。那这个时候又该如何让应用层的java代码调用下层的C函数呢?

    幸运地是,即使我们不使用任何代码做native函数显式注册,应用层的java代码在调用native函数时,也会采用默认的方法到转入的.so文件中的函数列表中查找对应的native 函数,只不过,这个native函数与java类型中声明的native函数的名字之前有一种默认的对应关系。如:

java类的native成员函数:public native int socket_send(int cmdid,String argus);默认会在.so文件中的函数列表中查找jint JNICALL Java_com_hase_bclm_bclm_socket_1send(JNIEnv *env, jobject obj, jint cmdid, jstring argus);函数,一旦找到此对应的函数,VM就会将此native函数自动注册到VM内部的native函数链表中,以便加快后续相同jni调用.

可接下来的问题是,作为码农的我们,又是如何知道java native成员函数对应着C组件中的native 函数的名字呢?简单地函数名字也许我们能搞定,复杂一点的就要傻眼了。同样幸运地是,JDK提供了一个javah工具,可以用这个工具来自动通过.class文件来生成C组件的头文件.

比如你用java写了一个xxx.java文件,里边的java类型里面声明了一些native成员函数,此xxx.java文件编译后会生成xxx.class文件,那么就在你生成的xxx.class包所在目录输入命令行:

 

$javah -jni com.packagename.yourclassname
 

就会在当前目录下生成一个头文件。

 

比如:

 

javah -jni com.test.example

则在当前目录下生成 com_test_example.h头文件.

当前目录下的com结构为:com/test/example.class

再将此头文件拷贝到你的C组件工程内,实现其中声明的native函数即可.

 

 

之前在显式注册native函数的相关章节中已经说明,显式注册是将native函数添加到VM内部的native函数链表中,以加快后续jni调用的效率,其实在隐性native 注册时,每一次执行某个jni调用时,VM在.so函数列表中找到对应的native函数后,同样也会将其注册到VM内部的native函数链表中,由此看到,隐式native注册的方法除了第一次执某个jni调用时会稍微速度慢点外,后续同样的调用就会直接在VM内部的native函数链表中找到对应的native函数,这样看来,显式与隐式注册native方法,其实效率相差无几.

 

本地代码中使用Java对象

通过使用合适的JNI函数,你可以创建Java对象,get、set 静态(static)和 实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数。

下表列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。

函数  描述
GetFieldID  得到一个实例的域的ID
GetStaticFieldID  得到一个静态的域的ID
GetMethodID 得到一个实例的方法的ID
GetStaticMethodID 得到一个静态方法的ID

构造一个Java对象的实例

1 jclass cls = (*env)->FindClass(env, "Lpackagename/classname;");  //创建一个class的引用2 jmethodID id = (*env)->GetMethodID(env, cls, "", "(D)V");  //注意这里方法的名称是"",它表示这是一个构造函数,而且构造参数是double型的3 jobject obj = (*env)->NewObjectA(env, cls, id, args);  //获得一实例,args是构造函数的参数,它是一个jvalue*类型。

 

首先是获得一个Java类的class引用 (*env)->FindClass(env, "Lpackagename/classname;");  请注意参数:Lpackagename/classname; ,L代表这是在描述一个对象类型,packagename/classname是该对象耳朵class路径,请注意一定要以分号(;)结束!

然后是获取函数的id,jmethodID id = env->GetMethodID(cls, "", "(D)V");  第一个是刚刚获得的class引用,第二个是方法的名称,最后一个就是方法的签名

还是不懂?我曾经如此,请接着看...

难理解的函数签名

JNINativeMethod的定义如下:

1 typedef struct {2    const char* name;3    const char* signature;4    void* fnPtr;5 } JNINativeMethod;

 

第一个变量name是Java中函数的名字。 

第二个变量signature,用字符串是描述了函数的参数和返回值 
 第三个变量fnPtr是函数指针,指向C函数。

 

其中比较难以理解的是第二个参数,例如

"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"

实际上这些字符是与函数的参数类型一一对应的。
"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();
"(II)V" 表示 void Func(int, int);

 

那其他情况呢?请查看下表:

类型
符号
boolean Z
byte B
char C
short S
int I
long L
float F
double D
void V
object对象 LClassName;      L类名;
Arrays
[array-type        [数组类型
methods方法 (argument-types)return-type     (参数类型)返回类型

稍稍补充一下:

1、方法参数或者返回值为java中的对象时,签名中必须以“L”加上其路径,不过此路径必须以“/”分开,自定义的对象也使用本规则

比如说 java.lang.String为“java/lang/String”,com.nedu.jni.helloword.Student为"Lcom /nedu/jni/helloword/Student;"

2、方法参数或者返回值为数组类型时,请前加上[

例如[I表示 int[],[[[D表示 double[][][],即几维数组就加几个[

 

在本地方法中调用Java对象的方法

1、获取你需要访问的Java对象的类:

1 jclass cls = (*env)->GetObjectClass(env, obj);       // 使用GetObjectClass方法获取obj对应的jclass。 2 jclass cls = (*env)->FindClass(“android/util/log”) // 直接搜索类名,需要是static修饰的类。

 

2、获取MethodID:

jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "(I)V"); //GetStaticMethodID(…),获取静态方法的ID使用GetMethdoID方法获取你要使用的方法的MethdoID

其参数的意义: 

env-->JNIEnv 
cls-->第一步获取的jclass 
"callback"-->要调用的方法名 
"(I)V"-->方法的Signature, 签名同前面的JNI规则。 

3、调用方法:

(*env)->CallVoidMethod(env, obj, mid, depth);// CallStaticIntMethod(….) , 调用静态方法

 

使用CallVoidMethod方法调用方法。参数的意义: 

env-->JNIEnv 
obj-->通过本地方法穿过来的jobject 
mid-->要调用的MethodID(即第二步获得的MethodID) 
 depth-->方法需要的参数(对应方法的需求,添加相应的参数)

 

注:这里使用的是CallVoidMethod方法调用,因为没有返回值,如果有返回值的话使用对应的方法,在后面会提到。

CallVoidMethod                   CallStaticVoidMethodCallIntMethod                     CallStaticVoidMethodCallBooleanMethod              CallStaticVoidMethodCallByteMethod                   CallStaticVoidMethod

现在稍稍明白文章开始构造Java对象那个实例了吧?让我们继续深入一下:

Jni操作Java的String对象

从java程序中传过去的String对象在本地方法中对应的是jstring类型,jstring类型和c中的char*不同,所以如果你直接当做char*使用的话,就会出错。因此在使用之前需要将jstring转换成为c/c++中的char*,这里使用JNIEnv提供的方法转换。

const char *str = (*env)->GetStringUTFChars(env, jstr, 0);(*env)->ReleaseStringUTFChars(env, jstr, str);

 

这里使用GetStringUTFChars方法将传进来的prompt(jstring类型)转换成为UTF-8的格式,就能够在本地方法中使用了。

注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。
下面是Jni访问String对象的一些方法:

  • GetStringUTFChars          将jstring转换成为UTF-8格式的char*
  • GetStringChars               将jstring转换成为Unicode格式的char*
  • ReleaseStringUTFChars    释放指向UTF-8格式的char*的指针
  • ReleaseStringChars         释放指向Unicode格式的char*的指针
  • NewStringUTF               创建一个UTF-8格式的String对象
  • NewString                    创建一个Unicode格式的String对象
  • GetStringUTFLength      获取UTF-8格式的char*的长度
  • GetStringLength           获取Unicode格式的char*的长度

下面提供两个String对象和char*互转的方法:

1 /* c/c++ string turn to java jstring */ 2 jstring charToJstring(JNIEnv* env, const char* pat) 3 { 4     jclass     strClass = (*env)->FindClass(env, "java/lang/String"); 5     jmethodID  ctorID   = (*env)->GetMethodID(env, strClass, "", "([BLjava/lang/String;)V"); 6     jbyteArray bytes    = (*env)->NewByteArray(env, strlen(pat)); 7     (*env)->SetByteArrayRegion(env, bytes, 0, strlen(pat), (jbyte*)pat); 8     jstring    encoding = (*env)->NewStringUTF(env, "UTF-8"); 9     return (jstring)(*env)->NewObject(env, strClass, ctorID, bytes, encoding);10 }11 12 /* java jstring turn to c/c++ char* */13 char* jstringToChar(JNIEnv* env, jstring jstr)14 {       15     char* pStr = NULL;16     jclass     jstrObj   = (*env)->FindClass(env, "java/lang/String");17     jstring    encode    = (*env)->NewStringUTF(env, "utf-8");18     jmethodID  methodId  = (*env)->GetMethodID(env, jstrObj, "getBytes", "(Ljava/lang/String;)[B");19     jbyteArray byteArray = (jbyteArray)(*env)->CallObjectMethod(env, jstr, methodId, encode);20     jsize      strLen    = (*env)->GetArrayLength(env, byteArray);21     jbyte      *jBuf     = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE);22     if (jBuf > 0)23     {24         pStr = (char*)malloc(strLen + 1);25         if (!pStr)26         {27             return NULL;28         }29         memcpy(pStr, jBuf, strLen);30         pStr[strLen] = 0;31     }32     env->ReleaseByteArrayElements(byteArray, jBuf, 0);33     return pStr;34 }

 

转载地址:http://utefa.baihongyu.com/

你可能感兴趣的文章
python 解析 XML文件
查看>>
MySQL 文件导入出错
查看>>
java相关
查看>>
由一个异常开始思考springmvc参数解析
查看>>
向上扩展型SSD 将可满足向外扩展需求
查看>>
虚机不能启动的特例思考
查看>>
SQL Server编程系列(1):SMO介绍
查看>>
在VMware网络测试“专用VLAN”功能
查看>>
使用Formik轻松开发更高质量的React表单(三)<Formik />解析
查看>>
也问腾讯:你把用户放在什么位置?
查看>>
CSS Sprites 样式生成工具(bg2css)
查看>>
[转]如何重构代码--重构计划
查看>>
类中如何对list泛型做访问器??
查看>>
C++解析XML--使用CMarkup类解析XML
查看>>
P2P应用层组播
查看>>
Sharepoint学习笔记—修改SharePoint的Timeouts (Execution Timeout)
查看>>
CSS引入的方式有哪些? link和@import的区别?
查看>>
Redis 介绍2——常见基本类型
查看>>
asp.net开发mysql注意事项
查看>>
(转)Cortex-M3 (NXP LPC1788)之EEPROM存储器
查看>>