深入理解JNI技術


一、JNI是什么?

JNI是Java Native Interface的縮寫,譯為Java本地調用。JNI是一種技術。

二、JNI技術的用途?

  • Java程序中的函數調用Native程序中的函數。Native一般指使用C/C++編寫的函數。
  • Native程序中的函數調用Java程序中的函數。

三、注冊JNI函數

  • 靜態注冊

  Java層函數通過Java編譯成.class文件,再通過Javah工具將將.class生成為JNI層的*.h頭文件,在*.h頭文件里有對應Java層的函數,在JNI層實現相關函數即可。

javah -o test packagename.classname
  • 動態注冊

  Java Native函數與JNI函數是一一對應的關系,所以,有一個數據結構存儲着對應關系,這個數據結構就是JNINativeMethod結構體。

typedef struct {
    const char* name;
    const char* signature;
    void* fnPtr;
} JNINativeMethod;

JNINativeMethod name:Java函數名稱,不包括包路徑。

JNINativeMethod signature:Java函數簽名,用字符串存儲,簽名信息由參數類型+返回值類型組成。

JNINativeMethod fnPtr:JNI層對應的函數指針,注意fnPtr是Viod*。

  在運行時,AndroidRuntime類提供方法registerNativeMethod函數完成java函數的注冊。registerNativeMethod函數通過調用jniRegisterNativeMethods函數實現函數注冊。jniRegisterNativeMethods函數是Android平台提供的幫助函數。

  那么,在什么時候完成注冊或者函數調用呢?

  在Java層通過System.loadLibrary函數加載JNI動態庫,在System.loadLibrary函數調用完成后,會調用JNI_OnLoad函數,如果有就調用它,函數注冊或者相關初始化在JNI_OnLoad函數中完成。

/**
     * Loads the native library specified by the <code>libname</code>
     * argument.  The <code>libname</code> argument must not contain any platform
     * specific prefix, file extension or path. If a native library
     * called <code>libname</code> is statically linked with the VM, then the
     * JNI_OnLoad_<code>libname</code> function exported by the library is invoked.
     * See the JNI Specification for more details.
     *
     * Otherwise, the libname argument is loaded from a system library
     * location and mapped to a native library image in an implementation-
     * dependent manner.
     * <p>
     * The call <code>System.loadLibrary(name)</code> is effectively
     * equivalent to the call
     * <blockquote><pre>
     * Runtime.getRuntime().loadLibrary(name)
     * </pre></blockquote>
     *
     * @param      libname   the name of the library.
     * @exception  SecurityException  if a security manager exists and its
     *             <code>checkLink</code> method doesn't allow
     *             loading of the specified dynamic library
     * @exception  UnsatisfiedLinkError if either the libname argument
     *             contains a file path, the native library is not statically
     *             linked with the VM,  or the library cannot be mapped to a
     *             native library image by the host system.
     * @exception  NullPointerException if <code>libname</code> is
     *             <code>null</code>
     * @see        java.lang.Runtime#loadLibrary(java.lang.String)
     * @see        java.lang.SecurityManager#checkLink(java.lang.String)
     */
    @CallerSensitive
    public static void loadLibrary(String libname) {
        Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
    }

  JNI_OnLoad是JNI層方法

jint JNI_OnLoad(JavaVM* vm, void* reserved __unused)
{
    // 函數注冊
}

 

四、數據類型轉換

  Java層數據類型在JNI層有一一對應的數據類型。  

五、垃圾回收

  在Java中,創建對象使用完后,通過Java GC回收對象和釋放內存。那么,Java GC對JNI層有什么影響?Java層變量傳入JNI層后如何使用?

  如下代碼通過Android源碼修改:

// Adnroid源碼 frameworks/base/core/jni/android_media_MediaRecorder.cpp
JNIMediaRecorderListener::JNIMediaRecorderListener(JNIEnv* env, jobject thiz, jobject weak_thiz)
{

    // Hold onto the MediaRecorder class for use in calling the static method
    // that posts events to the application thread.
    jclass clazz = env->GetObjectClass(thiz);
    if (clazz == NULL) {
        ALOGE("Can't find android/media/MediaRecorder");
        jniThrowException(env, "java/lang/Exception", NULL);
        return;
    }
    mClass = (jclass)env->NewGlobalRef(clazz);

    // We use a weak reference so the MediaRecorder object can be garbage collected.
    // The reference is only used as a proxy for callbacks.
    mObject  = weak_thiz;
}

  上面代碼正常情況是沒問題的,如果此廣告在Java層調用將傳遞參數到JNI層,通過mOjbect = week_thiz賦值,當前Java層GC回收了week_thiz變量。那么,在其它地方使用mObject變量就會出現異常,mObject指向一個野指針。

  那么,有一個疑問,正常在Java層mOjbect = week_thiz這個賦值,引用計數加1,不應該會被GC回收的。但是,需要注意在JNI層mOjbect = week_thiz這樣的語句,是不會增加引用計數的。

  正確寫法,未修改Android源碼,上面的源碼是人為改錯的:

 

JNIMediaRecorderListener::JNIMediaRecorderListener(JNIEnv* env, jobject thiz, jobject weak_thiz)
{

    // Hold onto the MediaRecorder class for use in calling the static method
    // that posts events to the application thread.
    jclass clazz = env->GetObjectClass(thiz);
    if (clazz == NULL) {
        ALOGE("Can't find android/media/MediaRecorder");
        jniThrowException(env, "java/lang/Exception", NULL);
        return;
    }
    mClass = (jclass)env->NewGlobalRef(clazz);

    // We use a weak reference so the MediaRecorder object can be garbage collected.
    // The reference is only used as a proxy for callbacks.
    mObject  = env->NewGlobalRef(weak_thiz);
}

  在JNI層引用分為Local Reference(本地引用)和Global Reference(全局引用),還有一種特殊的引用 Weak Global Reference(弱全局引用)。

  Local Reference:本地引用,在JNI層函數中使用的非全局引用都是Local Reference,包括函數調用傳遞的參數和JNI層本地創建的jobject。Local Reference在JNI最大的好處就是,在JNI層函數返回后,這些jobject就可能被GC回收。

  Global Reference:全局引用,全局引用的對象不主動釋放,那么,將永遠不會被GC回收。

  Weak Global Reference:弱全局引用,一種特殊的全局引用,在程序運行過程中可能被GC回收。  

六、異常處理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM