JNI入門這篇文章就夠了(含demo)

原本這篇文章想叫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

調用C的方法

上面的例子是一個在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);
複製代碼

C調用Java中的String類型

上面提到了如何從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

C中調用Java相加的方法

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;
}
複製代碼

JNI函數操做查詢

函數 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地址

相關文章
相關標籤/搜索