Android Studio下使用NDK的流程
我要重新拿回持之以恒徽章!!
老規矩,先說看能學會什么:ANDROID STUDIO下NDK的使用方法。JNI的基本使用方法,C語言調用JAVA的方法。
首先要下載NDK,如果你沒有V*N可以來http://www.androiddevtools.cn/進行下載。下載后解壓到任意目錄。
其次,新建一個安卓項目。在MainActivity里添加一個Native方法。
public native void showDialog();
這里不以HelloWorld舉例了。來使用Java來調用C語言的方法,然后C語言的方法里再次調用java的方法來show一個dialog。這里在MainActivity寫show()方法
public void show(String message){ AlertDialog.Builder bulider = new AlertDialog.Builder(this); bulider.setTitle("title"); bulider.setMessage(message); bulider.show(); }
寫完了之后 點擊Builde里面的 MakeProject
,
然后打開終端 進入app/src/main/java目錄,鍵入以下命令來編譯頭文件
cd app/src/main/java
javah -d ../jni com.wingsofts.jniii.MainActivity
之后再android studio下可以看到jni文件夾以及頭文件,這個頭文件的命名是 包名+類名。
然后我們再來配置我們的gradle,編輯app目錄下的build.gradle文件,在defaultConfig函數內加入以下函數。
ndk {
moduleName "JniTest"
abiFilters "armeabi", "armeabi-v7a", "x86"
}
然后再編輯local.properties。加入你的NDK路徑(即剛解壓的ndk)
sdk.dir=/Users/wing/android-sdk
ndk.dir=/Users/wing/android-ndk-r10e
此時,ndk的配置以及完成。我們將要用C語言實現函數的內容。
在jni文件夾下新建一個C文件。這里我起名叫做Hello。
具體怎么實現呢 首先看一下我們編譯好的頭文件是什么內容
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_wingsofts_jniii_MainActivity */ #ifndef _Included_com_wingsofts_jniii_MainActivity #define _Included_com_wingsofts_jniii_MainActivity #ifdef __cplusplus extern "C" { #endif /* * Class: com_wingsofts_jniii_MainActivity * Method: showDialog * Signature: ()V */ JNIEXPORT void JNICALL Java_com_wingsofts_jniii_MainActivity_showDialog (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
看到其中有一個函數 返回值為void 名稱叫做 Java_com_wingsofts_jniii_MainActivity_showDialog,哇塞,這個函數名有點長。仔細觀察,發現函數名是由 包名加類名加函數名命名的 ,這也就解釋了java和c方法是怎么對應的。然后看他的參數。(JNIEnv *,jobject),這是啥。。怎么看不懂呢。。 沒關系,我們再來看看他include的一個頭,jni.h
這里不全部貼出來了。只進行摘錄
typedef const struct JNINativeInterface* JNIEnv; typedef const struct JNIInvokeInterface* JavaVM;找到了以上語句。 原來這個JNIEnv 是一個JNINativieterface的指針,也就是說是一個環境。再來看
typedef void* jobject; typedef jobject jclass; typedef jobject jstring; typedef jobject jarray; typedef jarray jobjectArray; typedef jarray jbooleanArray; typedef jarray jbyteArray; typedef jarray jcharArray; typedef jarray jshortArray; typedef jarray jintArray; typedef jarray jlongArray; typedef jarray jfloatArray; typedef jarray jdoubleArray; typedef jobject jthrowable; typedef jobject jweak;
因為java的數據類型和C不一樣,所以現在要用別名實現。這里是對應表。 可以看到這個函數第二個參數其實是jobject 也就是一個函數指針。
現在大概了解了。快來實現我的c函數吧。
#include "com_wingsofts_jniii_MainActivity.h" JNIEXPORT void JNICALL Java_com_wingsofts_jniii_MainActivity_showDialog(JNIEnv* env,jobject jobj){ jclass clazz = (*env)->FindClass(env,"com/wingsofts/jniii/MainActivity"); jmethodID method = (*env)->GetMethodID(env,clazz,"show","(Ljava/lang/String;)V"); (*env)->CallVoidMethod(env,jobj,method,(*env)->NewStringUTF(env,"c調用java")); }這里首先導入編譯好的頭文件。
然后實現方法名和頭文件里的一樣。 這個函數的目的是:調用java的方法,這里選擇show一個dialog。所以需要用c語言來調用showDialog方法。于是要用到反射。跟java內的反射一樣,先需要獲取class然后放到類加載器中 在獲得方法,才可以調用。
通過jni.h看到 jclass 就是java的 class jmethodID就是java的Method 。
1.獲取class
首先使用(*env)->的 findClass方法 傳入環境變量 env 第二個參數是要尋找的類名 以路徑的形式寫。
2.獲取method
獲取到clazz之后 在獲取其中的方法 仍然是使用(*env)->的GetMethodID方法。傳入環境env Class clazz 然后是實例的方法名"show",最后一個是方法的簽名。看起來樣子很奇怪。其實也不奇怪,首先()里面的是參數的類型,因為java里傳入的是一個String 所以這里是(Ljava/lang/String;) 后面跟著是返回值,因為是VOID所以這里是V。如果你不確定你寫的方法對不對呢。還可以使用javap -s來查看方法的簽名。
3.調用方法
這里因為返回值為void所以使用CallVoidMethod 如果是返回obj就是CallObjectMethod, 這些都可以從jni.h看到。前三個參數不做解釋了,大家都明白。這里解釋一下最后一個參數。這個參數就是show(String message)的參數。一看是String的花 如果直接寫"c調用Java"是錯誤的。因為Java的字符串和C的字符串不是同一個類型。所以要調用NewStringUTF來轉換一下。
好了這時我們的C語言文件遍實現好了。
快來運行一下試試。得到效果圖
看起來也許不是很炫酷。但是想想這時由java調用C 在用C調用java來實現的。是不是立馬變得高大上了?