這是這個系列的第二篇,第一篇介紹瞭如何配置。這一篇介紹Java與C如何相互介紹。java
沒有配置過的能夠去看看Android JNI開發系列之配置android
首先介紹的就是Java如何調用C,而C調用Java核心使用的就是反射,下面會依次介紹。git
第一篇中有個簡單的例子,就是使用Java調用C,調用一個無參的native函數,並返回一個String,下面接着說點更多的狀況:github
由於Java和C的基本類型也有些許區別,而在這二者之間還有一個jni的類型做爲橋樑鏈接轉換類型,有一張圖特別好,一看就清楚了,借了一下這位做者文章中的圖,表示感謝。數組
下邊對於數據的處理就是基於這些類型去處理的。bash
這個也是坑了我這個萌新很多,體會到其實Java的垃圾回收機制仍是很方便的。函數
其中在c中字符串的拼接主要就是使用strcat
方法,導入#include<string.h>
包。post
仍是老樣子,先定義一個native方法,對於配置都是在上一篇的基礎上的:學習
public class Hello {
static {
System.loadLibrary("Hello");
}
//傳入一個字符串,拼接一段字符串後返回
public native String sayHello(String msg);
}
複製代碼
接着在Hello.c文件中寫這個方法,這裏有兩種方法去寫這個方法,第一種是手動本身寫,也有點技巧:ui
JNIEnv *env, jobject instance
,而後第三個參數開始就是在Java中定義的方法的參數,這裏傳入了一個String,在這裏的就改成jstring msg
,方法以下:jstring Java_net_arvin_androidstudy_jni_Hello_sayHello(JNIEnv *env, jobject instance,
jstring msg) {
// implement code...
}
複製代碼
還有一種方法就是使用javah命令,處理.java文件就能獲得定義的.h文件;方法就是在該項目的java目錄下,使用命令javah 類的徹底限定名
,在我這個項目裏就是: javah net.arvin.androidstudy.jni.Hello
這樣在java目錄下就有一個net_arvin_androidstudy_jni_Hello.h
文件,打開能夠看到這個方法:
JNIEXPORT jstring JNICALL Java_net_arvin_androidstudy_jni_Hello_sayHello
(JNIEnv *, jobject, jstring);
複製代碼
其中JNIEXPORT和JNICALL關鍵字均可以去掉的,去掉後就和上邊的方法同樣了,而後本身去把參數的名字補充上便可。
最後對於字符串的拼接,沒啥好說的,我這裏提供一種方式:
jstring Java_net_arvin_androidstudy_jni_Hello_sayHello(JNIEnv *env, jobject instance,
jstring msg) {
char *fromJava = (char *) (*env)->GetStringUTFChars(env, msg, JNI_FALSE);
char *fromC = " add I am from C~";
char *result = (char *) malloc(strlen(fromJava) + strlen(fromC) + 1);
strcpy(result, fromJava);
strcat(result, fromC);
return (*env)->NewStringUTF(env, result);
}
複製代碼
最後就是去調用,這就簡單了。
Hello jni = new Hello();
String result = jni.sayHello("I am from Java");
Log.d(TAG, result);
複製代碼
有了上文的介紹,這個比較就比較簡單,核心就是使用strcmp
方法,Java代碼以下:
public class Hello {
static {
System.loadLibrary("Hello");
}
//若是是c中要求的就返回200,不然就返回400
public native int checkStr(String str);
}
複製代碼
c代碼以下:
jint Java_net_arvin_androidstudy_jni_Hello_checkStr
(JNIEnv *env, jobject instance, jstring jstr) {
char *input = (char *) (*env)->GetStringUTFChars(env, jstr, JNI_FALSE);
char *real = "123456";
return strcmp(input, real) == 0 ? 200 : 400;
}
複製代碼
這裏就不接着介紹其餘的處理方法了,須要時能夠本身搜一下。
一樣有了上文的基礎,Java代碼以下:
public class Hello {
static {
System.loadLibrary("Hello");
}
public native void increaseArray(int[] arr);
}
複製代碼
C代碼以下:
void Java_net_arvin_androidstudy_jni_Hello_increaseArray
(JNIEnv *env, jobject instance, jintArray arr) {
jsize length = (*env)->GetArrayLength(env, arr);
jint *elements = (*env)->GetIntArrayElements(env, arr, JNI_FALSE);
for (int i = 0; i < length; i++) {
elements[i] += 10;
}
(*env)->ReleaseIntArrayElements(env, arr, elements, 0);
}
複製代碼
能夠看到:
到這裏Java調用C的介紹就到這裏,方法基本介紹了,可是如何更好的運用還需努力實踐。
上文中說到這個操做,主要是利用反射,這樣就能調用Java代碼了。
對於配置都不說了,也直接上代碼,主要的細節都是在反射那裏。
先來一個C調用Java無參無返回值的函數,Java代碼以下:
public class CallJava {
static {
System.loadLibrary("Hello");
}
private static final String TAG = "CallJava";
//調用無參,無返回函數
public native void callVoid();
public void hello() {
Log.d(TAG, "Java的hello方法");
}
}
複製代碼
能夠看到這裏換了一個類了,可是沒有影響,以後會介紹這一塊知識。
C代碼:
//調用public void hello()方法
void Java_net_arvin_androidstudy_jni_CallJava_callVoid
(JNIEnv *env, jobject instance) {
jclass clazz = (*env)->FindClass(env, "net/arvin/androidstudy/jni/CallJava");
jmethodID method = (*env)->GetMethodID(env, clazz, "hello", "()V");
jobject object = (*env)->AllocObject(env, clazz);
(*env)->CallVoidMethod(env, object, method);
}
複製代碼
這個就是四部曲:
第一步:使用FindClass
方法,第二個參數,就是要調用的函數的類的徹底限定名,可是須要把點換成/
第二步:使用GetMethodID
方法,第二個參數就是剛獲得的類的class,第三個就是方法名,第四個就是該函數的簽名,這裏有個技巧,使用javap -s 類的徹底限定名
就能獲得該函數的簽名,可是須要在build->intermediates->classes->debug目錄下,使用該命令,獲得以下結果:
//else method...
public void hello();
descriptor: ()V
複製代碼
descriptor:後邊的就是該方法的簽名
第三步:使用AllocObject
方法,使用clazz建立該class的實例。
第四步:使用CallVoidMethod
方法,能夠看到這個就是調用返回爲void的方法,第二個參數就是第三步中建立的實例,第三個參數就是上邊建立的要調用的方法。
有了這個四部就能在C中吊起Java中的代碼了。
而對於有參,有返回的方法,在這四部曲的基礎上,只須要修改第二步獲取方法的名字和簽名,其中籤名以及第四步的CallMethod方法,Type能夠是int,string,boolean,float等等。
提示:對於基本類型又個技巧,括號內依次是參數的類型的縮寫,括號右邊是返回類型的縮寫,用得多了就能夠不用每次都去使用命令查詢了,可是開始最好仍是都查一下,省得出錯
可是對於靜態方法的調用就應該使用GetStaticMethodID
和CallStaticVoidMethod
了,而對於靜態方法就不須要實例化對象,相對來講還少一步。
到這裏,可能有使用過java的反射的同窗有疑問了,若是是去調用private的方法,會不會報錯呢,這個能夠告訴你,我試過了,也是能夠調用起來的,沒有問題,不用擔憂啦。
到這裏,Java調用C,C調用Java基本就算是完成了,這個代碼我也會上傳到github上,須要的同窗能夠自行下載比對,有不足之處也請多多指教。地址在文末。
前文中說了,對於多文件的配置會在以後的文章中說到,果真,在第二篇中,想着方法太多了,我想放到別的文件中去處理,避免混亂了,因此就去了解了一下,在此告訴你們,其實很簡答。
首先,在以前的配置基礎上,再在cpp目錄下建立一個文件,例如這裏叫作Test.c,而後再到CMakeLists.txt文件中關聯上就好了,關聯方式以下:
cmake_minimum_required(VERSION 3.4.1)
add_library(Hello
SHARED
src/main/cpp/Hello.c
src/main/cpp/Test.c)
複製代碼
對比以前的配置,對了一行src/main/cpp/Test.c
至關於把Test.c文件也關聯到叫作Hello的這個lib中。
雖然如今c代碼也能夠調試debug了,可是仍是有打印日誌才方便,printf
是沒有用的,因此須要咱們手動去添加一個日誌庫,首先在CMakeLists.txt中添加成以下:
cmake_minimum_required(VERSION 3.4.1)
add_library(Hello
SHARED
src/main/cpp/Hello.c
src/main/cpp/Test.c)
find_library(log-lib log)
target_link_libraries(Hello ${log-lib})
複製代碼
多了後兩句代碼。而後再須要用到的地方申明:
#include "android/log.h"
#define LOG_TAG "JNI_TEST"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
複製代碼
這樣就能在這個類中使用了:
這裏就有個技巧了,定義一個Log.c文件,導入上文中的配置,而後在須要用日誌的地方引入Log.c便可。
這樣就不用在每一個文件開頭都去申明這些東西了。
在這個項目中,java代碼在包下的jni下,配置也可在相應位置查看。
部分代碼來源尚硅谷Android視頻《JNI》