本篇我們主要學習如何從C源碼中調用Java代碼:調用Java類中的成員變數,成員方法,同時也講介紹使用gradle-experimental來調試原生代碼。 ...
上一篇中,我們主要學習了Java調用本地方法,併列舉了兩大特殊實例來例證我們的論據,還沒學習的伙伴必須先去閱讀下,本次的學習是直接在上一篇的基礎上進行了。點擊:Android NDK開發之從Java與C互調中詳解JNI使用(一)
本篇我們主要學習如何從C源碼中調用Java代碼,以及使用gradle-experimental來調試原生代碼。
C 調用 Java 成員變數
- 首先我們現在Java2CJNI類中定義幾個成員變數,如下:
這裡定義了兩個普通成員變數和一個靜態成員變數。
就像C不能直接使用Java的引用類型一樣,C也不能直接的訪問Java成員變數,而是通過JNI所封裝的API來調用Java成員。通常會有如下的步驟:
1:獲取java實例對象的引用
2:通過實例對象獲取java成員變數ID
3:通過變數ID獲取java成員變數
那麼我們現在分步的講下學習以上的步驟。
獲取java實例對象的引用
獲取實例對象的引用JNI已為我們封裝好了方法,我們可以使用GetObjectClass函數來獲取class對象:
jclass (GetObjectClass)(JNIEnv, jobject);
例如:
jclass class = (*env)->GetObjectClass(env, jobj);
jobj對象我們在上一節也講過,這個是Java調用本地方法時,JNI會封裝調用類的一個實例,在這裡就是Java2CJNI類的引用。
另外一種方法也是可以獲取到class對象,就是通過反射機制來獲取對象:
jclass (FindClass)(JNIEnv, const char*);
例如:
jclass class = (*env)->FindClass(env, "com/sanhui/ndkdemo/Java2CJNI");
同上面的方法一樣,都可以獲取Java對象引用。
通過實例對象獲取java成員變數ID
由第一步我們獲取到了實例對象的引用,那麼我們可以通過JNI封裝的方法來獲取實例內的變數ID:
①:獲取普通成員變數ID
通過jfieldID (GetFieldID)(JNIEnv, jclass, const char, const char);可以獲取到一個jfieldID 類型的ID。
GetFieldID函數中第三個參數是Java類中的成員變數的名稱,如果在Java2CJNI類中定義的成員private String codeError = "驗證碼錯誤 !"中codeError 。第四個參數是變數簽名,說白點就是Java類中成員變數的返回類型,如變數codeError 的返回類型是String ,但是String在原生代碼中屬於引用類型,不能直接識別,所以在JNI中有相應的簽名映射,如下表:
Java 類型 | JNI 簽名映射 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
fully-qualified-class | Lfully-qualified-class ; |
type[] | [type |
method type | ( arg-types ) ret-type |
上述中基本數據類型的簽名多以大寫類型首字母為主,但是引用類型是使用“L”+ 類型路徑 + “;”,如String類型則是“Ljava/lang/String;”,數組則是"[" + 類型,如“[I”表示整形數組,如果是Java方法則是“(參數的類型) + 返回值類型” 。
ok,通過GetFieldID獲取成員變數ID,如:
jfieldID codeErrorID = (*env)->GetFieldID(env,jclazz,"codeError","Ljava/lang/String;");
②:獲取靜態成員變數ID
成員變數分為普通和靜態變數,那獲取靜態變數該如何呢?JNI也為我們封裝好了方法:
jfieldID (GetStaticFieldID)(JNIEnv, jclass, const char,
const char);
通過GetStaticFieldID函數可以獲取到靜態變數ID,參數如①一樣。舉例:
jfieldID loginSuccID = (*env)->GetStaticFieldID(env,jclazz,"loginSucc","Ljava/lang/String;");
通過變數ID獲取java成員變數
ok,獲取完變數ID,我們就可以通過ID來取得變數了,這裡獲取成員變數也是分為靜態和普通,分別使用:
jobject (GetObjectField)(JNIEnv, jobject, jfieldID);和jobject (GetStaticObjectField)(JNIEnv, jclass, jfieldID);函數。
例如:
jstring jcodeError = (*env)->GetObjectField(env,jobj,codeErrorID);
jstring juserNameError = (*env)->GetObjectField(env,jobj,userNameErrorID);
jstring jloginSucc = (*env)->GetStaticObjectField(env,jclazz,loginSuccID);
ok,到這裡我們就獲取到了Java類中的成員變數,來看下整體代碼:
獲取到Java中的成員變數,然後返回到Java中,然後我們通過Toast給列印出來:
來看下執行結果,是否如我們多料想的一樣:
從上圖中我們看到的執行結果顯然和我們在Java2CJNI中定義的loginSucc成員變數是一樣的,由此可以得出結論就是C成功的調用了Java類中的變數。
註意:由於獲取Java類中的變數需要在原生代碼中調用幾個方法才能獲取到最終的結果,對於性能來說要求的開銷過大,所以建議一般不這樣直接轟C中調用Java的成員變數,如果有需要建議以參數的形式傳遞給原生代碼。
C 調用 Java 方法
C調用Java方法和調用成員變數基本是一樣的,首先我們現在Java類中定義一個方法,用Toast來顯示信息,如:
上面也說過C調用Java方法和變數步驟基本一樣,下麵來看下基本步驟:
1:獲取java實例對象的引用
2:通過實例對象獲取實例方法ID
3:通過方法ID調用實際的Java方法
獲取java實例對象的引用
這一步和C獲取變數所介紹的獲取方式是一樣的,都是通過GetObjectClass或是FindClass函數來獲取的,這裡就不再贅述,可以參考上面的實力。
通過實例對象獲取實例方法ID
java中方法分為兩類,一類是普通的方法,一類是靜態方法。下麵來逐一的介紹。
①:獲取普通方法ID:
可以通過jmethodID (GetMethodID)(JNIEnv, jclass, const char, const char);來獲取方法ID,這也是JNI已經封裝好的原生方法,來解釋下這個函數:
GetMethodID函數前兩個參數就不必多介紹了,其中第三個參數是Java類中的方法名稱,對應的是Java2CJNI類中定義的方法:public void showMessage(String message){}中的showMessage。第四個參數是方法簽名,也就是Java類中方法的返回類型,至於什麼是簽名上面已介紹清楚。
獲取方法ID實例:
jmethodID showMessage = (*env)->GetMethodID(env,jclazz,"showMessage","(Ljava/lang/String;)V");
這裡和變數唯一不同的是,方法有可能帶參數,那麼簽名就需要帶上參數簽名和返回值簽名,也就是在()里的是參數簽名,()外的是返回值簽名,如“(Ljava/lang/String;)V”表示是含有一個String類型的參數和一個void的無返回類型。
②:獲取靜態方法ID:
獲取靜態方法ID會使用JNI的 jmethodID (GetStaticMethodID)(JNIEnv, jclass, const char, const char);函數,它的使用和參數與GetMethodID一樣,並沒有什麼差別。
例如:
jmethodID showMessage = (*env)->GetStaticMethodID(env,jclazz,"showMessage","(Ljava/lang/String;)V")
3:通過方法ID調用實際的Java方法
獲取到方法ID後,我們可以通過JNI提供的回調函數來真正的調用Java方法,這裡也是分為回調普通方法和靜態方法,由於兩者基本沒什麼差別,我們這裡就只講下普通方法的回到。
C回調Java方法會使用Call< type >Method函數來回調實際的方法,例如,我們調用我們顯示Toast的無返回值方法:
(*env)->CallVoidMethod(env,jobj,showMessage,jloginSucc);
直接的調用CallVoidMethod,它第三個參數傳入的是jmethodID類型的方法ID,由之前獲取到的,第四個參數是要傳遞給Java的參數,這裡接受的是一個String累的字元串。
ok,通過上面的三個步驟,我們已調用了Java的方法了,來看下整體的C代碼實現吧。
好,來看下執行結果:
ok,到這裡C調用Java就講完了,下麵講下實用的C原生代碼怎麼斷點調試。
Androidstudio中原生代碼斷點調試
下載C/C++調試器LLDB
在androidstudio->File->settings->androidSDK->SDK Tools中下載LLDB:
在項目目錄下的build.gradle文件添加對gradle-experimental的依賴
用項目對gradle-experimental的依賴代替原本項目對gradle的依賴。
配置工程下的build.gradle
① 使用com.android.model.application替代原來的com.android.application
② 把原有的配置存放在model{}中
③ 所有的配置屬性使用等號(=)連接
DUG運行
原生C代碼中斷點,然後執行dug運行模式。
ok,接下來就可以執行你的源代碼了。
好了,今天就講到這裡吧。
請關註微信公眾號。謝謝