JNI開發基礎篇:C語言呼叫Java中的方法
Life always has many things to bring you down.But what can really bring you down is just yourself.
這一篇來記錄如何在C中實現java方法的呼叫(最基本的原理:JAVA reflect 反射)
在JNI 中,java呼叫C的流程步驟:(個人的理解整理,如有誤,請包容也請指正)
/**
java ---> C
java--->System.loadLibrary()---->native method---->C method
*/
C呼叫Java
/**
C ----> Java
java--->System.loadLibrary()---->native method ----> c method ----> java method
*/
所以,C語言呼叫java的時候,從android呼叫角度來講:
1:建立Java native method.
2:建立相應的JNI c method
3:java 中呼叫 native method,然後native method 呼叫C method
4:C呼叫java方法
接下來,我完成一個例項。要求是:
1:建立一個帶有整型引數的native方法,並建立C將要呼叫的java方法
2:然後呼叫C方法完成硬體中wife的關閉的操作或者啟動
3:最後C中完成硬體啟動後
4:最後呼叫java方法來輸出成功還是失敗。
解釋:在C語言中不像java中那樣有Boolean和String型別,所以在自己建立方法的時候,也要符合C中的基本語法。(這個需要去另外學習的部分了。也是想要成為終端開發者的必經之路了。也會慢慢的靠近人工智慧的發展方向。純屬個人理解,努力探索永不止步!)
OK,Let’s beginning
1:建立一個帶有整型引數的native方法,並建立C將要呼叫的java方法
/*對於wifi的開啟或關閉的button*/
public native String WifiButton();
/*C將呼叫的java方法*/
public void returnTypeWife(int type){
Log.i("com.wedfrend.jni.JNI", "returnTypeWife: "+type);
if(type == 0){
Log.i(TAG, "returnTypeWife: 開啟");
}
Log.i(TAG, "returnTypeWife: 關閉");
}
2: 完成C中操作硬體功能,然後呼叫Java方法,最後輸出提示等
/**
C中方法宣告,這個在前面一篇說過
*@param env jni 提供萬能的二級指標
*@param instance 可以理解為我們java中的上下文引數
*/
JNIEXPORT void JNICALL
Java_ltd_xiamenwelivetechnologyco_myapplication_JNI_WifiButton(JNIEnv * env, jobject instance) {
/**
1:現在首先完成C語言中處理硬體的方法
由於具體操控硬體的相關程式碼複雜,而我目前並沒有完全掌握。
所以這設定一個int 值,0 表示wifi開啟 ,1表示wifi關閉。
(後續的進階篇會詳細的來聊聊硬體)
*/
int type = 0;//表示開啟wifi
/*2:C語言中呼叫Java中的方法,那麼具體的實現原理為Java的反射機制*/
/*
2.1:獲得想要呼叫Java方法的類
jclass (*FindClass)(JNIEnv*, const char*);
@param JNIEnv
@param const char* 呼叫的具體類的路徑 這裡需要將java中的 "."改為"/",表示具體路徑下的檔案
*/
jclass jClass = (*env)->FindClass(env,"com/wedfrend/jni/JNI");
/*
2.2:2:例項化該類
jobject (*AllocObject)(JNIEnv*, jclass);
@param jclass 2.1得到的jclass
*/
jobject jObject = (*env)->AllocObject(env,jClass);
/*
2.3:得到你將要呼叫java類中的方法
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
@param const char* 第一個為呼叫java的方法名。
比如說要呼叫java中的returnTypeWife方法
@param const char* 第二個表示該方法的函式簽名,對於這個函式簽名。
穿插說明:函式簽名。如何獲得一個java函式的函式簽名。
1:將自己的專案rebuild
2:成功後,在專案中 `app/build/intermediates/classes/debug`目錄下生成相應的class檔案
3:使用cmd命令進入相應的檔案目錄下
4:執行命令 javap -s 全類名。如下是我的命令
F:\"編譯專案成功之後的檔案路徑"\debug> javap -s com.wedfrend.jni.JNI
5:會顯示這樣的結果
Compiled from "JNI.java"
public class com.wedfrend.jni.JNI {
public com.wedfrend.jni.JNI();
descriptor: ()V //這個便是簽名
public native java.lang.String WifiButton();
descriptor: ()Ljava/lang/String;
public void returnTypeWife(int);
descriptor: (I)I
}
那麼我的呼叫那個方法,用那個的簽名,比如我們現在要用的是
public int returnTypeWife(int);
descriptor: (I)I
那麼簽名自然是 (I)I
*/
jmethodID jMethodId = (*env)->GetMethodID(env,jClass,"returnTypeWife","(I)I");
/**
2.4:方法呼叫
void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
相應的引數在上面已經得到,那麼最後的可變引數為我們方法中需要傳入的引數
*/
(**env).CallIntMethod(env,jObject,jMethodId,type);
//OK,It's done!
LOGD("C called the java method !");
下面編譯執行,看看結果:
點選按鈕後可以獲取相應的輸出:
補充內容
1:C語言呼叫java中的static方法
以上便是C呼叫java中的方法。那麼C中對java中的靜態方法的呼叫,相比上面簡單一點:
// TODO
//1:獲得想要呼叫Java方法的類
// jclass (*FindClass)(JNIEnv*, const char*);
jclass jClass = (*env)->FindClass(env,"xx/xxx/JNI");
//3:獲取我們想呼叫的方法 GetStaticMethodID
//jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID jMethodId = (*env)->GetStaticMethodID(env,jClass,"AlarmClock","()V");
// 呼叫靜態的方法 CallStaticVoidMethod
//void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);
(*env)->CallStaticVoidMethod(env,jClass,jMethodId);
只是將相應的方法改為呼叫靜態方法即可。
2:C語言處理後呼叫java方法改變UI介面
我們一直沒有使用生成方法中的一個引數:jobject instance
改變UI介面的時候,我們是需要在Activity中執行操作,那麼相應的方法,是需要放在具體的Activity中。
所以在需要修改的UI的Activity中,需要定義native方法,並定義C要掉用的方法。
之後在C中進行呼叫:
//1:獲得想要呼叫Java方法的類
// jclass (*FindClass)(JNIEnv*, const char*);
jclass jClass = (*env)->FindClass(env,"com/wedfrend/jni/MainActivity");
//3:獲取我們想呼叫的方法
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID jMethodId = (*env)->GetMethodID(env,jClass,"setUi","()V");
//4: 方法呼叫
/*
這裡的jobject我們直接使用方法中的帶有引數 instance 便可
*/
// jboolean (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
(**env).CallVoidMethod(env,instance,jMethodId);
問題總結:
Q1:在普通呼叫的時候,是否也可以使用方法中的自帶引數 jobject,從而省略掉在C中例項化類。
A:可以這樣使用。
Q2:需要通過C語言呼叫java方法的時候,需要改變UI狀態,我的呼叫類寫在Activity中,但是native方法與其他的類寫在同一個檔案。
A:目前我自記得試驗是不可行的,如果有人成功,請指教。
Q3:在C中獲取MethodId的時候,對於方法簽名,rebuild中是debug,那麼在正式簽名的時候,兩者之間的簽名結果是一樣的嗎?
A:1:在build.gradle中,我們設定一個release版本的keystore.android。並配置release版本,如下:
2:確認是否debug和release中的簽名祕鑰不同,執行如圖所示的步驟
3:檢視你的祕鑰,如圖:
4:所以目前的debug與release的keystore不同,MD5加密也不同,所以現在執行如下:
此時 專案中會生成兩個編譯版本,一個debug,一個release,如圖:
然後我們按照檢視簽名的方式,在相應目錄下執行`javap -s 全類名`
如圖為Debug下的JNI.class
下圖為Release下的JNI.class
所以可以看到,對於編譯版本的keystore改變的情況下,對class中檔案的方法簽名沒有影響。
以上的問題也是我在實踐中跌跌撞撞,整理和實踐,希望對大家有用。目前的基礎入門篇告一段落。
在後面的延伸階段中,會直接結合複雜的邏輯進行使用。由於工作的原因,工作處於FrameWork層,所以深入在後續的學習中持續更新。敬請期待