Android Jni 用动态库的加载与卸载函数说明

来源:互联网 发布:美即黑茶酵力面膜知乎 编辑:程序博客网 时间:2024/05/18 22:10
一、当 Android 的 Virtual Machine 执行到 System.loadLibrary( "动态库名" ) 函数时,
首先会去执行 C 语言动态库里的 JNI_OnLoad 函数。
它的用途有两个:
1)告诉 Virtual Machine 当前动态库使用了哪个版本的 Jni。
  如果当前动态库中没有提供 JNI_OnLoad 函数,
  Virtual Machine 会默认为动态库使用的是最老的 Jni 1.1 版本。
  由于新版 Jni 做了许多扩充,例如 Jni 1.4 的 java.nio.ByteBuffer。

2)动态库的开发者可以在 JNI_OnLoad 函数中进行动态库内的初始化设置(Initialization),
  将此动态库中提供的各个本地函数(Native Function)登记到 Virtual Machine 里,
  以便能加快以后调用动态库中的本地函数的效率,就是初始化设置的重要一项。
  应用层级的 Java 类通过 Virtual Machine 才能调用到动态库中的本地函数。
  如果没有注册登记过的话,Virtual Machine 就在 动态库名.so 里寻找要调用的本地函数。
  如果需要连续调用很多次且每次都需要寻找一遍的话,会多花许多时间。
  因此 C 语言动态库开发者可以自已将动态库中的本地函数向 Virtual Machine 进行注册登记。

代码示例:(注:由于新浪博客不支持 C 注释,所以请将 /^ ^/ 想像替换为 /星 星/)
jint
JNI_OnLoad( JavaVM* vm,
            void*   reserved )
{
    jint    jintResult = -1;
    JNIEnv* env = NULL;

    /^Reference types, in C.
       typedef void*   jobject;
       typedef jobject jclass; ^/

    jclass  cls = NULL;

    /^ typedef struct {
           const char* name;      /^ Java 代码中调用的函数名字 ^/
           const char* signature; /^ 描述了函数的 参数 和 返回值 ^/
           void*       fnPtr;     /^ 函数指针转成无符号指针 ^/
       } JNINativeMethod;
       其中比较复杂的是第二个参数,
       例如 "()V" 或 "(II)V" 或 "(Ljava/lang/String;)V"
       实际上这些字符是与函数的 参数 及 返回值 类型是一一对应的,
       括号()中的字符表示参数,括号后面的则代表返回值,
       例如 "()V" 就表示 void 函数名();
          "(II)V" 就表示 void 函数名( int, int );
          "(Ljava/lang/String;)V" 就表示 void 函数名( jstring );
       具体的每一个字符所表示的意义下面部分有所详见 ^/
    /^ 动态库中的本地函数信息数组 ^/

    JNINativeMethod aJNINativeMethod[] = {
        { "MeasureDistance",
          "(Ljava/lang/String;)V",
          (void*)Java_MyJni_MyNDK_MyDemo_MyJniNDKDemo_MeasureDistance }
    };

     /^ #if defined(__cplusplus)
           typedef _JNIEnv JNIEnv;
           typedef _JavaVM JavaVM;
       #else
           typedef const struct JNINativeInterface* JNIEnv;
           typedef const struct JNIInvokeInterface* JavaVM;
       #endif ^/

    /^ JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint); ^/
    /^ GetEnv()函数返回的 Jni 环境对每个线程来说是不同的,^/
    /^ 因此我们必须在每次进入函数时都要重新获取 ^/

    if ( JNI_OK != (*vm)->GetEnv( vm,
                                  (void**)env,
                                  JNI_VERSION_1_6 ) )
    {
        /^ 输出的 log 一般是到 /dev/log/ 下的三个设备中,可以用 logcat 工具查看 ^/
        __android_log_write( ANDROID_LOG_INFO,               /^ 日志信息 ^/
                             "MyJniDemo",                    /^ 日志标签 ^/
                             "Call JavaVM::GetEnv failed" ); /^ 日志内容 ^/
       return jintResult; /^ 此时返回的是负壹(-1) ^/
    }

    /^ 如果 将动态库中的本地函数向 Virtual Machine 进行注册登记失败 的话,则 ^/
    /^ 由于 aJNINativeMethod 是一组 函数名称 与 函数指针 的对照表,
       在程序执行期间可以多次调用registerNativeMethods函数来更换注册登记本地函数 ^/

    /^ #if defined(__cplusplus)
           typedef _JNIEnv JNIEnv;
           typedef _JavaVM JavaVM;
       #else
           typedef const struct JNINativeInterface* JNIEnv;
           typedef const struct JNIInvokeInterface* JavaVM;
       #endif ^/
    /^ Reference types, in C.
       typedef void*   jobject;
       typedef jobject jclass; ^/

    /^ struct JNINativeInterface 里的函数指针
       jint (*RegisterNatives)( JNIEnv*,
                                jclass,
                                const JNINativeMethod*,
                                jint ); ^/
    cls = (*env)->FindClass( env,
                             /^ 下面的字符串就是描述 Java 代码中的主类 ^/
                             "MyJni/MyNDK/MyDemo/MyJniNDKDemo" );
    if ( 0 > (*env)->RegisterNatives( env,
                                      cls,
                                      aJNINativeMethod,
                                      sizeof( aJNINativeMethod ) /
                                      sizeof( aJNINativeMethod[0] ) ) )
    {
        /^ 输出的 log 一般是到 /dev/log/ 下的三个设备中,可以用 logcat 工具查看 ^/
        __android_log_write( ANDROID_LOG_INFO,                   /^日志信息^/
                             "MyJniDemo",                        /^日志标签^/
                             "Register native methods failed" ); /^日志内容^/
       return jintResult; /^ 此时返回的是负壹(-1) ^/
    }

   
    jintResult = JNI_VERSION_1_6;

    /^ 此函数回传 JNI_VERSION_1_6 宏值给 Virtual Machine,
       于是 Virtual Machine 就知道当前动态库所使用的 Jni 版本了 ^/

    return jintResult; /^ JNI_VERSION_1_6(0x00010006) ^/
}

 
二、JNI_OnUnload 函数与 JNI_OnLoad 函数相对应。
在 JNI_OnLoad 函数中进行的动态库内的初期化设置,
要在 Virtual Machine 释放该动态库时调用 JNI_OnUnload 函数来进行善后清除。
同 Virtual Machine 调用 JNI_OnLoad 一样,
调用 JNI_Unload 函数时,也会将 JavaVM 的指针做为第一个参数传递,原型如下:
jint
JNI_OnUnload( JavaVM* vm,
              void*   reserved );

三、JNINativeMethod::signature 描述字符串字符意义说明:
1)基本类型对应关系:
标识符  Jni 类型       C 类型
  V    void           void
  Z    jboolean       boolean
  I    jint           int
  J    jlong          long
  D    jdouble        double
  F    jfloat         float
  B    jbyte          byte
  C    jchar          char
  S    jshort         short

2)基本类型数组:(则以 [ 开始,用两个字符表示)
标识串  Jni 类型        C 类型
  [Z   jbooleanArray  boolean[]
  [I   jintArray      int[]
  [J   jlongArray     long[]
  [D   jdoubleArray   double[]
  [F   jfloatArray    float[]
  [B   jbyteArray     byte[]
  [C   jcharArray     char[]
  [S   jshortArray    short[]

3)类(class):(则以 L 开头,以 ; 结尾,中间是用 / 隔开的 包 及 类名)
标识串        Java 类型  Jni 类型
L包1/包n/类名;     类名     jobject
例子:
Ljava/net/Socket; Socket      jobject

4)例外(String 类):
标识串               Java 类型  Jni 类型
Ljava/lang/String;  String    jstring

5)嵌套类(类位于另一个类之中,则用$作为类名间的分隔符)
标识串                         Java 类型  Jni 类型
L包1/包n/类名$嵌套类名;              类名      jobject
例子:

Landroid/os/FileUtils$FileStatus;  FileStatus  jobject


来源:

http://blog.csdn.net/yohoom/article/details/8133132


ps:推荐向jvm注册本地方法,好处:1.提高调用效率;2.便于错误检查,注册不了那就是哪里写错了;3.便于维护,比如改个类名什么的;


原创粉丝点击