zoukankan      html  css  js  c++  java
  • JNI 学习笔记

    JNI是Java Native Interface的缩写,JNI是一种机制,有了它就可以在java程序中调用其他native代码,或者使native代码调用java层的代码。也 就是说,有了JNI我们可以使Android项目中,java层与native层各自发挥所长并相互配合。如下图所示,JNI在Android中所处的位 置。

      

    JNI相对与native层来说是一个接口,java层的程序想访问native层,必须通过JNI,反过来也一样。下面我们来看几个问题。

    1,如何告诉VM(虚拟机)java层需要调用native层的哪些libs?

            我们知道java程序是运行在VM上的,而Native层的libs则不然。所以为了让java层能访问native层的libs,必须得告诉VM要使用哪些native层的libs。下面看一段代码

        public class MediaPlayer    
         {    
             ...    
             
             static {    
                 System.loadLibrary("media_jni");    
                 native_init();    
             }    
             
              ...    
             
             private native final void native_init();    
                  
              ...    
          }  

     

    可以看到上面的代码中,在MediaPlayer类中有一段static块包围起来的代码, 其中System.loadLibrary("media_jni")就是告诉VM去加载libmedia_jni.so这个动态库,那么这个动态库什么 时候被加载呢?因为static语句块的原因,所以在MediaPlayer第一次实例化的时候就会被加载了。这段代码中,我们还看到了一个函数 native_init(),该函数被申明为native型,就是告诉VM该函数由native层来实现。

     

     

    2,如何做到java层到native层的映射。

     

           所谓Java 层到native层的映射就是说JVM在调用 void native_init() 这个函数时,该怎么去找相应的动态链接库里面的函数。因为void native_init () 函数并没有在java文件中定义。

      这里有2种办法,

      一种是我们在C 文件中按照一定的命名规则来定义函数。

      假设上面的MediaPlayer类在android.media包内。

      我们的native层的代码可如下定义

     

      /* DO NOT EDIT THIS FILE - it is machine generated */
      #include <jni.h>
      /* Header for class MediaPlayer */
      
      #ifndef _Included_MediaPlayer
      #define _Included_MediaPlayer
      #ifdef __cplusplus
      extern "C" {
      #endif
      /*
      * Class:   MediaPlayer
      * Method:  init media player
      * Signature: ()V
      */
      JNIEXPORT void JNICALL Java_android_media_MediaPlayer_native_init
      (JNIEnv *, jobject);
      
      #ifdef __cplusplus
      }
      #endif
      #endif

     

      其中jobject参数是JVM的MediaPlayer调用native_init时的对象,JNIEnv* 是一个全局环境结构。

      这个命名规则,想必很容易看懂,两个修饰符JNIEXPORT JNICALL,应该分别用来兼容so和dll的导出函数的,JNICALL是用来兼容stdcall 等这些不同的函数调用方式的。

    函数名字以Java开头,后面跟着的是声明这个函数的类的全路径,包括包名,最后是函数名。

      可以发现,这个函数名实在是很丑陋,难以阅读。

      第二种方法,我们可以进行动态注册:

      我们来看看Android源码里的MediaPlayer的native是怎么写的。

      当VM执行到System.loadLibrary()的时候就会去执行native libs中的JNI_OnLoad(JavaVM* vm, void* reserved)函数,因为JNI_OnLoad函数是从java层进入native层第一个调用的方法,所以可以在JNI_OnLoad函数中完成一些native层组件的初始化工作,同时更加重要的是,通常在JNI_jint JNI_OnLoad(JavaVM* vm, void* reserved)函数中会注册java层的native方法。下面看一段代码:

      

        jint JNI_OnLoad(JavaVM* vm, void* reserved)  
        {  
            JNIEnv* env = NULL;  
            jint result = -1;  
            //判断一下JNI的版本   
            if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
                LOGE("ERROR: GetEnv failed
    ");  
                goto bail;  
            }  
            assert(env != NULL);  
          
            if (register_android_media_MediaPlayer(env) < 0) {  
                LOGE("ERROR: MediaPlayer native registration failed
    ");  
                goto bail;  
            }  
          
            if (register_android_media_MediaRecorder(env) < 0) {  
                LOGE("ERROR: MediaRecorder native registration failed
    ");  
                goto bail;  
            }  
          
            if (register<span style="font-size:16px;">_android_media_MediaScanner(env) < 0) {  
                LOGE("ERROR: MediaScanner native registration failed
    ");  
                goto bail;  
            }</span>  
          
            if (register_android_media_MediaMetadataRetriever(env) < 0) {  
                LOGE("ERROR: MediaMetadataRetriever native registration failed
    ");  
                goto bail;  
            }  
          
            if (register_android_media_AmrInputStream(env) < 0) {  
                LOGE("ERROR: AmrInputStream native registration failed
    ");  
                goto bail;  
            }  
          
            if (register_android_media_ResampleInputStream(env) < 0) {  
                LOGE("ERROR: ResampleInputStream native registration failed
    ");  
                goto bail;  
            }  
          
            if (register_android_media_MediaProfiles(env) < 0) {  
                LOGE("ERROR: MediaProfiles native registration failed");  
                goto bail;  
            }  
          
            /* success -- return valid version number */  
            result = JNI_VERSION_1_4;  
          
        bail:  
            return result;  
        }  

     

    上面这段代码的JNI_OnLoad(JavaVM* vm, void* reserved)函数实现与libmedia_jni.so库中。上面的代码中调用了一些形如register_android_media_MediaPlayer(env)的函数,这些函数的作用是注册native method。我们来看看函数register_android_media_MediaPlayer(env)的实现。

        // This function only registers the native methods  
        static int register_android_media_MediaPlayer(JNIEnv *env)  
        {  
            return AndroidRuntime::registerNativeMethods(env,  
                        "android/media/MediaPlayer", gMethods, NELEM(gMethods));  
    』  }

     

        /* 
         * 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);  
        }  

    最终jniRegisterNativeMethods函数完成java标准的native函数的映射工作。下面我们来具体的看看上面这个函数中各个参数的意义。

    关于JNIEnv我在google上找到了这些信息:

    JNI defines two key data structures, "JavaVM" and "JNIEnv". Both of these are essentiallypointers to pointers to function tables. (In the C++ version, they're classes with apointer to a function table and a member function for each JNI function that indirects throughthe table.) The JavaVM provides the "invocation interface" functions,which allow you to create and destroy a JavaVM. In theory you can have multiple JavaVMs per process,but Android only allows one.

    The JNIEnv provides most of the JNI functions. Your native functions all receive a JNIEnv asthe first argument.

    The JNIEnv is used for thread-local storage. For this reason, you cannot share a JNIEnv between threads.If a piece of code has no other way to get its JNIEnv, you should sharethe JavaVM, and useGetEnv to discover the thread's JNIEnv. (Assuming it has one; see AttachCurrentThread below.)

    这里需要注意一点的是,JNIEnv是一个线程的局部变量,这以为这JNIEnv是存在与多线程环境下的,因为 VM 通常是多执行绪(Multi-threading)的执行环境。每一个执行绪在呼叫JNI_OnLoad()时,所传递进来的 JNIEnv 指标值都是不同的。为了配合这种多执行绪的环境,C/C++组件开发者在撰写本地函数时,可藉由 JNIEnv 指标值之不同而避免执行绪的资料冲突问题,才能确保所写的本地函数能安全地在 Android 的多执行绪 VM 里安全地执行。基于这个理由,当在
    呼叫 C/C++ 组件的函数时,都会将 JNIEnv 指标值传递给它。

    b,char* className,这个没什么好说的,java空间中类名,其中包含了包名。

    c,JNINativeMethod* gMethods,传递进去的是一个JNINativeMethod类型的指针gMethods,gMethods指向一个JNINativeMethod数组,我们先看看JNINativeMethod这个结构体。

     

        typedef struct {  
         const char* name; /*Java 中函数的名字*/  
         const char* signature; /*描述了函数的参数和返回值*/  
         void* fnPtr; /*函数指针,指向 C 函数*/  
         } JNINativeMethod;  

    再来看看gMethods数组

        static JNINativeMethod gMethods[] = {  
            {"setDataSource",       "(Ljava/lang/String;)V",            (void *)android_media_MediaPlayer_setDataSource},  
            。。。  
            {"setAuxEffectSendLevel", "(F)V",                           (void *)android_media_MediaPlayer_setAuxEffectSendLevel},  
            {"attachAuxEffect",     "(I)V",                             (void *)android_media_MediaPlayer_attachAuxEffect},  
            {"getOrganDBIndex",     "(II)I",                            (void *)android_media_MediaPlayer_getOrganDBIndex},  
        };  

    在JNINativeMethod的结构体中,有一个描述函数的参数和返回值的签名字段,它是java中对应函数的签名信息,由参数类型和返回值类型共同组成。这个函数签名信息的作用是什么呢?

           由于java支持函数重载,也就是说,可以定义同名但不同参数的函数。然而仅仅根据函数名是没法找到具体函数的。为了解决这个问题,JNI技术中就将参数 类型和返回值类型的组合作为一个函数的签名信息,有了签名信息和函数名,就能顺利的找到java中的函数了。

            JNI规范定义的函数签名信息格式如下:

            (参数1类型标示参数2类型标示......参数n类型标示)返回值类型标示

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

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

            值得注意的一点是,当参数类型是引用数据类型时,其格式是“L包名;”其中包名中的“.” 换成“/”,所以在上面的例子中(Ljava/lang/String;Ljava/lang/String;)V 表示 void Func(String,String);

             如果 JAVA 函数位于一个嵌入类,则用$作为类名间的分隔符。

             例如 “(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z”

      下表是对应关系

    JAVA类型 本地类型 对应的描述字符串 描述
    boolean jboolean Z C/C++8位整型
    byte jbyte B C/C++带符号8bit integer
    char jchar C C/C++ 无符号16位整型
    short jshort S C/C++ 带符号的16位整型
    int jint I C/C++ 带符号的32位整型
    long jlong J C/C++ 带符号的64位整型
    float jfloat F C/C++ 32位浮点数
    double jdouble D C/C++ 64位浮点数
    Object jobject   任何Java对象
    Class jclass   Class 对象
    String jstring   字符串对象
    Object[] jobjectArray   任何对象的数组
    boolean[] jbooleanArray [Z 布尔型数组
    byte[] jbyteArray [B byte型数组
    char[] jcharArray [C 短整型数组
    short[] jshortArray [S 整型数组
    int[] jintArray [I 长整型数组
    long[] jlongArray [J 浮点型数组
    float[] jfloatArray [F 双精度浮点数数组
    double[] jdoubleArray [D  
    void   void V  

     

    java层和JNI层应该是可以互相交互,我们通过java层中的native函数可以进入到JNI层,那么JNI层的代码能不能操作java层中函数呢?当然可以,通过JNIEnv。

    先来看看两个函数原型

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

    结合前面的知识来看,JNIEnv是一个与线程相关的代表JNI环境的结构体。JNIEnv实际上提供了一些JNI系统函数。通过这些系统函数可以调用java层中的函数或者操作jobect。下面我看一段函数

        class MyMediaScannerClient : public MediaScannerClient  
        {  
        public:  
            MyMediaScannerClient(JNIEnv *env, jobject client)  
                :   mEnv(env),  
                    mClient(env->NewGlobalRef(client)),  
                    mScanFileMethodID(0),  
                    mHandleStringTagMethodID(0),  
                    mSetMimeTypeMethodID(0)  
            {  
                jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient");  
                if (mediaScannerClientInterface == NULL) {  
                    fprintf(stderr, "android/media/MediaScannerClient not found
    ");  
                }  
                else {  
                    mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile",  
                                                             "(Ljava/lang/String;JJ)V");  
                    mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag",  
                                                             "(Ljava/lang/String;Ljava/lang/String;)V");  
                    mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface, "setMimeType",  
                                                             "(Ljava/lang/String;)V");  
                    mAddNoMediaFolderMethodID = env->GetMethodID(mediaScannerClientInterface, "addNoMediaFolder",  
                                                             "(Ljava/lang/String;)V");  
                }  
            }  
        ...  
          
        // returns true if it succeeded, false if an exception occured in the Java code  
            virtual bool scanFile(const char* path, long long lastModified, long long fileSize)  
            {  
                jstring pathStr;  
                if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;  
          
                mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);  
          
                mEnv->DeleteLocalRef(pathStr);  
                return (!mEnv->ExceptionCheck());  
            }  
        class MyMediaScannerClient : public MediaScannerClient  
        {  
        public:  
            MyMediaScannerClient(JNIEnv *env, jobject client)  
                :   mEnv(env),  
                    mClient(env->NewGlobalRef(client)),  
                    mScanFileMethodID(0),  
                    mHandleStringTagMethodID(0),  
                    mSetMimeTypeMethodID(0)  
            {  
                jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient");  
                if (mediaScannerClientInterface == NULL) {  
                    fprintf(stderr, "android/media/MediaScannerClient not found
    ");  
                }  
                else {  
                    mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile",  
                                                             "(Ljava/lang/String;JJ)V");  
                    mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag",  
                                                             "(Ljava/lang/String;Ljava/lang/String;)V");  
                    mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface, "setMimeType",  
                                                             "(Ljava/lang/String;)V");  
                    mAddNoMediaFolderMethodID = env->GetMethodID(mediaScannerClientInterface, "addNoMediaFolder",  
                                                             "(Ljava/lang/String;)V");  
                }  
            }  
        ...  
          
        // returns true if it succeeded, false if an exception occured in the Java code  
            virtual bool scanFile(const char* path, long long lastModified, long long fileSize)  
            {  
                jstring pathStr;  
                if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;  
          
                mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);  
          
                mEnv->DeleteLocalRef(pathStr);  
                return (!mEnv->ExceptionCheck());  
            }  

    可以看到上面的代码中,先找到java层中MediaScannerClinet类在JNI层中对应的jclass实例(通过FindClass)。然后拿到MediaScannerclient类中所需要用到函数的函数函数id(通过GetMethodID)。接着通过JNIEnv调用CallXXXMethod函数并且把对应的jobject,jMethodID还有对应的参数传递进去,这样的通过CallXXXMethod就完成了JNI层向java层的调用。这里要注意一点的是这里JNI层中调用的方法实际上是java中对象的成员函数,如果要调用static函数可以使用CallStaticXXXMethod。这种机制有利于native层回调java代码完成相应操作。

    上面讲述了如下在JNI层中去调用java层的代码,那么理所当然的应该可以在JNI层中访问或者修改java层中某对象的成员变量的值。我们通过JNIEnv中的GetFieldID()函数来得到java中对象的某个域的id。看下面的具体代码

        int register_android_backup_BackupHelperDispatcher(JNIEnv* env)  
        {  
            jclass clazz;  
          
            clazz = env->FindClass("java/io/FileDescriptor");  
            LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor");  
            s_descriptorField = env->GetFieldID(clazz, "descriptor", "I");  
            LOG_FATAL_IF(s_descriptorField == NULL,  
                    "Unable to find descriptor field in java.io.FileDescriptor");  
              
            clazz = env->FindClass("android/app/backup/BackupHelperDispatcher$Header");  
            LOG_FATAL_IF(clazz == NULL,  
                    "Unable to find class android.app.backup.BackupHelperDispatcher.Header");  
            s_chunkSizeField = env->GetFieldID(clazz, "chunkSize", "I");  
            LOG_FATAL_IF(s_chunkSizeField == NULL,  
                    "Unable to find chunkSize field in android.app.backup.BackupHelperDispatcher.Header");  
            s_keyPrefixField = env->GetFieldID(clazz, "keyPrefix", "Ljava/lang/String;");  
            LOG_FATAL_IF(s_keyPrefixField == NULL,  
                    "Unable to find keyPrefix field in android.app.backup.BackupHelperDispatcher.Header");  
              
            return AndroidRuntime::registerNativeMethods(env, "android/app/backup/BackupHelperDispatcher",  
                    g_methods, NELEM(g_methods));  
        }  

    获得jfieldID之后呢,我们就可以在JNI层之间来访问和操作java层的field的值了,方法如下

        NativeType Get<type>Field(JNIEnv *env,jobject object,jfieldID fieldID)  
          
        void Set<type>Field(JNIEnv *env,jobject object ,jfieldID fieldID,NativeType value)  

    现在我们看到有了JNIEnv,我们可以很轻松的操作jobject所代表的java层中的实际的对象了。

    jstring介绍

             之所以要把jstring单独拿出来说正是由于它的特殊性。java中String类型也是一个引用类型,但是JNI中并没有用jobject来与之对 应,JNI中单独创建了一个jstring类型来表示java中的String类型。显然java中的String不能和C++中的String等同起 来,那么怎么操作jstring呢?方法很多下面看几个简单的方法

    1,调用JNIEnv的NewStringUTF将根据Native的一个UTF-8字符串得到一个jstring对象。只有这样才能让一个C++中String在JNI中使用。

    2,调用JNIEnv的GetStringChars函数(将得到一个Unicode字符串)和GetStringUTFChars函数(将得到一个UTF-8字符串),他们可以将java String对象转换诚本地字符串。下面我们来看段示例代码。

        virtual bool scanFile(const char* path, long long lastModified, long long fileSize)  
            {  
                jstring pathStr;  
                //将char*数组字符串转换诚jstring类型  
                if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;  
          
                mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);  
          
                mEnv->DeleteLocalRef(pathStr);  
                return (!mEnv->ExceptionCheck());  
            }  
          
              ....  
              ....  
        while (env->CallBooleanMethod(iter, hasNext)) {  
                    jobject entry = env->CallObjectMethod(iter, next);  
                    jstring key = (jstring) env->CallObjectMethod(entry, getKey);  
                    jstring value = (jstring) env->CallObjectMethod(entry, getValue);  
          
                    const char* keyStr = env->GetStringUTFChars(key, NULL);  
                    ...  
                    ...  

    GetStringUTFChars()函数将jstring类型转换成一个UTF-8本地字符串,另外如果代码中调用了上面的几个函数,则在做完相关工 作后,要调用ReleaseStringChars函数或者ReleaseStringUTFChars函数来释放资源。看下面的代码

    ...  
                ...  
                jstring key = (jstring) env->CallObjectMethod(entry, getKey);  
                jstring value = (jstring) env->CallObjectMethod(entry, getValue);  
      
                const char* keyStr = env->GetStringUTFChars(key, NULL);  
                if (!keyStr) {  // Out of memory  
                    jniThrowException(  
                            env, "java/lang/RuntimeException", "Out of memory");  
                    return;  
                }  
      
                const char* valueStr = env->GetStringUTFChars(value, NULL);  
                if (!valueStr) {  // Out of memory  
                    jniThrowException(  
                            env, "java/lang/RuntimeException", "Out of memory");  
                    return;  
                }  
      
                headersVector.add(String8(keyStr), String8(valueStr));  
      
                env->DeleteLocalRef(entry);  
                env->ReleaseStringUTFChars(key, keyStr);  
                env->DeleteLocalRef(key);  
                ...  
                ...  

    可以看到GetStringUTFChars与下面的ReleaseStringUTFChars对应。

    主要是转载自

    http://blog.csdn.net/mci2004/article/details/7211678

    http://blog.csdn.net/mci2004/article/details/7219140

  • 相关阅读:
    Java实现 蓝桥杯VIP 算法训练 字符删除
    Java实现 蓝桥杯VIP 算法训练 字符删除
    Java实现 蓝桥杯VIP 算法训练 字符删除
    Java实现 蓝桥杯VIP 算法训练 字符删除
    Java实现 蓝桥杯VIP 算法训练 字符删除
    Java实现 蓝桥杯VIP 算法训练 字符串编辑
    Java实现 蓝桥杯VIP 算法训练 字符串编辑
    Java实现 蓝桥杯VIP 算法训练 字符串编辑
    Java实现 蓝桥杯VIP 算法训练 字符串编辑
    Java实现 蓝桥杯VIP 算法训练 字符串编辑
  • 原文地址:https://www.cnblogs.com/tlm1992/p/3253423.html
Copyright © 2011-2022 走看看