zoukankan      html  css  js  c++  java
  • 框架基础JNI

    转载请标明出处: 


    2.1 概述

    JNI(Java Native Interface)。这是一个既熟悉又陌生的名词。熟悉是由于java之中JNI技术很常见;陌生是由于绝大多数时候我们并没有关心这个技术在java中是怎样使用的。
    先看一下在J2SE中的File类中一个方法:setLastModified方法
       public boolean setLastModified(long time) {
            if (time < 0) {
                throw new IllegalArgumentException("time < 0");
            }
            return setLastModifiedImpl( path, time);//调用本地方法setLastModifiedImpl方法
        }
       private static native boolean setLastModifiedImpl(String path, long time);
    我们能够看出setLastModifiedImpl的声明方式非常像抽象函数,仅仅有函数名称,没有函数实现。


    在J2SE中,这样的native方法非常多。这里为什么要使用这样的技术呢?
    我们知道java语言不能操作訪问硬件,硬件的訪问是依靠Native语言(通常是C/C++语言)。可是不同的平台有不同的时间机制:比方说打开一个文件。在windows中使用openFile函数,在Linux中则是使用open函数。假设在编写java语言时还要考虑跨平台。那显然不符合java"一次编写,处处执行"的思想。那应该怎么办呢?
    java是能够跨平台的,java跨平台的基础就是JVM在不同的详细平台上的不同实现,JVM是不跨平台的,也就是说JVM本身无法做到与平台无关,必须在不同的平台之上有不同的实现机制。那么JNI的目的就是对java层屏蔽不同平台之间的差异。java中打开一个文件。仅仅须要声明它是一个native方法,不须要关心是执行在哪个平台之上的,这个工作由虚拟机来选择。

    不同平台的虚拟机有自己的实现方式。而java层不须要关心平台的差异。

    这仅仅是JNI的一个功能:在java中调用native语言;还有一个功能就是native语言能够訪问java层,能够看出JNI的作用就是连接java层和native层:


    也就是java和native通过JNI的方式连接起来。
    以下我们来先两个android中JNI使用的样例。


    2.2 android中JNI实例分析
    以下介绍两个样例:一个是开发过程中很常见。用来打印日志信息的android.util.Log类;一个是android系统用来扫描多媒体文件的MediaScanner类

    2.2.1Log类jni实例分析
    这个类在开发过程中经经常使用它来打印日志信息:
    Log.d("tag","Msg");
    我们先看一个Log类在java层的实现:
    public final class Log{
      public static int d(String tag, String msg) {
            return println_native( LOG_ID_MAIN, DEBUG , tag, msg);//调用本地方法println_native
        }
      public static native int println_native(int bufID, int priority, String tag, String msg);
    }
    java层声明了native方法:println_native,它们在JNI层中是怎样实现?

    看它相应的jni代码就可以。

    一个非常实际的问题是:我们去哪里找他们的JNI层实现,这里先给出答案,后面会讨论这个问题
    Log类的JNI文件是frameworksasecorejniandroid_util_Log.cpp。当中println_native方法:
    android_util_Log.cpp中println_native函数的实现:
    /*
    * In class android.util.Log:
    *  public static native int println_native(int buffer, int priority, String tag, String msg)
    */
    static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
            jint bufID, jint priority, jstring tagObj, jstring msgObj)
    {
       //比java层的println_native 函数多了两个參数JNIEnv* env, jobject clazz,其它的參数和java层一一相应。
        const char* tag = NULL;
        const char* msg = NULL;
    
        if (msgObj == NULL) {
            //异常处理,后面会涉及到
            jniThrowNullPointerException(env, "println needs a message");
            return -1;
        }
    
        if (bufID < 0 || bufID >= LOG_ID_MAX) {
            jniThrowNullPointerException(env, "bad bufID");
            return -1;
        }
    
        if (tagObj != NULL)
          // 将java 中String对象转换成本地UTF-8字符串
            tag = env->GetStringUTFChars(tagObj, NULL);
            msg = env->GetStringUTFChars(msgObj, NULL);
    
         //继续调用本地方法 __android_log_buf_write。
        int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
    
        if (tag != NULL)
            //使用完之后要释放资源。否则导致JVM内存泄露
            env->ReleaseStringUTFChars(tagObj, tag);
        env->ReleaseStringUTFChars(msgObj, msg);
    
        return res;
    }
    这里实际是调用本地方法__android_log_buf_write函数进行打印(兴许更新__android_log_buf_write),如今仅仅须要知道java层的println_native方法的jni层实现就是android_util_Log_println_native函数就可以。


    回到刚才那个问题:java层的声明的println_native方法和native层的android_util_Log_println_native是怎样关联在一起的呢?
    我们发如今frameworksasecorejniandroid_util_Log.cpp文件里有一个register_android_util_Log方法,推測可能和两者关联有关:
    int register_android_util_Log(JNIEnv* env)
    {
        jclass clazz = env->FindClass("android/util/Log");
    
        if (clazz == NULL) {
            LOGE("Can't find android/util/Log");
            return -1;
        }
        //通过jni操作java相应。后面会介绍。
        levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
        levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
        levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
        levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
        levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
        levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
        //调用了registerNativeMethod方法
        return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
    }
    当中gMethods是一个数组:
    /*
    * JNI registration.
    */
    static JNINativeMethod gMethods[] = {
        /* name, signature, funcPtr */
        { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
        { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
    };
    当中JNINativeMethod 是一个结构体类型。在jni.h文件之中
    typedef struct {
        const char* name;    //java层的native函数的名称
        const char* signature;    //该native函数的签名(返回值+參数列表)。由于java中支持函数重载,所以函数名称+函数签名才干明白确定一个函数
        void*       fnPtr;    //函数指针,指向jni层该相应的函数实现
    } JNINativeMethod;
    println_native函数出如今gMethods数组之中
    { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native }
    "println_native",  //相应java层的函数名称为println_native的函数
    "(IILjava/lang/String;Ljava/lang/String;)I",//该函数签名为(IILjava/lang/String;Ljava/lang/String;)I,关于函数签名后面会介绍含义
     (void*) android_util_Log_println_native//该函数在jni层实现的方法指针为 (void*) android_util_Log_println_native

    了解gMethods之后。register_android_util_Log中调用了registerNativeMethods方法,该方法在frameworksasecorejniAndroidRuntime.cpp
    /*
    * 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方法,该方法在:
    dalviklibnativehelper.JNIHelp.cpp文件里:
    extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
        const JNINativeMethod* gMethods, int numMethods)
    {
        JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
        LOGV("Registering %s natives", className);
        scoped_local_ref<jclass> c(env, findClass(env, className));
        if (c.get() == NULL) {
            LOGE("Native registration unable to find class '%s', aborting", className);
            abort();
        }
        if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
            LOGE("RegisterNatives failed for '%s', aborting", className);
            abort();
        }
        return 0;
    }
    终于调用RegisterNatives(JNIEnv *env,jclass clazz,const JNINativeMethod *method,jint nMethods)方法,该方法向clazz类注冊在method数组中本地方法方法。这样虚拟机就能够建立起java层和jni层的两个函数之间的相应关系。

    如今我们已经知道register_android_util_Log方法能够完毕println_native在java层和jni层的映射,可是在那么调用了register_android_util_Log方法呢?

    在AndroidRuntime.cpp中的register_jni_procs方法。该方法会调用register_android_util_Log方法。关于这部分后面会介绍


    2.2.1 MediaScanner
    看完Log,我们再看一个样例:MediaScanner
    java层相应的是MediaScanner,里面定义了一些函数须要native层来实现
    先看java层的MediaScanner:
    public class MediaScanner{
    static {
            System.loadLibrary("media_jni");
            native_init();
        }
    ...
     private static native final void native_init();
     private native void processFile(String path, String mimeType, MediaScannerClient client);
    }
    MediScanner在jni层相应的是frameworksasemediajniandroid_media_MediaScanner.cpp
    java层中的native_init在jni层的实现
    // This function gets a field ID, which in turn causes class initialization.
    // It is called from a static block in MediaScanner, which won't run until the
    // first time an instance of this class is used.
    //java层中native_init 在jni层的实现
    static void android_media_MediaScanner_native_init(JNIEnv *env)
    {
        LOGV("native_init");
         // kClassMediaScannerClient = "android/media/MediaScannerClient";
        jclass clazz = env->FindClass(kClassMediaScanner);
        if (clazz == NULL) {
            return;
        }
        //在fields.context 中保存int类型的mNativeContext成员变量的fieldId
        fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
        if (fields.context == NULL) {
            return;
        }
    }
    java层中processFile在jni层的实现
    static void android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path,
            jstring mimeType, jobject client)
    {
        LOGV("processFile");
        // Lock already hold by processDirectory
       
        MediaScanner *mp = getNativeScanner_l(env, thiz);
    
        if (mp == NULL) {
           //异常处理。后面会介绍异常处理    
            jniThrowException(env, kRunTimeException, "No scanner available");
            return;
        }
    
        if (path == NULL) {
             //异常处理       
            jniThrowException(env, kIllegalArgumentException, NULL);
            return;
        }
    
        const char *pathStr = env->GetStringUTFChars(path, NULL);
        if (pathStr == NULL) {  // Out of memory
            return;
        }
    
        const char *mimeTypeStr = (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
        if (mimeType && mimeTypeStr == NULL) {  // Out of memory
            // ReleaseStringUTFChars can be called with an exception pending.
            env->ReleaseStringUTFChars(path, pathStr);
            return;
        }
    
        MyMediaScannerClient myClient(env, client);
         //调用MediaScanner的本地方法processFile
        MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient);
    
        if (result == MEDIA_SCAN_RESULT_ERROR) {
            LOGE("An error occurred while scanning file '%s'.", pathStr);
        }
        env->ReleaseStringUTFChars(path, pathStr);
        if (mimeType) {
            env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
        }

    在上一个样例中我们找到register_android_util_Log能够用来完毕函数println_native在jni层的映射,相同在android_media_MediaScanner.cpp也找到了类似的函数:
    // This function only registers the native methods, and is called from
    // JNI_OnLoad in android_media_MediaPlayer.cpp
    int register_android_media_MediaScanner(JNIEnv *env)
    {
        return AndroidRuntime::registerNativeMethods(env,
                    kClassMediaScanner, gMethods, NELEM(gMethods));
    }
    当中gMethods:
    static JNINativeMethod gMethods[] = {
          //..
        {
            "processFile",    //java层名称为processFile 
            "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",    //java层函数签名为(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;) 
            (void *)android_media_MediaScanner_processFile   //该函数在jni层中的函数指针为(void *)android_media_MediaScanner_processFile 
        },
        //..
        {
            "native_init",  //java层名称为native_init 
            "()V",   //java层函数签名为()V 
            (void *)android_media_MediaScanner_native_init   该函数在jni层中的函数指针为(void *)android_media_MediaScanner_native_init 
        },
     };
    后面的操作就和Log中的流程一样,不再赘述。


    2.2 JNI注冊方式
    上面的两个样例中都涉及到一个问题。怎样将java层和jni层相应的函数一一关联起来,这就是jni注冊。有了这个注冊,在java层调用native方法时。就能非常方便找到jni层的实现并运行。
    注冊方式有两种方式:静态注冊和动态注冊

    2.2.1 JNI静态注冊
    静态注冊的思想:依据函数名称来建立java函数和jni函数之间的关联关系
    详细方法:
    1.编写java文件,编译生成class文件
    2.使用javah工具,javah -o output packagename.classname 命令生成output.h的jni头文件。

    这样的方式要求函数的命名符合一定的要求,主要有以下几个部分拼接而成
    1.Java_前缀
    2.全路径类名称(将.替换为/)
    3下划线_
    4參数列表加入JNIEnv* env,jobject class
    5java层函数的參数映射
    6返回值

    採用这样的方法,在java层调用native方法时,会在jni库中寻找按上述规则生成的jni函数。假设找到就将两个之间建立起一个关系。也就是保存在jni层这个函数指针。

    下次调用时直接使用这个函数指针就可以。

    这么做会非常繁琐,所以出现了第二中方式:jni动态注冊

    2.2.2 JNI动态注冊
    上面提到了能够保存java层函数在jni层的函数指针。那么我们直接保存这样的关系就能够了。

    这就涉及到2.1中的一个结构体类型JNINativeMethod

    typedef struct {
        const char* name; //java层的native函数的名称
        const char* signature; //该native函数的签名(返回值+參数列表)。由于java中支持函数重载,所以函数名称+函数签名才干明白确定一个函数
        void*       fnPtr; //函数指针,指向jni层该相应的函数实现
    } JNINativeMethod;
    然后的流程就是上述两个样例的流程:register_android_util_Log、register_android_media_MediaScanner函数,调用AndroidRuntime::registerNativeMethods函数,AndroidRuntime::registerNativeMethods中调用JNIHelp中jniRegisterNativeMethods方法。


    而register_android_media_MediaScanner是在哪里调用的呢?
    就是在android_media_MediaPlayer.cpp中的JNI_OnLoad方法中调用。

    jint JNI_OnLoad(JavaVM* vm, void* reserved)
    {
        JNIEnv* env = NULL;
        jint result = -1;
    
        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;
        }
    
        //在这里调用register_android_media_MediaScanner 注冊函数
        if (register_android_media_MediaScanner(env) < 0) {
            LOGE("ERROR: MediaScanner native registration failed
    ");
            goto bail;
        }
    
        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;
        }
    
        if (register_android_mtp_MtpDatabase(env) < 0) {
            LOGE("ERROR: MtpDatabase native registration failed");
            goto bail;
        }
    
        if (register_android_mtp_MtpDevice(env) < 0) {
            LOGE("ERROR: MtpDevice native registration failed");
            goto bail;
        }
    
        if (register_android_mtp_MtpServer(env) < 0) {
            LOGE("ERROR: MtpServer native registration failed");
            goto bail;
        }
    
        /* success -- return valid version number */
        result = JNI_VERSION_1_4;
    }
    能够看出在该方法中还调用了其它的注冊函数,所以假设我们动态注冊,就须要实现该函数。


    2.3 JNIEnv
    上面的代码中我们都使用了JNIEnv这个指针,利用它能够实现jni函数的注冊,它的功能远不止这些。它还能够訪问java虚拟机,操作java对象。是jni中最重要的一个概念。
    JNIEnv是一个和线程相关的代表JNI环境的结构体:


    它指向虚拟机内部数据结构,该结构又能够指向一个一个的jni函数。能够通过它来调用jni函数。
    JNIEnv结构体在dalviklibnativehelperinclude ativehelper.Jni.h定义
    struct _JNIEnv;
    typedef const struct JNINativeInterface* C_JNIEnv;
    #if defined(__cplusplus)
    typedef _JNIEnv JNIEnv;  //c++中使用_JNIEnv  
    .. 
    #else
    typedef const struct JNINativeInterface* JNIEnv; //c语言中使用JNINativeInterface 
    ..
    #endif
    先看_JNIEnv(C++语言中使用) 

    /*
    * C++ object wrapper.
    *
    * This is usually overlaid on a C struct whose first element is a
    * JNINativeInterface*.  We rely somewhat on compiler behavior.
    */
    struct _JNIEnv {
        /* do not rename this; it does not seem to be entirely opaque */
        const struct JNINativeInterface* functions;
    
    #if defined(__cplusplus)
    ..非常多方法
     jclass FindClass(const char* name)
        { return functions->FindClass(this, name); }
     jthrowable ExceptionOccurred()
        { return functions->ExceptionOccurred(this); }
    
        void ExceptionDescribe()
        { functions->ExceptionDescribe(this); }
    
        void ExceptionClear()
        { functions->ExceptionClear(this); }
    
     jobject NewGlobalRef(jobject obj)
        { return functions->NewGlobalRef(this, obj); }
    
        void DeleteGlobalRef(jobject globalRef)
        { functions->DeleteGlobalRef(this, globalRef); }
    
    jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
        { return functions->GetMethodID(this, clazz, name, sig); }
    
        jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
        { return functions->GetStaticMethodID(this, clazz, name, sig); }
    
     jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
        { return functions->GetFieldID(this, clazz, name, sig); }
    
        jobject GetObjectField(jobject obj, jfieldID fieldID)
        { return functions->GetObjectField(this, obj, fieldID); }
         //各种GetXXXField 方法
    
        void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
        { functions->SetObjectField(this, obj, fieldID, value); }
      //各种SetXXXField 方法 
      
    #define CALL_TYPE_METHOD(_jtype, _jname)                                     
        _jtype Call##_jname##Method(jobject obj, jmethodID methodID, ...)      
        {                                                                    
            _jtype result;                                                      
            va_list args;                                                       
            va_start(args, methodID);                                          
            result = functions->Call##_jname##MethodV(this, obj, methodID,args);                                                
            va_end(args);                                                     
            return result;                                                       
        }
    
    
    #define CALL_STATIC_TYPE_METHOD(_jtype, _jname)                           
        _jtype CallStatic##_jname##Method(jclass clazz, jmethodID methodID, ..)                                                               
        {                                                                       
            _jtype result;                                                       
            va_list args;                                                       
            va_start(args, methodID);                                         
            result = functions->CallStatic##_jname##MethodV(this, clazz,methodID, args);                                     
            va_end(args);                                                       
            return result;                                                      
        }
         //..其它方法
    }
    

    _JNIEnv,JNINativeInterface(C语言中使用) 
    struct JNINativeInterface {
    
        jclass      (*FindClass)(JNIEnv*, const char*);
        jint        (*Throw)(JNIEnv*, jthrowable);
        jint        (*ThrowNew)(JNIEnv *, jclass, const char *);
        jthrowable  (*ExceptionOccurred)(JNIEnv*);
        void        (*ExceptionDescribe)(JNIEnv*);
        void        (*ExceptionClear)(JNIEnv*);
        jobject     (*NewGlobalRef)(JNIEnv*, jobject);
        void        (*DeleteGlobalRef)(JNIEnv*, jobject);
        jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
        jfieldID    (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
        jobject     (*GetObjectField)(JNIEnv*, jobject, jfieldID);
        //..其它方法
    }
    上面解释了:JNIEnv是一个和线程相关的概念,不能将一个线程的JNIEnv从一个线程传递到还有一个线程中。

    同一个线程对本地方法的多次调用,使用都是同一个JNIEnv。


    在JNI_OnLoad(JavaVM* vm, void* reserved)中第一个參数类型为JavaVM。它是虚拟机在jni层的代表。JavaVM和JNIEnv的关系是:
    在调用AttachCurrentThread时。就返回该线程的JNIEnv
    jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThread(this, p_env, thr_args); }
    在调用DetachCurrentThread时,就释放该线程的JNIEnv 资源
    jint DetachCurrentThread()
    { return functions->DetachCurrentThread(this); }


    2.4 java中调用jni的实现方法
    主要有数据类型转换,jni函数签名
    2.4.1 数据类型转换
    分为两个部分:基本数据类型和引用数据类型。

    先看基本数据类型转换
    1.基本数据类型转换
    /*
    * Primitive types that match up with Java equivalents.
    */
    #ifdef HAVE_INTTYPES_H
    # include <inttypes.h>      /* C99 */
    typedef uint8_t         jboolean;       /* unsigned 8 bits */
    typedef int8_t          jbyte;          /* signed 8 bits */
    typedef uint16_t        jchar;          /* unsigned 16 bits */
    typedef int16_t         jshort;         /* signed 16 bits */
    typedef int32_t         jint;           /* signed 32 bits */
    typedef int64_t         jlong;          /* signed 64 bits */
    typedef float           jfloat;         /* 32-bit IEEE 754 */
    typedef double          jdouble;        /* 64-bit IEEE 754 */
    #else
    typedef unsigned char   jboolean;       /* unsigned 8 bits */
    typedef signed char     jbyte;          /* signed 8 bits */
    typedef unsigned short  jchar;          /* unsigned 16 bits */
    typedef short           jshort;         /* signed 16 bits */
    typedef int             jint;           /* signed 32 bits */
    typedef long long       jlong;          /* signed 64 bits */
    typedef float           jfloat;         /* 32-bit IEEE 754 */
    typedef double          jdouble;        /* 64-bit IEEE 754 */
    #endif
    相应关系就是前面加一个字母j

    注意字长变化,java中char是8位,而jchar则是16位。


    2.引用类型变化
    jni中引用类型有jobject、jclass、jarray、jstring、jthrowable以及九种数组类型。继承结构:

    和java引用相应的关系是:

    比方函数static jint android_util_Log_println_native(JNIEnv* env, jobject clazz, jint bufID, jint priority, jstring tagObj, jstring msgObj)
    java中int相应jni中的jint,java中的String相应jni中的jstring
    函数android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path,jstring mimeType, jobject client)
    java中String相应jni中的jstring,java中的android.media.MediaScannerClient相应jobject


    2.4.2jni函数方法签名
    通过类型之间的相应关系,jni能够和java类型一一相应。那么jni怎样定位java的方法?

    就是通过方法签名。

    方法签名就是用一个字符串表示一个方法的參数类型和返回值,规则例如以下:
    (參数1类型签名參数2类型签名...參数n类型签名)返回值类型签名
    注意:參数类型签名中间没有空格

    參数类型签名有以下相应关系


    分为4类
    1 原生数据类型boolean,byte,char,short,int ,long,float,double 这些分别用一个字母表示
    2L+"全限定类名称"+";"  ,这里要将"."替换成"/",比方String,相应签名类型  "Ljava/lang/String;"
    3 数组,[+"元素的类型签名",比方 int[] 相应參数签名为"[I", String[]相应參数签名为"[Ljava/lang/String;"
    4 返回值假设是void。则用V表示
    看一个样例:
     { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native }
    函数签名为(IILjava/lang/String;Ljava/lang/String;)I
    返回參数签名为I,相应java的int
    參数列表签名为(IILjava/lang/String;Ljava/lang/String;)相应參数列表为(int,int,String,String)
    所以在Log类中的println_native函数的声明:
    int println_native(int,int,String,String)
    再看一个样例
     {
            "processFile","(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V"
            (void *)android_media_MediaScanner_processFile   
      }
    函数签名为(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V
    返回參数签名为V。相应java的void
    參数列表签名为(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;),相应參数列表为(String,String,android/media/MediaScannerClient)
    能够找到MediaScanner类中的processFile函数的声明
    void MediaScanner (String,String,android.media.MediaScannerClient)


    2.5 jni层操作java对象
    java对象中有哪些:成员变量和成员函数,那么jni中操作java对象也是操作变量和函数。实际在JNIEnv定义中中我们已经看见非常多函数能够进行这两个操作了。

    要操作对象。就要找到该对象的类信息,jni中主要使用以下两个方法:
    jclass Findclass(const char* name)//查找全路径名称为name的类信息
    jclass GetObjectClass(jobject ojb)//返回该对象所在类的信息
    比方: 
     jclass clazz = env->FindClass("android/util/Log");

    在jni中。使用jfieldIDjmethodID来表示java中的成员变量和成员函数。能够通过以下的方式获得到:
    jfieldID GetFieldID(jclass clazz,const char* name,const char*sig);
    clazz:该类信息
    name:变量名称
    sig:变量參数签名
    如:android_media_MediaScanner_native_init中的
    fields.context =env->GetFieldID(clazz, "mNativeContext", "I");
    就是訪问clazz对象中类型为int的mNativeContext变量。将其赋值给fields.context变量保存。

    获得jmethodID的方法:
    jmethodID GetMethodID(jclass clazz,const char* name,const char*sig);
    clazz:该类信息
    name:函数名称
    sig:函数签名
    如:
    MyMediaScannerClient(JNIEnv *env, jobject client){
    ..
     jclass mediaScannerClientInterface =
                    env->FindClass(kClassMediaScannerClient);
    
    //相应MediaScannerClie中void scanFile(String,long,long,boolean,boolean)方法
    mScanFileMethodID = env->GetMethodID(
                                        mediaScannerClientInterface,
                                        "scanFile",
                                        "(Ljava/lang/String;JJZZ)V");
    
    //相应MediaScannerClie中void  handleStringTag (String,String)方法
    mHandleStringTagMethodID = env->GetMethodID(
                                        mediaScannerClientInterface,
                                        "handleStringTag",
                                        "(Ljava/lang/String;Ljava/lang/String;)V");
    
    //相应MediaScannerClie中    void setMimeType(String)方法 
                mSetMimeTypeMethodID = env->GetMethodID(
                                        mediaScannerClientInterface,
                                        "setMimeType",
                                        "(Ljava/lang/String;)V");
    ...
    }
    有了jfieldID和jmethodID之后就能够直接訪问变量和函数了。

    使用jfieldID
    static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz)
    {
       //fields.context =env->GetFieldID(clazz, "mNativeContext", "I"); 
        return (MediaScanner *) env->GetIntField(thiz, fields.context);
    }
    能够利用这种方法,訪问在thiz类中mNativeContext的值。
    类似的方法还有非常多:
    GetTypeField(jobject,jfieldID)//返回jobject类中变量为jfieldID的变量
    与得到变量相应的就是设置变量的值
    //fields.context =env->GetFieldID(clazz, "mNativeContext", "I"); 
     env->SetIntField(thiz, fields.context, 0);//将mNativeContext设置为0
    类似的:
    SetTypeField(jobject obj,jfieldID fieldID,nativeType value)//fieldID的值设置为value

    使用jmethodID 
    scanFile(const char* path, long long lastModified,long long fileSize, bool isDirectory, bool noMedia)中的
     mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
                    fileSize, isDirectory, noMedia);
    类似这样的还有:
    CallTypeMethod(jobject obj,jmethodID methodID,參数1,參数2...)来调用jmethodID函数,并将參数传递进去。


    这是对于对象的成员变量和成员函数,假设是类级别的,加上statickeyword就可以
    GetStaticFieldID(jclass clazz,const char* name,const char*sig);
    GetStaticTypeField(jobject obj,jfieldID fieldID)
    SetStaticTypeField(jobject obj,jfieldID fieldID,nativeType value)
    
    GetStaticMethodID(jclass clazz,const char* name,const char*sig);
    CallStaticTypeMethod(jobject obj,jmethodID methodID,參数1,參数2...)
    相应关系:



    2.6 垃圾回收
    java层的垃圾回收由垃圾回收器来进行。可是jni层呢?
    在java层。每个对象维护一个该对象的引用计数。假设对象被赋值为一个引用类型,则引用计数加一。可是在jni层中不是会导致该计数加一:
    static jobject save_class = NULL//定义了一个全局的jobject
    static void android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path,
            jstring mimeType, jobject client)
    {
        ....
         //保存thiz对象,也就是MediaScanner对象
        save_class  =thiz ;
        ...
    return ;  
    }
     //调用call方法
     void call()
    {
       //使用save_class  能够吗?
       //不能够,由于有可能MediaScanner对象已经被回收了。
    }

    引用计数并没有添加,MediaScanner有可能被回收。为了解决问题,jni提出了三类引用:local reference,global reference,weak global reference
    1 local reference:本地引用,能够添加引用计数。作用范围为本线程。一旦jni函数返回,这些引用就会被回收
    2 global reference:全局应用,能够添加引用计数,作用范围多线程。须要显示释放。假设不释放。永远不会被回收

    3 weak global reference:弱全局引用,不添加引用计数,作用范围为多线程。

    须要显示释放。可是及时没有释放,也可能被虚拟机回收。


    经常使用的是local reference和global reference:
    先看local reference
    boolean test(const char* name)
    {
    
    for(int i=0;i<10000;i++)
    {
    jstring nameStr = env->NewStringUTF(name);
    //假设这里我们不马上释放nameStr。那么会在函数结束之后才释放。看起来没有太大差别,若像这样创建10000个jstring
    就占用了许多的内存了。所以要这里在不须要使用的时候还是及时释放
    //env->DeleteLocalRef(nameStr);
    }
    }

    再看global reference:
    public:
        MyMediaScannerClient(JNIEnv *env, jobject client)
            :   mEnv(env),
                mClient(env->NewGlobalRef(client)),//创建一个全局引用mClient 
                mScanFileMethodID(0),
                mHandleStringTagMethodID(0),
                mSetMimeTypeMethodID(0)
        {
            LOGV("MyMediaScannerClient constructor");
            jclass mediaScannerClientInterface =
                    env->FindClass(kClassMediaScannerClient);
    
            if (mediaScannerClientInterface == NULL) {
                LOGE("Class %s not found", kClassMediaScannerClient);
            } else {
                mScanFileMethodID = env->GetMethodID(
                                        mediaScannerClientInterface,
                                        "scanFile",
                                        "(Ljava/lang/String;JJZZ)V");
    
                mHandleStringTagMethodID = env->GetMethodID(
                                        mediaScannerClientInterface,
                                        "handleStringTag",
                                        "(Ljava/lang/String;Ljava/lang/String;)V");
    
                mSetMimeTypeMethodID = env->GetMethodID(
                                        mediaScannerClientInterface,
                                        "setMimeType",
                                        "(Ljava/lang/String;)V");
            }
        }
    
        virtual ~MyMediaScannerClient()
        {
            LOGV("MyMediaScannerClient destructor");
            //在析构函数中主动释放mClient 
            mEnv->DeleteGlobalRef(mClient);
        }


    2.7 异常
    2.7.1检測异常
    使用jni中的ExceptionOccurred()函数来推断

    2.7.2处理异常
    两种方式
    1.马上返回,该异常在java层抛出,所以要在java层处理异常,否则程序异常退出
    2.使用ExceptionClear来清除异常
    static void
    android_media_MediaScanner_processFile(
            JNIEnv *env, jobject thiz, jstring path,
            jstring mimeType, jobject client)
    {
        ...
        // Lock already hold by processDirectory
        MediaScanner *mp = getNativeScanner_l(env, thiz);
        if (mp == NULL) {
            jniThrowException(env, kRunTimeException, "No scanner available");
            return;
        }
      ..
    }

    调用了jniThrowException。刚函数在:dalviklibnativehelperJNIHelper.cpp
    extern "C" int jniThrowException(C_JNIEnv* env, const char* className, const char* msg) {
        JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
    
        if ((*env)->ExceptionCheck(e)) {
            /* TODO: consider creating the new exception with this as "cause" */
            scoped_local_ref<jthrowable> exception(env, (*env)->ExceptionOccurred(e));
            (*env)->ExceptionClear(e);
    
            if (exception.get() != NULL) {
                char* text = getExceptionSummary(env, exception.get());
                LOGW("Discarding pending exception (%s) to throw %s", text, className);
                free(text);
            }
        }
    
        scoped_local_ref<jclass> exceptionClass(env, findClass(env, className));
        if (exceptionClass.get() == NULL) {
            LOGE("Unable to find exception class %s", className);
            /* ClassNotFoundException now pending */
            return -1;
        }
    
        if ((*env)->ThrowNew(e, exceptionClass.get(), msg) != JNI_OK) {
            LOGE("Failed throwing '%s' '%s'", className, msg);
            /* an exception, most likely OOM, will now be pending */
            return -1;
        }
    
        return 0;
    }

























  • 相关阅读:
    ArcGIS 网络分析[8.1] 资料1 使用AO打开或创建网络数据集之【打开】
    【AO笔记】Addins的Toolbar 添加一条分割线
    ArcGIS 网络分析[4] 网络数据集深入浅出之连通性、网络数据集的属性及转弯要素
    ArcGIS 网络分析[2.5] VRP(车辆配送)【较难】
    ArcGIS 网络分析[2.4] OD成本矩阵
    Python中time模块详解
    python 读取文件、并以十六进制的方式写入到新文件
    【C++程序员学 python】python 之奇葩地方
    【C++程序员学 python】python 之变量
    校验值的计算----移位算法
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/5182959.html
Copyright © 2011-2022 走看看