一、前言
最近在Android上的NDK開發時遇到一個問題,在Java層需要獲取到設備的註冊信息,然後在JNI層將這些信息封裝爲結構體參數的形式傳遞到C++中的方法中進行處理。也就是說,在Java層獲取到的信息需要先轉換成結構體,再傳進去,在C++和Java的JNI層轉換的這個過程中整整卡了兩三天,一直找不到解決問題的思路。
二、分析
從結構體的特性來看,其實結構體就是不同屬性的合集,只不過嵌套結構體是在結構體內部還包含了一個結構體。而在Java層中,類的特性和結構體比較類似,而且用Java的類來替換比較直觀,類中的屬性和結構體中的屬性可以一一對應。舉一反三,嵌套結構體對應的自然就是嵌套類了(嵌套類指的是含有內部類的外部類)。
三、舉例
嵌套結構體如下:
typedef struct _Stru_Outer_Info // 外部結構體
{
StruInnerInfo struInnerInfo; // 嵌套的內部結構體
char outerName[OUTERNAME_LEN]; // 外部結構體名稱
}StruOuterInfo;
typedef struct _Stru_Inner_Info // 內部結構體
{
char innerName[INNERNAME_LEN]; // 內部結構體名稱
}StruInnerInfo;
需要傳進該嵌套結構體的方法:
void callInnerClassField(const StruOuterInfo* struOuterInfo); // 需傳進嵌套結構體
1
1、Java層聲明一個嵌套類來對應C++中的嵌套結構體
(注:這裏嵌套類用Outer和Inner類來對應比較直觀,StruOuterInfo對應Outer類,StruInnerInfo對應內部類)
// 外部類OuterClass 對應 StruOuterInfo外部結構體
public class OuterClass {
private String outerName;
private InnerClass innerClass;
OuterClass(){ // 外部類構造函數,對內部類實例化
innerClass = new InnerClass();
}
public InnerClass getInnerClass(){ // 獲取內部類對象,在Java層調用該方法即可獲得內部類對象InnerClass,並使用該對象對屬性進行設置
return innerClass;
}
public void setOuterName(String name){
this.outerName = name;
}
public String getOuterName() {
return outerName;
}
// 內部類InnerClass 對應 StruInnerInfo內部類結構體
class InnerClass{
private String innerName;
public String getInnerName() {
return innerName;
}
public void setInnerName(String name){
this.innerName = name;
}
}
}
注意:
在外部類的構造函數中進行內部類的實例化,並聲明瞭一個提供內部類對象的方法。在Java調用的時候一定要通過getInnerClass()來獲取內部類的對象,不能通過new來創建。如果通過new來創建,那麼是分配一個新的內存空間,在C++層中獲取到的不再是同一個對象!
2、在JNI層創建一個與C++中的callInnerClassField相對應的方法
extern "C" JNIEXPORT void JNICALL
Java_com_example_hasee_ndkdemo_NDKUtil_callInnerClassField( // 注意方法名格式:Java_包名_類名_方法名
JNIEnv *env, jobject instance,jobject outerClassObject) { // 這裏需要傳入一個外部類對象參數,與外部結構體相對應
// 外部類
jclass outer_class_cls = env -> GetObjectClass(outerClassObject); // 通過外部類對象獲取外部類的引用
jfieldID outer_name_fid = env -> GetFieldID(outer_class_cls,"outerName","Ljava/lang/String;"); // 通過外部類引用獲取外部類的屬性ID
jstring outerName = (jstring)env -> GetObjectField(outerClassObject,outer_name_fid); // 通過外部類對象和屬性ID獲取外部類的屬性
if (outerName == NULL){
LOGE("外部類名字爲空!!!" );
}else{
const char *outerName_ = env -> GetStringUTFChars(outerName, 0);
LOGE("外部類名字:%s" , outerName_);
env -> ReleaseStringUTFChars(outerName,outerName_); // 記得手動釋放字符串所佔用的內存空間
}
// 關鍵部分 將InnerClass作爲外部類的一個屬性,通過外部類獲取屬性ID的方法來獲取到內部類的對象引用
jfieldID inner_class_fid = env -> GetFieldID(outer_class_cls,"innerClass","Lcom/example/hasee/ndkdemo/OuterClass$InnerClass;");
jobject innerClassObject = env -> GetObjectField(outerClassObject,inner_class_fid); // 獲取到內部類對象引用
jclass inner_class_cls = env -> GetObjectClass(innerClassObject); // 通過內部類對象引用獲取內部類引用
jfieldID inner_name_fid = env -> GetFieldID(inner_class_cls,"innerName","Ljava/lang/String;"); // 同過內部類引用獲取到內部類屬性ID,注意屬性簽名爲Ljava/lang/String後面記得還要加上“;”
jstring innerName = (jstring)env -> GetObjectField(innerClassObject,inner_name_fid); // 通過內部類對象和屬性ID獲取內部類的屬性
if (innerName == NULL){
LOGE("內部類名字爲空!!!!!!");
}else{
const char *innerName_ = env -> GetStringUTFChars(innerName,0);
LOGE("內部類名字:%s",innerName_);
env -> ReleaseStringUTFChars(innerName,innerName_); // 記得手動釋放字符串所佔用的內存空間
}
注意:
1)屬性簽名:引用類型的簽名要記得在後面加上“;”,否則識別不到該屬性;
2)記得對獲取到的屬性ID、方法ID等進行判空,有特殊情況沒有獲取到的話應用就崩了。因爲我這裏爲了方便閱讀,暫時沒有加上。
3)這裏只是將獲取到內部類屬性,即 innerName ,進行JNI層的打印,如果能打印出來,證明可以訪問到內部類的屬性。因爲該例子只是爲了驗證能不能訪問到內部類的屬性,所以暫不做其他處理。
3、將JNI層的方法封裝到Java層的native方法
public native void callInnerClassField(Object outerClassObject);
1
4、在Activity中調用
// 測試 Java層嵌套類與C/C++層的嵌套結構體的對應傳參
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.getInnerClass(); // 注意:這裏是通過getInnerClass()方法來獲取內部類對象,而不是通過new來創建
outerClass.setOuterName("I am OuterClass"); // Java層對嵌套類的外部類進行屬性設置
innerClass.setInnerName("I am InnerClass"); // Java層對嵌套類的內部類進行屬性設置
NdkUtil ndkUtil = new NdkUtil(); // 我自己創建用來加載第三方so庫以及存放native方法的工具類
ndkUtil.callInnerClassMethod(outerClass); // 調用native方法
注意:
這裏一定要通過getInnerClass()方法來獲取內部類對象,如果使用new來創建的話,C++層的內部類對象和該對象指向的是不同的內存地址空間,new出來的內部類對象賦值了,但C++層指向的是另外的內部類對象,還沒賦值,獲取到的內部類屬性自然爲空,這也是我這兩天打印屬性(innerName)一直爲空的原因!!!
5、效果驗證
========= Error =========: 外部類名字:I am OuterClass
========= Error =========: 內部類名字:I am InnerClass
如有錯誤,歡迎指正,虛心學習!
————————————————
版權聲明:本文爲CSDN博主「Xiongjiayo」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/Xiongjiayo/article/details/86484115