1.C++調用Java
-
從classpath路徑下搜索ClassMethod這個類,並返回該類的Class對象。
-
獲取類的默認構造方法ID。
-
查找實例方法的ID。
-
創建該類的實例。
-
調用對象的實例方法。
-
/**
* 調用java的方法
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_xfhy_ndkdemo_CallJava_callVoidMethod(JNIEnv *env, jobject instance) {
//通過反射調用java中的方法
//找class 使用FindClass方法,參數就是要調用的函數的類的完全限定名,但是需要把點換成/
jclass clazz = env->FindClass("com/xfhy/ndkdemo/CallJava");
//獲取對應的函數: 參數1:類class,參數2:方法名,參數3:方法簽名
//ps:方法簽名的獲取:進入build->intermediates->classes->debug目錄下,使用javap -s 類的完全限定名,就能獲得函數簽名
jmethodID method = env->GetMethodID(clazz, "hello", "()V");
//實例化該class對應的實例 使用AllocObject方法,使用clazz創建該class的實例。
jobject object = env->AllocObject(clazz);
//調用方法
env->CallVoidMethod(object, method);
}
——————————————
2.Java調用C函數
Java調用C++
- 在Java中聲明Native方法(即需要調用的本地方法)
- 編譯上述 Java源文件javac(得到 .class文件) 3。 通過 javah 命令導出JNI的頭文件(.h文件)
- 使用 Java需要交互的本地代碼 實現在 Java中聲明的Native方法
- 編譯.so庫文件
- 通過Java命令執行 Java程序,最終實現Java調用本地代碼
- CMake:一個跨平臺的編譯構建工具,替代 Android.mk
- LLDB:一個高效的 C/C++ 的調試工具
- NDK:即我們需要下載的工具,會生成到 SDK 根目錄下的 ndk-bundle 目錄下
3.JNIEnv分析我們知道 ,JNIEnv是JNINativeInterface_
結構體的指針別名 , 在JNINativeInterface_
結構體中 , 定義很多操作函數 。例如:<span style="color:#000000"><span style="color:#cccccc"><span style="color:#404040"><code class="language-c">jstring (JNICALL <span style="color:#67cdcc">*</span>NewStringUTF) (JNIEnv <span style="color:#67cdcc">*</span>env, <span style="color:#cc99cd">const</span> <span style="color:#cc99cd">char</span> <span style="color:#67cdcc">*</span>utf); jsize (JNICALL <span style="color:#67cdcc">*</span>GetStringUTFLength) (JNIEnv <span style="color:#67cdcc">*</span>env, jstring str); <span style="color:#cc99cd">const</span> <span style="color:#cc99cd">char</span><span style="color:#67cdcc">*</span> (JNICALL <span style="color:#67cdcc">*</span>GetStringUTFChars)(JNIEnv <span style="color:#67cdcc">*</span>env, jstring str, jboolean <span style="color:#67cdcc">*</span>isCopy); <span style="color:#cc99cd">void</span> (JNICALL <span style="color:#67cdcc">*</span>ReleaseStringUTFChars)(JNIEnv <span style="color:#67cdcc">*</span>env, jstring str, <span style="color:#cc99cd">const</span> <span style="color:#cc99cd">char</span><span style="color:#67cdcc">*</span> chars);</code></span></span></span>
由上述函數可以看出,每個函數都需要一個JNIEnv指針,但是爲什麼需要呢 ?
有兩點:
第一:函數需要 , 在函數中仍然需要JNINativeInterface_結構體中的函數做處理第二:區別對待C和C++
我們知道 , jni是支
由上可知 , 在C和C++兩個環境中 , 使用了兩個不同的JNIEnv , 一個是JNIEnv二級指針 , 一個是JNIEnv一級指針 。基本用法:<span style="color:#000000"><span style="color:#cccccc"><span style="color:#404040"><code class="language-c"> <span style="color:#999999">// 得到jclass</span> jclass jcls <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetObjectClass</span>(env, jobj); </code></span></span></span>
<span style="color:#000000"><span style="color:#cccccc"><span style="color:#404040"><code class="language-c"><span style="color:#999999">// com.zeno.jni_HelloJNI.h</span> JNIEXPORT <span style="color:#cc99cd">void</span> JNICALL Java_com_zeno_jni_HelloJni_accessJavaStringField (JNIEnv <span style="color:#67cdcc">*</span>, jobject); <span style="color:#999999">// Hello_JNI.c</span> <span style="color:#999999">/*C語言訪問java String類型字段*/</span> JNIEXPORT <span style="color:#cc99cd">void</span> JNICALL Java_com_zeno_jni_HelloJni_accessJavaStringField (JNIEnv <span style="color:#67cdcc">*</span>env, jobject jobj) { <span style="color:#999999">// 得到jclass</span> jclass jcls <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetObjectClass</span>(env, jobj); <span style="color:#999999">// 得到字段ID</span> jfieldID jfID <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetFieldID</span>(env, jcls, <span style="color:#7ec699">"name"</span>, <span style="color:#7ec699">"Ljava/lang/String;"</span>); <span style="color:#999999">// 得到字段的值</span> jstring jstr <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetObjectField</span>(env, jobj, jfID); <span style="color:#999999">// 將jstring類型轉換成字符指針</span> <span style="color:#cc99cd">char</span><span style="color:#67cdcc">*</span> cstr <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetStringUTFChars</span>(env, jstr, JNI_FALSE); <span style="color:#999999">//printf("is vaule:%s\n", cstr);</span> <span style="color:#999999">// 拼接字符</span> <span style="color:#cc99cd">char</span> text[<span style="color:#f08d49">30</span>] <span style="color:#67cdcc">=</span> <span style="color:#7ec699">" xiaojiu and "</span>; <span style="color:#f08d49">strcat</span>(text, cstr); <span style="color:#999999">//printf("modify value %s\n", text);</span> <span style="color:#999999">// 將字符指針轉換成jstring類型</span> jstring new_str <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">NewStringUTF</span>(env, text); <span style="color:#999999">// 將jstring類型的變量 , 設置到java 字段中</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">SetObjectField</span>(env, jobj, jfID, new_str); }</code></span></span></span>
JNIEnvJNIEnv 表示 Java 調用 native 語言的環境,是一個封裝了幾乎全部 JNI 方法的指針。JNIEnv 只在創建它的線程生效,不能跨線程傳遞,不同線程的 JNIEnv 彼此獨立。native 環境中創建的線程,如果需要訪問 JNI,必須要調用 AttachCurrentThread 關聯,並使用 DetachCurrentThread 解除鏈接。二、兩種代碼風格(C/C++)JavaVM 和 JNIEnv 在 C 語言環境下和 C++ 環境下調用是有區別的,主要表現在:C風格:(*env)->NewStringUTF(env, “Hellow World!”);C++風格:env->NewStringUTF(“Hellow World!”);建議使用 C++ 風格,這也是大部分代碼使用的形式。注意:C++ 風格其實只是封裝了 C 風格,使得調用更加簡介方便。————————————————C 函數訪問java字段:
爲什麼要得到jclass呢 ?
因爲 ,我們要獲取字段ID , 在JNI中 , 獲取java字段與方法都需要簽名。而簽名是在類加載的時候完成 , 所以在獲取字段ID的時候需要傳入jclass 。:類似於反射一樣,先得到jclass。然後得到字段。<span style="color:#000000"><span style="color:#cccccc"><span style="color:#404040"><code class="language-c"> <span style="color:#999999">// 得到jclass</span> jclass jcls <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetObjectClass</span>(env, jobj); <span style="color:#999999">// 得到字段ID</span> jfieldID jfID <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetFieldID</span>(env, jcls, <span style="color:#7ec699">"name"</span>, <span style="color:#7ec699">"Ljava/lang/String;"</span>); <span style="color:#999999">// 得到字段的值</span> jstring jstr <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetObjectField</span>(env, jobj, jfID);</code></span></span></span>
C 調用Java對象的構造方法
C調用Java的構造方法與調用普通方法略有不同 , 其不同之處在於方法名稱上 , 普通方法直接使用方法名
, 構造方法則不是使用類名 , 而使用一個固定寫法<init>
。<span style="color:#000000"><span style="color:#cccccc"><span style="color:#404040"><code class="language-c"> <span style="color:#999999">// 得到構造方法id</span> jmethodID dateConstructMid <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetMethodID</span>(env, dateCls, <span style="color:#7ec699">"<init>"</span>, <span style="color:#7ec699">"()V"</span>);</code></span></span></span>
native方法爲static
在java中聲明的JNI的native方法靜態方法和非靜態,對於底層的C/C++代碼來說是有區別的:
1,JNI函數的參數也由三部分組成:首先是JNIEnv*,是一個指向JNI運行環境的指針;2,第二個參數隨本地方法是靜態還是非靜態而有所不同一一非靜態本地方法的第二個參數是對對象的引用,而靜態本地方法的第二個參數是對其Java類的引用;
3,其餘的參數對應通常Java方法的參數,參數類型需要根據一定規則進行映射。
4.jni語法:基本數據類型
以下是java的基本數據類型和jni中的基本數據類型的比較,及各類型的字節。
- 傳遞字符串
①, Java字符串轉爲c字符串<span style="color:#000000"><span style="color:#404040"><span style="color:#cccccc"><code class="language-cpp"><span style="color:#f8c555">#<span style="color:#cc99cd">include</span> <span style="color:#7ec699"><jni.h></span></span> <span style="color:#999999">//因爲下面用到的NULL在stdlib庫中,所以導入該庫</span> <span style="color:#f8c555">#<span style="color:#cc99cd">include</span> <span style="color:#7ec699"><stdlib.h></span> </span> <span style="color:#999999">/** * 把一個jstring轉換成一個c語言的char* 類型. */</span> <span style="color:#cc99cd">char</span><span style="color:#67cdcc">*</span> <span style="color:#f08d49">_JString2CStr</span>(JNIEnv<span style="color:#67cdcc">*</span> env, jstring jstr) { <span style="color:#cc99cd">char</span><span style="color:#67cdcc">*</span> rtn <span style="color:#67cdcc">=</span> <span style="color:#f8c555">NULL</span>; jclass clsstring <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">FindClass</span>(env, <span style="color:#7ec699">"java/lang/String"</span>); jstring strencode <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">NewStringUTF</span>(env,<span style="color:#7ec699">"GB2312"</span>); jmethodID mid <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetMethodID</span>(env, clsstring, <span style="color:#7ec699">"getBytes"</span>, <span style="color:#7ec699">"(Ljava/lang/String;)[B"</span>); jbyteArray barr <span style="color:#67cdcc">=</span> (jbyteArray)(<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">CallObjectMethod</span>(env, jstr, mid, strencode); <span style="color:#999999">// String .getByte("GB2312");</span> jsize alen <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetArrayLength</span>(env, barr); jbyte<span style="color:#67cdcc">*</span> ba <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetByteArrayElements</span>(env, barr, JNI_FALSE); <span style="color:#cc99cd">if</span>(alen <span style="color:#67cdcc">></span> <span style="color:#f08d49">0</span>) { rtn <span style="color:#67cdcc">=</span> (<span style="color:#cc99cd">char</span><span style="color:#67cdcc">*</span>)<span style="color:#f08d49">malloc</span>(alen<span style="color:#67cdcc">+</span><span style="color:#f08d49">1</span>); <span style="color:#999999">//"\0"</span> <span style="color:#f08d49">memcpy</span>(rtn, ba, alen); rtn[alen]<span style="color:#67cdcc">=</span><span style="color:#f08d49">0</span>; } (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">ReleaseByteArrayElements</span>(env, barr, ba,<span style="color:#f08d49">0</span>); <span style="color:#cc99cd">return</span> rtn; } </code></span></span></span>
語法參考:
https://www.jianshu.com/p/1229580b2356其他:混淆的時候:
如何加載NDK 庫 ?如何在JNI 中註冊Native 函數,有幾種註冊方法 ?參考回答:public class JniTest{//加載NDK 庫static{System.loadLirary("jni-test");}}註冊JNI 函數的兩種方法o 靜態方法談談你對JNIEnv 和JavaVM 理解?1.JavaVmJavaVM 是虛擬機在JNI 層的代表,一個進程只有一個JavaVM,所有的線程共用一個JavaVM。2.JNIEnvJNIEnv 表示Java 調用native 語言的環境,是一個封裝了幾乎全部JNI 方法的指針。JNIEnv 只在創建它的線程生效,不能跨線程傳遞,不同線程的JNIEnv 彼此獨立。native 環境中創建的線程,如果需要訪問JNI,必須要調用AttachCurrentThread關聯,並使用DetachCurrentThread 解除鏈接。so 的加載流程是怎樣的,生命週期是怎樣的?
這個要從 java 層去看源碼分析,是從 ClassLoader 的 PathList 中去找到目標路徑加載的,同時 so 是通過 mmap 加載映射到虛擬空間的。生命週期加載庫和卸載庫時分別調用 JNI_OnLoad 和 JNI_OnUnload() 方法。