JNI開發學習之C反射調用java方法
前面記錄了調用C的學習筆記,現在來記錄一下C反射調用Java的筆記。
Android開發中調用一個類中沒有公開的方法,可以進行反射調用,而JNI開發中C調用java的方法也是反射調用。
C代碼回調Java方法步驟:
①獲取字節碼對象(jclass ( FindClass)(JNIEnv , const char*);)
②通過字節碼對象找到方法對象(jmethodID ( GetMethodID)(JNIEnv , jclass, const char , const char );)
③通過字節碼文件創建一個object對象(該方法可選,方法中已經傳遞一個object,如果需要調用的方法與本地方法不在同一個文件夾則需要新創建object(jobject ( AllocObject)(JNIEnv , jclass);),如果需要反射調用的java方法與本地方法不在同一個類中,需要創建該方法,但是如果是這樣,并且需要跟新UI操作,例如打印一個Toast 會報空指針異常,因為這時候調用的方法只是一個方法,沒有actiivty的生命周期。(下面有解決方案))
④通過對象調用方法,可以調用空參數方法,也可以調用有參數方法,并且將參數通過調用的方法傳入(void ( CallVoidMethod)(JNIEnv , jobject, jmethodID, ...);)
首先,也是按照前面的步驟新建一個 import C++ 工程,新建ccalljava.c 和一個JNI.java文件(別忘了修改CMakeLists.txt對應C方法的名字和路徑)
project.png
JNI.java中編寫本地方法:
//C調用java空方法
public native void callbackmethod();
//C調用java中的帶兩個int參數的方法
public native void callbackIntmethod();
//C調用java中參數為string的方法
public native void callbackStringmethod();
//C調用java中靜態方法
public native void callStaticmethod();
并且編寫被C反調的java方法:
//C調用java空方法
public void helloFromJava(){
Toast.makeText(context, "C調用了java的空方法",Toast.LENGTH_SHORT ).show();}
//C調用java中的帶兩個int參數的方法
public int add(int x,int y) {
return x+y;}
//C調用java中參數為string的方法
public void printString(String s){
Toast.makeText(context, s, Toast.LENGTH_SHORT).show();}
//C調用java中靜態方法
public static void staticmethod(String s){
Log.w("毛麒添",s+",我是被C調用的靜態方法");}
下面來編寫ccalljava.c中的C方法
/**C函數反射調用java中的空方法 */
JNIEXPORT void JNICALLJava_com_mao_ccalljava_JNI_callbackmethod(JNIEnv *env, jobject object) {
jclass jclazz = (*env)->FindClass(env, "com/mao/ccalljava/JNI");
jmethodID methodID = (*env)->GetMethodID(env, jclazz, "helloFromJava", "()V");
(*env)->CallVoidMethod(env,object,methodID);}
/**
調用java中Int方法
*/
JNIEXPORT void JNICALLJava_com_mao_ccalljava_JNI_callbackIntmethod(JNIEnv *env, jobject object) {
jclass clzz=(*env)->FindClass(env,"com/mao/ccalljava/JNI");
jmethodID methodID=(*env)->GetMethodID(env,clzz,"add","(II)I");
int result=(*env)->CallIntMethod(env,object,methodID,3,4);
//logcat 打印相加返回的結果
LOGD("RESLUT = %d",result);
}
/**
調用java中String方法
*/
JNIEXPORT void JNICALLJava_com_mao_ccalljava_JNI_callbackStringmethod(JNIEnv *env, jobject object) {
//先獲取字節碼對象 jclass (*FindClass)(JNIEnv*, const char*);
jclass clzz=(*env)->FindClass(env,"com/mao/ccalljava/JNI");
//獲取method對象 jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID methodID=(*env)->GetMethodID(env,clzz,"printString","(Ljava/lang/String;)V");
//將要傳遞的字符串先轉換成jstring類型 ,然后在傳遞給java方法 int result=(*env)->NewStringUTF(env,"hello form C/C++ "); (*env)->CallVoidMethod(env,object,methodID,result);
}
/**
調用Java中的靜態方
*/
JNIEXPORT void JNICALLJava_com_mao_ccalljava_JNI_callStaticmethod(JNIEnv *env, jobject instance) {
jclass clzz=(*env)->FindClass(env,"com/mao/ccalljava/JNI");
jmethodID methodID=(*env)->GetStaticMethodID(env,clzz,"staticmethod","(Ljava/lang/String;)V");
jstring str = (*env)->NewStringUTF(env, "C調用java");
(*env)->CallStaticVoidMethod(env,clzz,methodID,str);
}
通過字節碼對象找到方法對象,該方法中的第四個參數是方法簽名
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
獲取方法簽名的方法是進入工程目的 ..../build/classes/debug 進入控制臺,
輸入命令 javap -s 要獲取方法的路徑(例如本例 javap -s com.mao.ccalljava.JNI)
project—獲取方法簽名目錄.png
方法簽名.png
上面步驟二中提到的沒有生命周期的解決方法:
報空指針,主要就是沒上下文環境,反射調用的方法是new出來的,也會沒有生命周期.這時候就可以將本地方法和調用的方法都放在同一個類中,沒有上下文環境就在創建方法的時候在構造方法中接收一個。
private Context context;
public JNI(Context context){
this.context=context;
}
最后,別忘了添加在JNI.java中添加動態鏈接庫文件(布局和MianActiivty中邏輯比較簡單,這里就不貼了)
static {
System.loadLibrary("ccalljava");
}
在gradle 配置一些處理器架構
externalNativeBuild {
cmake {
cppFlags ""
// Clang是一個C語言、Objective-C、C++語言的輕量級編譯器。
arguments "-DANDROID_TOOLCHAIN=clang"
// 生成.so庫的目標平臺
abiFilters "armeabi-v7a" , "armeabi" ,"x86"
}
}
接下來在工程編譯通過后可以該目錄下找到不同處理器架構的動態鏈接庫文件
不同架構動態鏈接庫文件.png
最后最后,上幾張運行成功的截圖:
反調java String方法.png
c調java空方法.png
調用int和靜態方法在logcat中打印.png
如果有錯我沒發現的大家可以幫我指出來,大家一起進步。
來自:http://www.jianshu.com/p/4893848a3249