原本這篇文章想叫JNI使用詳解或者使用全解的,可是想了想,這篇文章的內容應該只算基礎教學。因此改爲這個名字,既成爲了標題黨,也算是客觀。java
這篇文章直接進入正題,所謂的ndk下載工程建立我就很少說了,若是有疑問的能夠參考我以前的一篇文章Android Studio中jni的使用。 在app的build.gradle中:android
defaultConfig {
applicationId "umeng.testjni"
minSdkVersion 15
targetSdkVersion 24
versionCode 1
versionName "1.0"
ndk {
moduleName "JniTest"
ldLibs "log", "z", "m"
abiFilters "armeabi", "armeabi-v7a", "x86"
}
}
複製代碼
其中moduleName是生成的.so文件的名字,若是設置成JniTest,生成的.so文件會是libJniTest.so,ldLibs是依賴的庫。 打開gradle.properties文件,增長配置:c++
android.useDeprecatedNdk = true
複製代碼
local.properties增長ndk的配置路徑:git
ndk.dir=/Users/xxxx/xxxx/sdk/android-ndk-r10e
sdk.dir=/Users/xxxxx/Library/Android/sdk
複製代碼
工程中新建一個接口類JniInterface
:github
public class JniInterface {
static {
System.loadLibrary("JniTest");
}
public static native String sayHello();
}
複製代碼
待會咱們會一個一個的在這個類中添加方法。 其中 System.loadLibrary("JniTest");是加載.so文件,sayHello是c++的方法名字。 這時打開命令行,切到當前應用工程的目錄下(嚴格來講不是工程目錄下,而是java代碼目錄下,即app/src/main/java),輸入以下命令:數組
javah -jni xxxx.com.jnitech.JniInterface
複製代碼
其中 xxxx.com.jnitech.JniInterface是咱們剛剛編寫的文件,這時會在對應的路徑下生成一個 xxxx.com.jnitech.JniInterface.h文件安全
在main目錄下創建jni文件夾,新建main.c實現剛纔生成的頭文件中的方法:bash
#include <jni.h>
/* Header for class umeng_com_jnitech_JniInterface */
#include <stddef.h>
#ifndef _Included_umeng_com_jnitech_JniInterface
#define _Included_umeng_com_jnitech_JniInterface
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: umeng_com_jnitech_JniInterface
* Method: sayHello
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_umeng_com_jnitech_JniInterface_sayHello
(JNIEnv *env, jclass object){
return (*env)->NewStringUTF(env,"hello umeng");
}
#ifdef __cplusplus
}
#endif
#endif
複製代碼
此時運行便可編譯.so文件,在build/intermediates/ndk目錄下能夠找到對應文件。 若是文章就這樣結束了,你們必定以爲很水,因此這僅僅是一個開始。app
上面的例子是一個在Java中調用C中字符串的方法。下面要實現一個Java調用C方法的例子。 找到頭文件umeng_com_jnitech_JniInterface.h
:函數
JNIEXPORT jint JNICALL Java_umeng_com_jnitech_JniInterface_sum
(JNIEnv *, jclass,jint,jint);
複製代碼
添加了這個聲明以後,須要去.c文件中進行實現:
JNIEXPORT jint JNICALL Java_umeng_com_jnitech_JniInterface_sum
(JNIEnv *env, jclass object, jint a, jint b){
return (a+b);
}
複製代碼
這個方法能夠看出是傳入兩個int值,作一個加法,再將值返回的操做。 在Java中也須要聲明一下:
public static native int sum(int a,int b);
複製代碼
而後調用便可:
Toast.makeText(MainActivity.this,"3+4="+JniInterface.sum(3,4),Toast.LENGTH_SHORT).show();
複製代碼
這裏要作一下詳細說明,java中的int對應到C中就是jint,這是一個原始類型的轉化問題,除此還有:
Java類型 | 本地類型(JNI) | 描述 |
---|---|---|
boolean(布爾型) | jboolean | 無符號8個比特 |
byte(字節型) | jbyte | 有符號8個比特 |
char(字符型) | jchar | 無符號16個比特 |
short(短整型) | jshort | 有符號16個比特 |
int(整型) | jint | 有符號32個比特 |
long(長整型) | jlong | 有符號64個比特 |
float(浮點型) | jfloat | 32個比特 |
double(雙精度浮點型) | jdouble | 64個比特 |
void(空型) | void | N/A |
上面的方法是傳入兩個int值,若是是數組如何操做,這裏注意,傳入的類型要是基礎數據類型,要想傳入一個ArrayList確定是不能夠的。因此咱們就用最基礎的String數組。 頭文件增長方法:
JNIEXPORT jstring JNICALL Java_umeng_com_jnitech_JniInterface_testArray
(JNIEnv *, jclass,jobjectArray,jobjectArray);
複製代碼
實現:
JNIEXPORT jstring JNICALL Java_umeng_com_jnitech_JniInterface_testArray
(JNIEnv *env, jclass object, jobjectArray a, jobjectArray b){
jsize count1 = (*env)->GetArrayLength(env,a);
jsize count2 = (*env)->GetArrayLength(env,b);
jstring aa = (jstring) (*env)->GetObjectArrayElement(env,a, count1-1);
jstring bb = (jstring) (*env)->GetObjectArrayElement(env,b, count2-1);
return (*env)->NewStringUTF(env,"數組收到");
}
複製代碼
增長java的接口
public static native String testArray(String[] a,String[] b);
複製代碼
上面提到了如何從Java中調用C的String,若是C須要調用Java的類型該如何處理呢? 如在Java中有以下方法:
public String helloNoStatic(){
return "這個字符串來自java非靜態,穿越c,展現在這裏";
}
複製代碼
C中應該如何調用呢?答案是反射,反射的方法與Java反射的方法相似。 頭文件:
JNIEXPORT jstring JNICALL Java_umeng_com_jnitech_JniInterface_callNonStaticJavaString
(JNIEnv *, jclass);
複製代碼
實現:
JNIEXPORT jstring JNICALL Java_umeng_com_jnitech_JniInterface_callNonStaticJavaString
(JNIEnv *env, jclass object){
jclass dpclazz = (*env)->FindClass(env,"umeng/com/jnitech/JniInterface");
if(dpclazz==0){
return;
}
jmethodID method1 = (*env)->GetMethodID(env,dpclazz,"helloNoStatic","()Ljava/lang/String;");
if(method1==0){
return;
}
jstring result = (jstring)(*env)->CallObjectMethod(env,object,method1);
return result;
}
複製代碼
若是是一個靜態方法:
public static String hello(){
return "這個字符串來自java靜態,穿越c,展現在這裏";
}
複製代碼
該如何實現呢? 與靜態相似,只是C中調用的方法不一樣:
JNIEXPORT jstring JNICALL Java_umeng_com_jnitech_JniInterface_callJavaStringSum
(JNIEnv *env, jclass object,jstring a,jstring b){
jclass dpclazz = (*env)->FindClass(env,"umeng/com/jnitech/JniInterface");
if(dpclazz==0){
return;
}
jmethodID method1 = (*env)->GetStaticMethodID(env,dpclazz,"sumStringCallBackJava","(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
if(method1==0){
return;
}
jstring result = (jstring)(*env)->CallStaticObjectMethod(env,object,method1,a,b);
// return (*env)->NewStringUTF(env,"hello umeng");
return result;
}
複製代碼
看過代碼的同窗,可能會疑問"()Ljava/lang/String;
這是什麼意思,這是域描述符,這裏詳細介紹一下: 括號內爲調用方法的參數,括號後面的是返回值。 域描述符對應以下:
域 | JAVA語言 |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
String | Ljava/lang/String; |
int[ ] | [I |
float[ ] | [F |
String[ ] | [Ljava/lang/String; |
Object[ ] | [Ljava/lang/Object; |
int [ ][ ] | [[I |
float[ ][ ] | [[F |
Java中:
public static int sumCallBackJava(int a,int b){
Log.e("xxxxxx","a+b="+a+b);
return a+b;
}
複製代碼
頭文件:
JNIEXPORT jstring JNICALL Java_umeng_com_jnitech_JniInterface_sumCallBack
(JNIEnv *, jclass,jint,jint);
複製代碼
實現:
JNIEXPORT jint JNICALL Java_umeng_com_jnitech_JniInterface_sumCallBack
(JNIEnv *env, jclass object, jint a, jint b){
jclass dpclazz = (*env)->FindClass(env,"umeng/com/jnitech/JniInterface");
if(dpclazz==0){
return 0;
}
jmethodID method1 = (*env)->GetStaticMethodID(env,dpclazz,"sumCallBackJava","(II)I");
if(method1==0){
return 0;
}
jint result = (*env)->CallStaticIntMethod(env,object,method1,a,b);
// return (*env)->NewStringUTF(env,"hello umeng");
return result;
}
複製代碼
函數 | Java | 本地類型 | 說明 |
---|---|---|---|
GetBooleanArrayElements | jbooleanArray | jboolean | ReleaseBooleanArrayElements 釋放 |
GetByteArrayElements | jbyteArray | jbyte | ReleaseByteArrayElements 釋放 |
GetCharArrayElements | jcharArray | jchar | ReleaseShortArrayElements 釋放 |
GetShortArrayElements | jshortArray | jshort | ReleaseBooleanArrayElements 釋放 |
GetIntArrayElements | jintArray | jint | ReleaseIntArrayElements 釋放 |
GetLongArrayElements | jlongArray | jlong | ReleaseLongArrayElements 釋放 |
GetFloatArrayElements | jfloatArray | jfloat | ReleaseFloatArrayElements 釋放 |
GetDoubleArrayElements | jdoubleArray | jdouble | ReleaseDoubleArrayElement 釋放 |
GetObjectArrayElement | 自定義對象 | object | |
SetObjectArrayElement | 自定義對象 | object | |
GetArrayLength | 獲取數組大小 | ||
NewArray | 建立一個指定長度的原始數據類型的數組 | ||
GetPrimitiveArrayCritical | 獲得指向原始數據類型內容的指針,該方法可能使垃圾回收不能執行,該方法可能返回數組的拷貝,所以必須釋放此資源。 | ||
ReleasePrimitiveArrayCritical | 釋放指向原始數據類型內容的指針,該方法可能使垃圾回收不能執行,該方法可能返回數組的拷貝,所以必須釋放此資源。 | ||
NewStringUTF | jstring類型的方法轉換 | ||
GetStringUTFChars | jstring類型的方法轉換 | ||
DefineClass | 從原始類數據的緩衝區中加載類 | ||
FindClass 該函數用於加載本地定義的類。它將搜索由CLASSPATH 環境變量爲具備指定名稱的類所指定的目錄和 zip文件 | |||
GetObjectClass | 經過對象獲取這個類。該函數比較簡單,惟一注意的是對象不能爲NULL,不然獲取的class確定返回也爲NULL | ||
GetSuperclass | 獲取父類或者說超類 。 若是 clazz 表明類class而非類 object,則該函數返回由 clazz 所指定的類的超類。 若是 clazz指定類 object 或表明某個接口,則該函數返回NULL | ||
IsAssignableFrom | 肯定 clazz1 的對象是否可安全地強制轉換爲clazz2 | ||
Throw | 拋出 java.lang.Throwable 對象 | ||
ThrowNew | 利用指定類的消息(由 message 指定)構造異常對象並拋出該異常 | ||
ExceptionOccurred | 肯定是否某個異常正被拋出。在平臺相關代碼調用 ExceptionClear() 或 Java 代碼處理該異常前,異常將始終保持拋出狀態 | ||
ExceptionDescribe | 將異常及堆棧的回溯輸出到系統錯誤報告信道(例如 stderr)。該例程可便利調試操做 | ||
ExceptionClear | 清除當前拋出的任何異常。若是當前無異常,則此例程不產生任何效果 | ||
FatalError | 拋出致命錯誤而且不但願虛擬機進行修復。該函數無返回值 | ||
NewGlobalRef | 建立 obj 參數所引用對象的新全局引用。obj 參數既能夠是全局引用,也能夠是局部引用。全局引用經過調用DeleteGlobalRef() 來顯式撤消。 | ||
DeleteGlobalRef | 刪除 globalRef 所指向的全局引用 | ||
DeleteLocalRef | 刪除 localRef所指向的局部引用 | ||
AllocObject | 分配新 Java 對象而不調用該對象的任何構造函數。返回該對象的引用。clazz 參數務必不要引用數組類。 | ||
getObjectClass | 返回對象的類 | ||
IsSameObject | 測試兩個引用是否引用同一 Java 對象 | ||
NewString | 利用 Unicode 字符數組構造新的 java.lang.String 對象 | ||
GetStringLength | 返回 Java 字符串的長度(Unicode 字符數) | ||
GetStringChars | 返回指向字符串的 Unicode 字符數組的指針。該指針在調用 ReleaseStringchars() 前一直有效 | ||
ReleaseStringChars | 通知虛擬機平臺相關代碼無需再訪問 chars。參數chars 是一個指針,可經過 GetStringChars() 從 string 得到 | ||
NewStringUTF | 利用 UTF-8 字符數組構造新 java.lang.String 對象 | ||
GetStringUTFLength | 以字節爲單位返回字符串的 UTF-8 長度 | ||
GetStringUTFChars | 返回指向字符串的 UTF-8 字符數組的指針。該數組在被ReleaseStringUTFChars() 釋放前將一直有效 | ||
ReleaseStringUTFChars | 通知虛擬機平臺相關代碼無需再訪問 utf。utf 參數是一個指針,可利用 GetStringUTFChars() 得到 | ||
NewObjectArray | 構造新的數組,它將保存類 elementClass 中的對象。全部元素初始值均設爲 initialElement | ||
SetArrayRegion | 將基本類型數組的某一區域從緩衝區中複製回來的一組函數 | ||
GetFieldID | 返回類的實例(非靜態)域的屬性 ID。該域由其名稱及簽名指定。訪問器函數的GetField 及 SetField系列使用域 ID 檢索對象域。GetFieldID() 不能用於獲取數組的長度域。應使用GetArrayLength()。 | ||
GetField | 該訪問器例程系列返回對象的實例(非靜態)域的值。要訪問的域由經過調用GetFieldID() 而獲得的域 ID 指定。 | ||
SetField | 該訪問器例程系列設置對象的實例(非靜態)屬性的值。要訪問的屬性由經過調用SetFieldID() 而獲得的屬性 ID指定。 | ||
GetStaticFieldID GetStaticField SetStaticField | 同上,只不過是靜態屬性。 | ||
GetMethodID | 返回類或接口實例(非靜態)方法的方法 ID。方法可在某個 clazz 的超類中定義,也可從 clazz 繼承。該方法由其名稱和簽名決定。 GetMethodID() 可以使未初始化的類初始化。要得到構造函數的方法 ID,應將 做爲方法名,同時將void (V) 做爲返回類型。 | ||
CallVoidMethod | 同上 | ||
CallObjectMethod | 同上 | ||
CallBooleanMethod | 同上 | ||
CallByteMethod | 同上 | ||
CallCharMethod | 同上 | ||
CallShortMethod | 同上 | ||
CallIntMethod | 同上 | ||
CallLongMethod | 同上 | ||
CallFloatMethod | 同上 | ||
CallDoubleMethod | 同上 | ||
GetStaticMethodID | 調用靜態方法 | ||
CallMethod | 同上 | ||
RegisterNatives | 向 clazz 參數指定的類註冊本地方法。methods 參數將指定 JNINativeMethod 結構的數組,其中包含本地方法的名稱、簽名和函數指針。nMethods 參數將指定數組中的本地方法數。 | ||
UnregisterNatives | 取消註冊類的本地方法。類將返回到連接或註冊了本地方法函數前的狀態。該函數不該在常規平臺相關代碼中使用。相反,它能夠爲某些程序提供一種從新加載和從新連接本地庫的途徑。 |
JNI中使用的幾種常見方法暫時介紹到這裏,有什麼問題,也歡迎你們給我留言。 demo地址