JNI實際上是Java Native Interface的簡稱,也就是java本地接口。它提供了若干的API實現了和Java和其餘語言的通訊(主要是C&C++)。也許很多人覺 得Java已經足夠強大,爲何要須要JNI這種東西呢?咱們知道Java是一種平臺無關性的語言,平臺對於上層的java代碼來講是透明的,因此在多數 時間咱們是不須要JNI的,可是假如你遇到了以下的三種狀況之一呢? 前端
- 你的Java代碼,須要獲得一個文件的屬性。可是你找遍了JDK幫助文檔也找不到相關的API。
- 在本地還有一個別的系統,不過他不是Java語言實現的,這個時候你的老闆要求你把兩套系統整合到一塊兒。
- 你的Java代碼中須要用到某種算法,不過算法是用C實現並封裝在動態連接庫文件(DLL)當中的。
對於上述的三種狀況,若是沒有JNI的話,那就會變得異常棘手了。就算找到解決方案了,也是費時費力。其實說到底仍是會增長開發和維護的成本。 java
說了那麼多一通廢話,如今進入正題。看過JDK源代碼的人確定會注意到在源碼裏有不少標記成native的方法。這些個方法只有方法簽名可是沒有方 法體。其實這些naive方法就是咱們說的 java native interface。他提供了一個調用(invoke)的接口,而後用C或者C++去實現。咱們首先來編寫這個「橋樑」.我本身的開發環境是 j2sdk1.4.2_15 + eclipse 3.2 + VC++ 6.0,先在eclipse裏創建一個HelloFore的Java工程,而後編寫下面的代碼。 ios
- package com.chnic.jni;
-
- public class SayHellotoCPP {
-
- public SayHellotoCPP(){
- }
- public native void sayHello(String name);
- }
- package com.chnic.jni;
-
- public class SayHellotoCPP {
-
- public SayHellotoCPP(){
- }
- public native void sayHello(String name);
- }
通常的第一個程序老是HelloWorld。今天換換口味,把world換成一個名字。個人native本地方法有一個String的參數。會傳 遞一個name到後臺去。本地方法已經完成,如今來介紹下javah這個方法,接下來就要用javah方法來生成一個相對應的.h頭文件。 c++
javah是一個專門爲JNI生成頭文件的一個命令。CMD打開控制檯以後輸入javah回車就能看到javah的一些參數。在這裏就很少介紹咱們 要用的是 -jni這個參數,這個參數也是默認的參數,他會生成一個JNI式的.h頭文件。在控制檯進入到工程的根目錄,也就是HelloFore這個目錄,而後輸 入命令。 算法
- javah -jni com.chnic.jni.SayHellotoCPP
- javah -jni com.chnic.jni.SayHellotoCPP
命令執行完以後在工程的根目錄就會發現com_chnic_jni_SayHellotoCPP.h 這個頭文件。在這裏有必要多句嘴,在執行javah的時候,要輸入完整的包名+類名。不然在之後的測試調用過程當中會發生java.lang.UnsatisfiedLinkError這個異常。 編程
到這裏java部分算是基本完成了,接下來咱們來編寫後端的C++代碼。(用C也能夠,只不過cout比printf用起來更快些,因此這裏俺偷下 懶用C++)打開VC++首先新建一個Win32 Dynamic-Link library工程,以後選擇An empty DLL project空工程。在這裏我C++的工程是HelloEnd,把剛剛生成的那個頭文件拷貝到這個工程的根目錄裏。隨便用什麼文本編輯器打開這個頭文 件,發現有一個以下的方法簽名。 windows
- /*
- * Class: com_chnic_jni_SayHellotoCPP
- * Method: sayHello
- * Signature: (Ljava/lang/String;)V
- */
- JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
- (JNIEnv *, jobject, jstring);
- /*
- * Class: com_chnic_jni_SayHellotoCPP
- * Method: sayHello
- * Signature: (Ljava/lang/String;)V
- */
- JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
- (JNIEnv *, jobject, jstring);
仔細觀察一下這個方法,在註釋上標註類名、方法名、簽名(Signature),至於這個簽名是作什麼用的,咱們之後再說。在這裏最重要的是 Java_com_chnic_jni_SayHellotoCPP_sayHello這個方法簽名。在Java端咱們執行 sayHello(String name)這個方法以後,JVM就會幫咱們喚醒在DLL裏的Java_com_chnic_jni_SayHellotoCPP_sayHello這個方 法。所以咱們新建一個C++ source file來實現這個方法。 後端
- #include <iostream.h>
- #include "com_chnic_jni_SayHellotoCPP.h"
-
-
- JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
- (JNIEnv* env, jobject obj, jstring name)
- {
- const char* pname = env->GetStringUTFChars(name, NULL);
- cout << "Hello, " << pname << endl;
- }
- #include <iostream.h>
- #include "com_chnic_jni_SayHellotoCPP.h"
-
-
- JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
- (JNIEnv* env, jobject obj, jstring name)
- {
- const char* pname = env->GetStringUTFChars(name, NULL);
- cout << "Hello, " << pname << endl;
- }
由於咱們生成的那個頭文件是在C++工程的根目錄不是在環境目錄,因此咱們要把尖括號改爲單引號,至於VC++的環境目錄能夠在 Tools->Options->Directories裏設置。F7編譯工程發現缺乏jni.h這個頭文件。這個頭文件能夠 在%JAVA_HOME%/include目錄下找到。把這個文件拷貝到C++工程目錄,繼續編譯發現仍是找不到。原來是由於在咱們剛剛生成的那個頭文件 裏,jni.h這個文件是被 #include <jni.h>引用進來的,所以咱們把尖括號改爲雙引號#include "jni.h",繼續編譯發現少了jni_md.h文件,接着在%JAVA_HOME%/include/win32下面找到那個頭文件,放入到工程根目 錄,F7編譯成功。在Debug目錄裏會發現生成了HelloEnd.dll這個文件。 app
這個時候後端的C++代碼也已經完成,接下來的任務就是怎麼把他們鏈接在一塊兒了,要讓前端的java程序「認識並找到」這個動態連接庫,就必須把這個DLL放在windows path環境變量下面。有兩種方法能夠作到: dom
- 把這個DLL放到windows下面的sysytem32文件夾下面,這個是windows默認的path
- 複製你工程的Debug目錄,我這裏是C:/Program Files/Microsoft Visual Studio/MyProjects/HelloEnd/Debug這個目錄,把這個目錄配置到User variable的Path下面。重啓eclipse,讓eclipse在啓動的時候從新讀取這個path變量。
比較起來,第二種方法比較靈活,在開發的時候不用來回copy dll文件了,節省了不少工做量,因此在開發的時候推薦用第二種方法。在這裏咱們使用的也是第二種,eclipse重啓以後打開 SayHellotoCPP這個類。其實咱們上面作的那些是否是是讓JVM能找到那些DLL文件,接下來咱們要讓咱們本身的java代碼「認識」這個動態 連接庫。加入System.loadLibrary("HelloEnd");這句到靜態初始化塊裏。
- package com.chnic.jni;
-
- public class SayHellotoCPP {
-
- static{
- System.loadLibrary("HelloEnd");
- }
- public SayHellotoCPP(){
- }
- public native void sayHello(String name);
-
- }
- package com.chnic.jni;
-
- public class SayHellotoCPP {
-
- static{
- System.loadLibrary("HelloEnd");
- }
- public SayHellotoCPP(){
- }
- public native void sayHello(String name);
-
- }
這樣咱們的代碼就能認識並加載這個動態連接庫文件了。萬事俱備,只欠測試代碼了,接下來編寫測試代碼。
- SayHellotoCPP shp = new SayHellotoCPP();
- shp.sayHello("World");
- SayHellotoCPP shp = new SayHellotoCPP();
- shp.sayHello("World");
咱們不讓他直接Hello,World。咱們把World傳進去,執行代碼。發現控制檯打印出來Hello, World這句話。就此一個最簡單的JNI程序已經開發完成。也許有朋友會對CPP代碼裏的
- const char* pname = env->GetStringUTFChars(name, NULL);
- const char* pname = env->GetStringUTFChars(name, NULL);
這句有疑問,這個GetStringUTFChars就是JNI給developer提供的API,咱們之後再講。在這裏不得很少句嘴。
- 由於JNI有一個Native這個特色,一點有項目用了JNI,也就說明這個項目基本不能跨平臺了。
- JNI調用是至關慢的,在實際使用的以前必定要先想明白是否有這個必要。
- 由於C++和C這樣的語言很是靈活,一不當心就容易出錯,好比我剛剛的代碼就沒有寫析構字符串釋放內存,對於java developer來講由於有了GC 垃圾回收機制,因此大多數人沒有寫析構函數這樣的概念。因此JNI也會增長程序中的風險,增大程序的不穩定性。
其實在Java代碼中,除了對本地方法標註native關鍵字和加上要加載動態連接庫以外,JNI基本上是對上層coder透明的,上層coder調用那些本地方法的時候並不知道這個方法的方法體到底是在哪裏,這個道理就像咱們用JDK所提供的API同樣。因此在Java中使用JNI仍是很簡單的,相比之下在C++中調用java,就比前者要複雜的多了。
如今來介紹下JNI裏的數據類型。在C++裏,編譯器會很據所處的平臺來爲一些基本的 數據類型來分配長度,所以也就形成了平臺不一致性,而這個問題在Java中則不存在,由於有JVM的緣故,因此Java中的基本數據類型在全部平臺下獲得 的都是相同的長度,好比int的寬度永遠都是32位。基於這方面的緣由,java和c++的基本數據類型就須要實現一些mapping,保持一致性。下面 的表能夠歸納:
Java類型 |
本地類型 |
JNI中定義的別名 |
int |
long |
jint |
long |
_int64 |
jlong |
byte |
signed char |
jbyte |
boolean |
unsigned char |
jboolean |
char |
unsigned short |
jchar |
short |
short |
jshort |
float |
float |
jfloat |
double |
double |
jdouble |
Object |
_jobject* |
jobject |
上面的表格是我在網上搜的,放上來給你們對比一下。對於每一種映射的數據類型,JNI的設計者其實已經幫咱們取好了相應的別名以方便記憶。若是想了解一些更加細緻的信息,能夠去看一些jni.h這個頭文件,各類數據類型的定義以及別名就被定義在這個文件中。
瞭解了JNI中的數據類型,下面就來看此次的例子。此次咱們用Java來實現一個前端的market(如下就用Foreground代替)用CPP來實現一個後端factory(如下用backend代替)。咱們首先仍是來編寫包含本地方法的java類。
- package com.chnic.service;
-
- import com.chnic.bean.Order;
-
- public class Business {
- static{
- System.loadLibrary("FruitFactory");
- }
-
- public Business(){
-
- }
-
- public native double getPrice(String name);
- public native Order getOrder(String name, int amount);
- public native Order getRamdomOrder();
- public native void analyzeOrder(Order order);
-
- public void notification(){
- System.out.println("Got a notification.");
- }
-
- public static void notificationByStatic(){
- System.out.println("Got a notification in a static method.");
- }
- }
- package com.chnic.service;
-
- import com.chnic.bean.Order;
-
- public class Business {
- static{
- System.loadLibrary("FruitFactory");
- }
-
- public Business(){
-
- }
-
- public native double getPrice(String name);
- public native Order getOrder(String name, int amount);
- public native Order getRamdomOrder();
- public native void analyzeOrder(Order order);
-
- public void notification(){
- System.out.println("Got a notification.");
- }
-
- public static void notificationByStatic(){
- System.out.println("Got a notification in a static method.");
- }
- }
這個類裏面包含4個本地方法,一個靜態初始化塊加載將要生成的dll文件。剩下的方法都是很普通的java方法,等會在backend中回調這些方法。這個類須要一個名爲Order的JavaBean。
- package com.chnic.bean;
-
- public class Order {
-
- private String name = "Fruit";
- private double price;
- private int amount = 30;
-
- public Order(){
-
- }
-
- public int getAmount() {
- return amount;
- }
-
- public void setAmount(int amount) {
- this.amount = amount;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public double getPrice() {
- return price;
- }
-
- public void setPrice(double price) {
- this.price = price;
- }
- }
- package com.chnic.bean;
-
- public class Order {
-
- private String name = "Fruit";
- private double price;
- private int amount = 30;
-
- public Order(){
-
- }
-
- public int getAmount() {
- return amount;
- }
-
- public void setAmount(int amount) {
- this.amount = amount;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public double getPrice() {
- return price;
- }
-
- public void setPrice(double price) {
- this.price = price;
- }
- }
JavaBean中,咱們爲兩個私有屬性賦值,方便後面的例子演示。到此爲止除了測試代碼以外的Java端的代碼就所有高調了,接下來進行生成.h 頭文件、創建C++工程的工做,在這裏就一筆帶過,不熟悉的朋友請回頭看第一篇。在工程裏咱們新建一個名爲Foctory的C++ source file 文件,去實現那些native方法。具體的代碼以下。
- #include <iostream.h>
- #include <string.h>
- #include "com_chnic_service_Business.h"
-
- jobject getInstance(JNIEnv* env, jclass obj_class);
-
- JNIEXPORT jdouble JNICALL Java_com_chnic_service_Business_getPrice(JNIEnv* env,
- jobject obj,
- jstring name)
- {
- const char* pname = env->GetStringUTFChars(name, NULL);
- cout << "Before release: " << pname << endl;
-
- if (strcmp(pname, "Apple") == 0)
- {
- env->ReleaseStringUTFChars(name, pname);
- cout << "After release: " << pname << endl;
- return 1.2;
- }
- else
- {
- env->ReleaseStringUTFChars(name, pname);
- cout << "After release: " << pname << endl;
- return 2.1;
- }
- }
-
-
- JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getOrder(JNIEnv* env,
- jobject obj,
- jstring name,
- jint amount)
- {
- jclass order_class = env->FindClass("com/chnic/bean/Order");
- jobject order = getInstance(env, order_class);
-
- jmethodID setName_method = env->GetMethodID(order_class, "setName", "(Ljava/lang/String;)V");
- env->CallVoidMethod(order, setName_method, name);
-
- jmethodID setAmount_method = env->GetMethodID(order_class, "setAmount", "(I)V");
- env->CallVoidMethod(order, setAmount_method, amount);
-
- return order;
- }
-
- JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getRamdomOrder(JNIEnv* env,
- jobject obj)
- {
- jclass business_class = env->GetObjectClass(obj);
- jobject business_obj = getInstance(env, business_class);
-
- jmethodID notification_method = env->GetMethodID(business_class, "notification", "()V");
- env->CallVoidMethod(obj, notification_method);
-
- jclass order_class = env->FindClass("com/chnic/bean/Order");
- jobject order = getInstance(env, order_class);
- jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
- jint amount = env->GetIntField(order, amount_field);
- cout << "amount: " << amount << endl;
- return order;
- }
-
-
- JNIEXPORT void JNICALL Java_com_chnic_service_Business_analyzeOrder (JNIEnv* env,
- jclass cls,
- jobject obj)
- {
- jclass order_class = env->GetObjectClass(obj);
- jmethodID getName_method = env->GetMethodID(order_class, "getName", "()Ljava/lang/String;");
- jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
- const char* pname = env->GetStringUTFChars(name_str, NULL);
-
- cout << "Name in Java_com_chnic_service_Business_analyzeOrder: " << pname << endl;
- jmethodID notification_method_static = env->GetStaticMethodID(cls, "notificationByStatic", "()V");
- env->CallStaticVoidMethod(cls, notification_method_static);
-
- }
-
- jobject getInstance(JNIEnv* env, jclass obj_class)
- {
- jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");
- jobject obj = env->NewObject(obj_class, construction_id);
- return obj;
- }
- #include <iostream.h>
- #include <string.h>
- #include "com_chnic_service_Business.h"
-
- jobject getInstance(JNIEnv* env, jclass obj_class);
-
- JNIEXPORT jdouble JNICALL Java_com_chnic_service_Business_getPrice(JNIEnv* env,
- jobject obj,
- jstring name)
- {
- const char* pname = env->GetStringUTFChars(name, NULL);
- cout << "Before release: " << pname << endl;
-
- if (strcmp(pname, "Apple") == 0)
- {
- env->ReleaseStringUTFChars(name, pname);
- cout << "After release: " << pname << endl;
- return 1.2;
- }
- else
- {
- env->ReleaseStringUTFChars(name, pname);
- cout << "After release: " << pname << endl;
- return 2.1;
- }
- }
-
-
- JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getOrder(JNIEnv* env,
- jobject obj,
- jstring name,
- jint amount)
- {
- jclass order_class = env->FindClass("com/chnic/bean/Order");
- jobject order = getInstance(env, order_class);
-
- jmethodID setName_method = env-><span class="hilite1">GetMethodID</span>(order_class, "setName", "(Ljava/lang/String;)V");
- env->CallVoidMethod(order, setName_method, name);
-
- jmethodID setAmount_method = env-><span class="hilite1">GetMethodID</span>(order_class, "setAmount", "(I)V");
- env->CallVoidMethod(order, setAmount_method, amount);
-
- return order;
- }
-
- JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getRamdomOrder(JNIEnv* env,
- jobject obj)
- {
- jclass business_class = env->GetObjectClass(obj);
- jobject business_obj = getInstance(env, business_class);
-
- jmethodID notification_method = env-><span class="hilite1">GetMethodID</span>(business_class, "notification", "()V");
- env->CallVoidMethod(obj, notification_method);
-
- jclass order_class = env->FindClass("com/chnic/bean/Order");
- jobject order = getInstance(env, order_class);
- jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
- jint amount = env->GetIntField(order, amount_field);
- cout << "amount: " << amount << endl;
- return order;
- }
-
-
- JNIEXPORT void JNICALL Java_com_chnic_service_Business_analyzeOrder (JNIEnv* env,
- jclass cls,
- jobject obj)
- {
- jclass order_class = env->GetObjectClass(obj);
- jmethodID getName_method = env-><span class="hilite1">GetMethodID</span>(order_class, "getName", "()Ljava/lang/String;");
- jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
- const char* pname = env->GetStringUTFChars(name_str, NULL);
-
- cout << "Name in Java_com_chnic_service_Business_analyzeOrder: " << pname << endl;
- jmethodID notification_method_static = env->GetStaticMethodID(cls, "notificationByStatic", "()V");
- env->CallStaticVoidMethod(cls, notification_method_static);
-
- }
-
- jobject getInstance(JNIEnv* env, jclass obj_class)
- {
- jmethodID construction_id = env-><span class="hilite1">GetMethodID</span>(obj_class, "<init>", "()V");
- jobject obj = env->NewObject(obj_class, construction_id);
- return obj;
- }
能夠看到,在我Java中的四個本地方法在這裏所有被實現,接下來針對這四個方法來解釋下,一些JNI相關的API的使用方法。先從第一個方法講起吧:
1.getPrice(String name)
這個方法是從foreground傳遞一個類型爲string的參數到backend,而後backend判斷返回相應的價格。在cpp的代碼中, 咱們用GetStringUTFChars這個方法來把傳來的jstring變成一個UTF-8編碼的char型字符串。由於jstring的實際類型是 jobject,因此沒法直接比較。
GetStringUTFChars方法包含兩個參數,第一參數是你要處理的jstring對象,第二個參數是否須要在內存中生成一個副本對象。將 jstring轉換成爲了一個const char*了以後,咱們用string.h中帶strcmp函數來比較這兩個字符串,若是傳來的字符串是「Apple」的話咱們返回1.2。反之返回 2.1。在這裏還要多說一下ReleaseStringUTFChars這個函數,這個函數從字面上不難理解,就是釋放內存用的。有點像cpp裏的析構函 數,只不過Sun幫咱們已經封裝好了。因爲在JVM中有GC這個東東,因此多數java coder並無寫析構的習慣,不過在JNI裏是必須的了,不然容易形成內存泄露。咱們在這裏在release以前和以後分別打出這個字符串來看一下效果。
粗略的解釋完一些API以後,咱們編寫測試代碼。
- Business b = new Business();
- System.out.println(b.getPrice("Apple"));
- Business b = new Business();
- System.out.println(b.getPrice("Apple"));
運行這段測試代碼,控制檯上打出
Before release: Apple
After release: ? ?
1.2
在release以前打印出來的是咱們「須要」的Apple,release以後就成了亂碼了。因爲傳遞的是Apple,因此獲得1.2。測試成功。
2. getOrder(String name, int amount)
在foreground中能夠經過這個方法讓backend返回一個你「指定」的Order。所謂「指定」,其實也就是指方法裏的兩個參數:name和amout,在cpp的代碼在中,會根據傳遞的兩個參數來構造一個Order。回到cpp的代碼裏。
- jclass order_class = env->FindClass("com/chnic/bean/Order");
- jclass order_class = env->FindClass("com/chnic/bean/Order");
是否是以爲這句代碼似曾相識?沒錯,這句代碼很像咱們java裏寫的Class.forName(className)反射的代碼。其實在這裏 FindClass的做用和上面的forName是相似的。只不過在forName中要用完整的類名,可是在這裏必須用"/"來代替「.」。這個方法會返 回一個jclass的對象,其實也就是咱們在Java中說的類對象。
- jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");
- jobject obj = env->NewObject(obj_class, construction_id);
- jmethodID construction_id = env-><span class="hilite1">GetMethodID</span>(obj_class, "<init>", "()V");
- jobject obj = env->NewObject(obj_class, construction_id);
拿到"類對象"了以後,按照Java RTTI的邏輯咱們接下來就要喚醒那個類對象的構造函數了。在JNI中,包括構造函數在內的全部方法都被當作Method。每一個method都有一個特定的ID,咱們經過GetMethodID這個方法就能夠拿到咱們想要的某一個java 方法的ID。GetMethodID須要傳三個參數,第一個是很顯然jclass,第二個參數是java方法名,也就是你想取的method ID的那個方法的方法名(有些繞口 ),第三個參數是方法簽名。
在這裏有必要單獨來說一講這個方法簽名,爲何要用這個東東呢?咱們知道,在Java裏方法是能夠被重載的,好比我一個類裏有public void a(int arg)和public void a(String arg)這兩個方法,在這裏用方法名來區分方法顯然就是行不通的了。方法簽名包括兩部分:參數類型和返回值類型;具體的格式:(參數1類型簽名參數2類型 簽名)返回值類型簽名。下面是java類型和年名類型的對照的一個表
Java類型 |
對應的簽名 |
boolean |
Z |
byte |
B |
char |
C |
shrot |
S |
int |
I |
long |
L |
float |
F |
double |
D |
void |
V |
Object |
L用/分割包的完整類名; Ljava/lang/String; |
Array |
[簽名 [I [Ljava/lang/String; |
其實除了本身對照手寫以外,JDK也提供了一個很好用的生成簽名的工具javap,cmd進入控制檯到你要生成簽名的那個類的目錄下。在這裏用 Order類打比方,敲入: javap -s -private Order。全部方法簽名都會被輸出,關於javap的一些參數能夠在控制檯下面輸入 javap -help查看。(作coder的 畢竟仍是要認幾個單詞的)
囉嗦了一大堆,仍是回到咱們剛剛的getMethodID這個方法上。由於是調用構造函數,JNI規定調用構造函數的時候傳遞的方法名應該爲<init> ,經過javap查看 咱們要的那個無參的構造函數的方法籤是()V。獲得方法簽名,最後咱們調用NewObject方法來生成一個新的對象。
拿到了對象,以後咱們開始爲對象jobject填充數值,仍是首先拿到setXXX方法的Method ID,以後調用Call<Type>Method來調用java方法。這裏的<Type>所指的是方法的返回類型,咱們剛剛調用 的是set方法的返回值是void,所以這裏的方法也就是CallVoidMethod,這個方法的參數除了前兩個要傳入jobject和 jmethodID以外還要傳入要調用的那個方法的參數,並且要順序必須一致,這點和Java的反射如出一轍,在這裏就很少解釋。(看到這一步是否是對 java 反射又有了本身新的理解?)
終於介紹完了第二個方法,下來就是測試代碼測試。
- Order o = b.getOrder("Watermelom", 100);
- System.out.println("java: " + o.getName());
- System.out.println("java: " + o.getAmount());
- Order o = b.getOrder("Watermelom", 100);
- System.out.println("java: " + o.getName());
- System.out.println("java: " + o.getAmount());
控制檯打出
java: Watermelom
java: 100
就此,咱們完成了第二個方法的測試。
3.getRamdomOrder()
這個方法會從backend獲得一個隨機的Order對象(抱歉這裏「Random」拼錯了),而後再調用java中相應的通知方法來通知 foreground。getRamdomOrder方法沒有參數,可是所對應的C++方法裏卻有兩個參數,必定有人會不解。其實細心的朋友必定會發 現,JNI裏全部對應Java方法的C++ 方法都會比Java方法多兩個參數,第一個參數是咱們很熟悉的JNIEnv*指針,第二個參數有時是jobject有時是個jclass。針對這第二個參 數在這裏有必要多廢話兩句。
其實第二個參數傳遞的是包含了native本地方法的對象或者類對象,咱們知道非靜態的方法是屬於某一個對象的,而靜態方法是屬於類對象的,因此靜 態方法能夠被全部對象共享。有這個對象/類對象,咱們就能夠很方便的操做包含了native方法的對象的一些函數了。(這句話有點繞口,沒看明白的建議多 讀兩遍)。
廢話完了言歸正傳,由於getRamdomOrder不是靜態的,因此C++相對應的參數中傳遞來的是一個jobject對象。
- jclass business_class = env->GetObjectClass(obj);
- jclass business_class = env->GetObjectClass(obj);
這一句不難理解,GetObjectClass方法能夠獲得一個對象的類對象,這句有點像Java中的Object.class。不熟悉的朋友建議 再去看一下Java反射機制。接下來的幾句C++代碼應該在以前的方法1和方法2中都解釋過。早backend端會發一個「消息」給 foreground,以後new一個新的Order類出來。接下來的三句有必要再廢話一下。
- jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
- jint amount = env->GetIntField(order, amount_field);
- cout << "amount: " << amount << endl;
- jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
- jint amount = env->GetIntField(order, amount_field);
- cout << "amount: " << amount << endl;
以前我爲Order這個Javabean的amount的屬性設置了一個初始值爲30,其實就是爲了在這裏演示如何在C++中拿一個Java對象的 屬性,拿的方法和咱們以前說過的調用Java方法的程序差很少,也要先拿到一個jfieldID,以後調用Get<type>Field方法 來取得某一個對象中的某一個屬性的數值,最後cout把他打印出來。咱們編寫測試代碼來看一下最終效果。
- Business b = new Business();
- Order o2 = b.getRamdomOrder();
- System.out.println(o2.getName());
- Business b = new Business();
- Order o2 = b.getRamdomOrder();
- System.out.println(o2.getName());
運行上述的測試代碼以後,控制檯上打出了
Got a notification.
amount: 30
Fruit
和咱們想要的結果是同樣的,測試成功。
4.analyzeOrder(Order order)
這是一個靜態方法,foreground會經過這個方法傳一個Order的對象到backend去,而後再由CPP端進行「analyze」。在這 裏咱們取出來傳遞過來的Order對象的name屬性,而後打印到控制檯上。由於這個方法是靜態static方法,因此相對應的C++方法中的第二個參數 也變成了jclass對象,也就是Business.class這個類對象。第三個參數是一個jobject對象,很明顯就是咱們傳遞過來的order對 象。
前5句代碼應該不難理解,就是調用getName這個方法,而後打印出來。由於JNI的API中並無提供CallStringMethod這個方 法,因此咱們用CallObjectMethod這個方法來取得name這個字符串(String很明顯也是一個Object),而後再轉型成爲 jstring。也就是下面這句代碼。
- jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
- jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
取到了name這個字符串以後cout打印出來,以後調用Business這個類對象中的靜態方法notificationByStatic來通知 foreground。調用的流程以及方法和非靜態都是同樣的,只不過注意JNI中調用靜態方法的API所傳遞的一個參數是一個jclass而非 jobject(這個也不難理解,由於靜態方法是屬於class類對象的)
仍是編寫測試代碼測試這個方法
- Business b = new Business();
- Order o = b.getOrder("Watermelom", 100);
- Business.analyzeOrder(o);
- Business b = new Business();
- Order o = b.getOrder("Watermelom", 100);
- Business.analyzeOrder(o);
控制檯上打印出
Name in Java_com_chnic_service_Business_analyzeOrder: Watermelom
Got a notification in a static method.
第一句是C++中cout打印出來的,第二句則是Java中的靜態方法打印出來的,和咱們想要的結果是一致的。
呼~好不容易介紹完了4個方法,最後總結一下吧。
- JNI中所提供的API遠遠不止這4個方法中所使用的API。上面介紹的都是比較經常使用的,本人也不可能羅列出全部的API。
- 瞭解了JNI編程更加有利於深刻了解Java中的反射機制,反之亦然。
所以若是有對JNI編程有興趣或者有更深刻的須要,能夠參考一下sun的相關文檔。在這裏上傳sun提供的JNI的API手冊,還有上面例子中所用的演示代碼給你們參考。