<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                通過前面5章的學習,我們知道了如何通過JNI函數來訪問JVM中的基本數據類型、字符串和數組這些數據類型。下一步我們來學習本地代碼如何與JVM中任意對象的**屬性**和**方法**進行交互。比如本地代碼調用Java層某個對象的方法或屬性,也就是通常我們所說的來自C/C++層本地函數的callback(回調)。這個知識點分2篇文章分別介紹,本篇先介紹方法回調,在第七章中介紹本地代碼訪問Java的屬性。 在這之前,先回顧一下在Java中調用一個方法時在JVM中的實現原理,有助于下面講解本地代碼調用Java方法實現的機制。寫過Java的童鞋都知道,調用一個類的靜態方法,直接通過 **類名.方法**就可以調用。這也太簡單了,有什么好講的呢。。。但在這個調用過程中,JVM是幫我們做了很多工作的。當我們在運行一個Java程序時,JVM會先將程序運行時所要用到所有**相關的class**文件加載到JVM中,并采用按需加載的方式加載,也就是說某個類只有在被用到的時候才會被加載,這樣設計的目的也是為了提高程序的性能和節約內存。所以我們在用類名調用一個靜態方法之前,JVM首先會判斷該類是否已經加載,如果沒有被**ClassLoader**加載到JVM中,JVM會從classpath路徑下查找該類,如果找到了,會將其加載到JVM中,然后才是調用該類的靜態方法。如果沒有找到,JVM會拋出java.lang.ClassNotFoundException異常,提示找不到這個類。ClassLoader是JVM加載class字節碼文件的一種機制,不太了解的童鞋,請移步閱讀[《深入分析Java ClassLoader原理》](http://blog.csdn.net/xyang81/article/details/7292380)一文。其實在JNI開發當中,本地代碼也是按照上面的流程來訪問類的靜態方法或實例方法的,下面通過一個例子,詳細介紹本地代碼調用Java方法流程當中的每個步聚: ~~~ package com.study.jnilearn; /** * AccessMethod.java * 本地代碼訪問類的實例方法和靜態方法 * @author yangxin */ public class AccessMethod { public static native void callJavaStaticMethod(); public static native void callJavaInstaceMethod(); public static void main(String[] args) { callJavaStaticMethod(); callJavaInstaceMethod(); } static { System.loadLibrary("AccessMethod"); } } ~~~ ~~~ package com.study.jnilearn; /** * ClassMethod.java * 用于本地代碼調用 * @author yangxin */ public class ClassMethod { private static void callStaticMethod(String str, int i) { System.out.format("ClassMethod::callStaticMethod called!-->str=%s," + " i=%d\n", str, i); } private void callInstanceMethod(String str, int i) { System.out.format("ClassMethod::callInstanceMethod called!-->str=%s, " + "i=%d\n", str, i); } } ~~~ 由AccessMethod.class生成的頭文件: ~~~ /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_study_jnilearn_AccessMethod */ #ifndef _Included_com_study_jnilearn_AccessMethod #define _Included_com_study_jnilearn_AccessMethod #ifdef __cplusplus extern "C" { #endif /* * Class: com_study_jnilearn_AccessMethod * Method: callJavaStaticMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaStaticMethod (JNIEnv *, jclass); /* * Class: com_study_jnilearn_AccessMethod * Method: callJavaInstaceMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod (JNIEnv *, jclass); #ifdef __cplusplus } #endif #endif ~~~ 本地代碼對頭文件中函數原型的實現: ~~~ // AccessMethod.c #include "com_study_jnilearn_AccessMethod.h" /* * Class: com_study_jnilearn_AccessMethod * Method: callJavaStaticMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaStaticMethod (JNIEnv *env, jclass cls) { jclass clazz = NULL; jstring str_arg = NULL; jmethodID mid_static_method; // 1、從classpath路徑下搜索ClassMethod這個類,并返回該類的Class對象 clazz =(*env)->FindClass(env,"com/study/jnilearn/ClassMethod"); if (clazz == NULL) { return; } // 2、從clazz類中查找callStaticMethod方法 mid_static_method = (*env)->GetStaticMethodID(env,clazz,"callStaticMethod","(Ljava/lang/String;I)V"); if (mid_static_method == NULL) { printf("找不到callStaticMethod這個靜態方法。"); return; } // 3、調用clazz類的callStaticMethod靜態方法 str_arg = (*env)->NewStringUTF(env,"我是靜態方法"); (*env)->CallStaticVoidMethod(env,clazz,mid_static_method, str_arg, 100); // 刪除局部引用 (*env)->DeleteLocalRef(env,clazz); (*env)->DeleteLocalRef(env,str_arg); } /* * Class: com_study_jnilearn_AccessMethod * Method: callJavaInstaceMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod (JNIEnv *env, jclass cls) { jclass clazz = NULL; jobject jobj = NULL; jmethodID mid_construct = NULL; jmethodID mid_instance = NULL; jstring str_arg = NULL; // 1、從classpath路徑下搜索ClassMethod這個類,并返回該類的Class對象 clazz = (*env)->FindClass(env, "com/study/jnilearn/ClassMethod"); if (clazz == NULL) { printf("找不到'com.study.jnilearn.ClassMethod'這個類"); return; } // 2、獲取類的默認構造方法ID mid_construct = (*env)->GetMethodID(env,clazz, "<init>","()V"); if (mid_construct == NULL) { printf("找不到默認的構造方法"); return; } // 3、查找實例方法的ID mid_instance = (*env)->GetMethodID(env, clazz, "callInstanceMethod", "(Ljava/lang/String;I)V"); if (mid_instance == NULL) { return; } // 4、創建該類的實例 jobj = (*env)->NewObject(env,clazz,mid_construct); if (jobj == NULL) { printf("在com.study.jnilearn.ClassMethod類中找不到callInstanceMethod方法"); return; } // 5、調用對象的實例方法 str_arg = (*env)->NewStringUTF(env,"我是實例方法"); (*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200); // 刪除局部引用 (*env)->DeleteLocalRef(env,clazz); (*env)->DeleteLocalRef(env,jobj); (*env)->DeleteLocalRef(env,str_arg); } ~~~ **運行結果:** ![](https://box.kancloud.cn/2016-02-16_56c2c946e267c.jpg) **代碼解析:** AccessMethod.java是程序的入口,在main方法中,分別調用了callJavaStaticMethod和callJavaInstaceMethod這兩個native方法,用于測試native層調用**MethodClass.java中的**callStaticMethod靜態方法和callInstanceMethod實例方法,這兩個方法的返回值都為Void,參數都有兩個,分別為String和int **一、callJavaStaticMethod靜態方法實現說明** ~~~ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaStaticMethod (JNIEnv *env, jclass cls) ~~~ 定位到AccessMethod.c的31行: ~~~ (*env)->CallStaticVoidMethod(env,clazz,mid_static_method, str_arg, 100); ~~~ CallStaticVoidMethod函數的原型如下: ~~~ void (JNICALL *CallStaticVoidMethod)(JNIEnv *env, jclass cls, jmethodID methodID, ...); ~~~ **該函數接收4個參數:** env:JNI函數表指針 cls:調用該靜態方法的Class對象 methodID:方法ID(因為一個類中會存在多個方法,需要一個唯一標識來確定調用類中的哪個方法)? 參數4:方法實參列表 **根據函數參數的提示,分以下四步完成Java靜態方法的回調:** **第一步:**調用FindClass函數,傳入一個Class描述符,JVM會從classpath路徑下搜索該類,并返回jclass類型(用于存儲Class對象的引用)。注意ClassMethod的Class描述符為com/study/jnilearn/ClassMethod,要將**.**(點)全部換成**/**(反斜杠) ~~~ (*env)->FindClass(env,"com/study/jnilearn/ClassMethod"); ~~~ **第二步:**調用GetStaticMethodID函數,從ClassMethod類中獲取callStaticMethod方法ID,返回jmethodID類型(用于存儲方法的引用)。實參clazz是第一步找到的jclass對象,實參"callStaticMethod"為方法名稱,實參“(Ljava/lang/String;I)V”為方法的簽名 ~~~ (*env)->GetStaticMethodID(env,clazz,"callStaticMethod","(Ljava/lang/String;I)V"); ~~~ **第三步:**調用CallStaticVoidMethod函數,執行ClassMethod.callStaticMethod方法調用。str_arg和100是callStaticMethod方法的實參。 ~~~ str_arg = (*env)->NewStringUTF(env,"我是靜態方法"); (*env)->CallStaticVoidMethod(env,clazz,mid_static_method, str_arg, 100); ~~~ 注意:JVM針對所有數據類型的返回值都定義了相關的函數。上面callStaticMethod方法的返回類型為Void,所以調用CallStaticVoidMethod。根據返回值類型不同,JNI提供了一系列不同返回值的函數,如:CallStaticIntMethod、CallStaticFloatMethod、CallStaticShortMethod、CallStaticObjectMethod等,分別表示調用返回值為int、float、short、Object類型的函數,引用類型統一調用CallStaticObjectMethod函數。另外,每種返回值類型的函數都提供了接收3種實參類型的實現:CallStatic**XXX**Method(env, clazz, methodID, ...),CallStaticXXXMethodV(env, clazz, methodID, va_list args),CallStaticXXXMethodA(env, clazz, methodID, const jvalue *args),分別表示:接收可變參數列表、接收va_list作為實參和接收const jvalue*為實參。下面是jni.h頭文件中CallStaticVoidMethod的三種實參的函數原型: ~~~ void (JNICALL *CallStaticVoidMethod) (JNIEnv *env, jclass cls, jmethodID methodID, ...); void (JNICALL *CallStaticVoidMethodV) (JNIEnv *env, jclass cls, jmethodID methodID, va_list args); void (JNICALL *CallStaticVoidMethodA) (JNIEnv *env, jclass cls, jmethodID methodID, const jvalue * args); ~~~ **第四步**、釋放局部變量 ~~~ // 刪除局部引用 (*env)->DeleteLocalRef(env,clazz); (*env)->DeleteLocalRef(env,str_arg); ~~~ 雖然函數結束后,JVM會自動釋放所有局部引用變量所占的內存空間。但還是手動釋放一下比較安全,因為在JVM中維護著一個引用表,用于存儲局部和全局引用變量,經測試在Android NDK環境下,這個表的最大存儲空間是512個引用,如果超過這個數就會造成引用表溢出,JVM崩潰。在PC環境下測試,不管申請多少局部引用也不釋放都不會崩,我猜可能與JVM和Android Dalvik虛擬機實現方式不一樣的原因。所以有申請就及時釋放是一個好的習慣!*(局部引用和全局引用在后面的文章中會詳細介紹)* **二、callInstanceMethod實例方法實現說明** ~~~ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod (JNIEnv *env, jclass cls) ~~~ 定位到AccessMethod.c的43行: ~~~ (*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200); ~~~ CallVoidMethod函數的原型如下: ~~~ void (JNICALL *CallVoidMethod) (JNIEnv *env, jobject obj, jmethodID methodID, ...); ~~~ **該函數接收4個參數:** env:JNI函數表指針 obj:調用該方法的實例 methodID:方法ID? 參數4:方法的實參列表 **根據函數參數的提示,分以下六步完成Java靜態方法的回調:** **第一步、**同調用靜態方法一樣,首先通過FindClass函數獲取 類的Class對象 **第二步、**獲取類的構造方法ID,因為創建類的對象首先會調用類的構造方法。這里以默認構造方法為例 ~~~ (*env)->GetMethodID(env,clazz, "<init>","()V"); ~~~ <init>代表類的構造方法名稱,()V代表無參無返回值的構造方法(即默認構造方法) **第三步、**調用GetMethodID獲取callInstanceMethod的方法ID ~~~ (*env)->GetMethodID(env, clazz, "callInstanceMethod", "(Ljava/lang/String;I)V"); ~~~ **第四步、**調用NewObject函數,創建類的實例對象 ~~~ (*env)->NewObject(env,clazz,mid_construct); ~~~ **第五步、**調用CallVoidMethod函數,執行ClassMethod.callInstanceMethod方法調用,str_arg和200是方法實參 ~~~ str_arg = (*env)->NewStringUTF(env,"我是實例方法"); (*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200); ~~~ 同JNI調用Java靜態方法一樣,JVM針對所有數據類型的返回值都定義了相關的函數(CallXXXMethod),如:CallIntMethod、CallFloatMethod、CallObjectMethod等,也同樣提供了支持三種類型實參的函數實現,以CallVoidMethod為例,如下是jni.h頭文件中該函數的原型: ~~~ void (JNICALL *CallVoidMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); void (JNICALL *CallVoidMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); void (JNICALL *CallVoidMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args); ~~~ **第六步、**刪除局部引用(從引用表中移除) ~~~ // 刪除局部引用 (*env)->DeleteLocalRef(env,clazz); (*env)->DeleteLocalRef(env,jobj); (*env)->DeleteLocalRef(env,str_arg); ~~~ **三、方法簽名** 在上面的的例子中,無論是調用靜態方法還是實例方法,都必須傳入一個jmethodID的參數。因為在Java中存在方法重載(方法名相同,參數列表不同),所以要明確告訴JVM調用的是類或實例中的哪一個方法。調用JNI的GetMethodID函數獲取一個jmethodID時,需要傳入一個方法名稱和方法簽名,方法名稱就是在Java中定義的方法名,**方法簽名的格式為**:**(形參參數類型列表)返回值**。形參參數列表中,引用類型以L開頭,后面緊跟類的全路徑名(需將.全部替換成/),以分號結尾。下面是一些示例: ![](https://box.kancloud.cn/2016-02-16_56c2c9470a971.jpg) Java基本類型與方法簽名中參數類型和返回值類型的映射關系如下: ![](https://box.kancloud.cn/2016-02-16_56c2c94724de1.jpg) 比如,String fun(int a, float b, boolean c, String d) 對應的JNI方法簽名為:"(IFZLjava/lang/String;)Ljava/lang/String;" **總結:** 1、調用靜態方法使用CallStaticXXXMethod/V/A函數,XXX代表返回值的數據類型。如:CallStaticIntMethod 2、調用實例方法使用CallXXXMethod/V/A函數,XXX代表返回的數據類型,如:CallIntMethod 3、獲取一個實例方法的ID,使用GetMethodID函數,傳入方法名稱和方法簽名 4、獲以一個靜態方法的ID,使用GetStaticMethodID函數,傳入方法名稱和方法簽名 5、獲取構造方法ID,方法名稱使用"<init>" 6、獲取一個類的Class實例,使用FindClass函數,傳入類描述符。JVM會從classpath目錄下開始搜索。 7、創建一個類的實例,使用NewObject函數,傳入Class引用和構造方法ID 8、刪除局部變量引用,使用DeleteLocalRef,傳入引用變量 9、方法簽名格式:(形參參數列表)返回值類型。注意:形參參數列表之間不需要用空格或其它字符分隔 10、類描述符格式:L包名路徑/類名;,包名之間用/分隔。如:Ljava/lang/String; 11、調用GetMethodID獲取方法ID和調用FindClass獲取Class實例后,要做異常判斷 示例代碼下載地址:[https://code.csdn.net/xyang81/jnilearn](https://code.csdn.net/xyang81/jnilearn)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看