JNI是Java Native Interface的縮寫,意思是Java的本地接口,這個本地接口主要指Java能夠經過本地接口去和其餘的編程語言通訊,有時在開發某個功能時想使用以前的技術積累或封裝好的模塊,但不幸的是以前不是用Java開發的,那對於此中狀況該如何處理呢?對於通過時間驗證的可靠程序不可能輕易重寫和修改,因此就須要JNI做爲程序的中轉樞紐;java
既然Jni是Java和其餘語言的溝通橋樑,那麼它既必須有一套基礎協議做爲與Java代碼溝通的基礎,這個基礎就是類型的映射和簽名,類型映射就是在Java類型和Jni中的類型創建一一對應關係,從而實現兩者的類型可讀性和惟一性,簽名指Java中類型、方法、屬性等在Java中的展示形式,根據最終的簽名查找方法的對應關係;android
public static native String action(short s , int i, long l, float f, double d, char c, boolean z, byte b, String str, Object obj, ArrayList<String> array, int[] arr, Action action);
//生成的Jni對應的代碼
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_action (JNIEnv *, jclass, jshort, jint, jlong, jfloat, jdouble, jchar, jboolean, jbyte, jstring, jobject, jobject, jintArray, jobject);
複製代碼
//native
public native void test();
//jni
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_test (JNIEnv *, jobject);
複製代碼
上面是Java代碼中聲明的test()轉換後的Jni方法,此方法名稱在編譯javah文件時生成,在實現的C文件中重寫並實現便可,方法的命名規則:編程
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_setTest (JNIEnv *env, jobject cls, jstring j_str) {
}
方法簽名:(Ljava/lang/Object,Ljava/lang/String)Lava/lang/String
複製代碼
靜態註冊JNI方法很簡單,咱們在Java中聲明native方法後,會執行Java命令編譯和生成Jni方法:數組
javac ***
javah ***
複製代碼
在執行javah的命令後,系統會在之間文件處建立.h文件,當咱們在Java層調用native方法時,系統就會根據JNI方法命名規則,按照JNI方法名尋找對應的方法,在找到JNI方法後保存JNI指針創建方法間的聯繫,下次調用時直接使用函數指針就能夠,不過靜態註冊在初次調用時須要查找再創建關聯,影響效率,與靜態註冊對應的就是動態註冊,不須要編譯和查找關聯;緩存
JNIEXPORT jstring JNICALL native_method(JNIEnv *env, jobject) {
return env->NewStringUTF("Register method in Jni");
};
複製代碼
static JNINativeMethod methods[] = {
//參數:一、Java聲明的native方法名;二、方法簽名;三、c中實現的方法名
{"method", "()Ljava/lang/String;", (void *) native_method},
};
複製代碼
// 聲明動態註冊對應的Java類的路徑
static const char *const PACKAGE_NAME = "com/alex/kotlin/jni/JniTest";
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
JNIEnv *env; //獲取JNIEnv
if (JNI_OK != vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6)) {
return JNI_ERR;
}
jclass jclass1 = env->FindClass(PACKAGE_NAME); //根據類名獲取jclass
if (jclass1 == NULL) {
return JNI_ERR;
}
jclassGlobal = static_cast<jclass>(env->NewWeakGlobalRef(jclass1)); //建立全局緩存jclass
env->DeleteLocalRef(jclass1); //釋放局部變量
if (JNI_OK != env->RegisterNatives(jclassGlobal, method, 1)) { //註冊方法
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
複製代碼
在建立的C++文件中重寫Jni.h中的JNI_OnLoad()方法,在JNI_OnLoad中首先根據報名獲取Java類的jclass對象,而後全局緩存jclass對象,最後調用RegisterNatives()方法傳入jclass和關係數組實現方法的註冊編程語言
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return;
}
env->UnregisterNatives(jclassGlobal); //解除註冊
env->DeleteGlobalRef(jclassGlobal); //釋放全局變量
}
複製代碼
在介紹完JNI基礎知識後,一塊兒來學習下JNI在開發中的基本使用,也就是Jni的基本語法,其實在上面動態註冊時已經使用到了一些,這裏根據使用頻率介紹下最經常使用的方法;ide
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_setTest (JNIEnv *env, jobject cls, jstring j_str) {
const char *c_str = NULL;
char buff[128] = {};
jboolean copy;
c_str = env->GetStringUTFChars(j_str, ©); // 字符串訪問
if(c_str == NULL){
return NULL;
}
sprintf(buff, "Jni %s", c_str); //字符串輸出
env->ReleaseStringUTFChars(j_str, c_str); //字符串釋放
return env->NewStringUTF(buff); // 字符串建立
}
複製代碼
env->NewStringUTF(「this is string !");
複製代碼
const jchar *c_str = NULL;
c_str = env->GetStringChars(j_str, ©);
if (c_str == NULL) {
return NULL;
}
sprintf(buff,"Jni %s",c_str); //將字符串緩存到buff中
env->ReleaseStringChars(j_str, c_str);
複製代碼
jsize lenUtf = env->GetStringLength(j_str);
複製代碼
jsize lenUtf = env->GetStringUTFLength(j_str);
複製代碼
const jchar *c_str = NULL;
char buff[128] = {};
jboolean copy;
c_str = env->GetStringCritical(j_str, ©); //讀取字符串
if (c_str == NULL) {
return NULL;
}
sprintf(buff,"Jni %s",c_str);
env->ReleaseStringCritical(j_str, c_str); //釋放字符串
return env->NewStringUTF(buff);
複製代碼
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_setTest(JNIEnv *env, jobject cls, jstring j_str) {
char buff[128] = {};
jsize lenUtf = env->GetStringUTFLength(j_str); //讀取字符串長度
env->GetStringUTFRegion(j_str, 0, 4, buff); //截取字符串0~4緩存到buff中
return env->NewStringUTF(buff); //建立字符串
}
//輸入 "From Native !」 ,輸出結果:「From」
複製代碼
Get<PrimitiveType>ArrayElements(ArrayType array, jboolean *isCopy)
複製代碼
* 0:將 elems 內容回寫到 Java 數組並釋放 elems 佔用的空間,回寫的意思是會去修改原Java中的數組
* JNI_COMMIT:將 elems 內容回寫到 Java 數組,但不釋放 elems 的空間;
* JNI_ABORT:不回寫 elems 內容到 Java 數組,釋放 elems 的空間。
複製代碼
jint *array ;
jboolean jboolean1;
array = env->GetIntArrayElements(jintArray1, &jboolean1); //獲取集合
if (array == NULL){
return;
}
array[2] = 5; //修改集合中的參數
env->ReleaseIntArrayElements(jintArray1,array,0); //釋放array集合並寫入Java集合
//在Java中調用Jni
int[] number = new int[]{1,2,3,4,5};
Log.e("Before Jni=====",number[2] + "");
test.getAction(number); //調用Jni方法
Log.e("After Jni=====",number[2] + "");
2019-04-29 22:09:07.698 15989-15989/com.alex.kotlin.jni E/Before Jni=====: 3
2019-04-29 22:09:07.698 15989-15989/com.alex.kotlin.jni E/After Jni=====: 5 //3改變程5
//使用JNI_ABORT
2019-04-29 22:11:55.485 16108-16108/com.alex.kotlin.jni E/Before Jni=====: 3
2019-04-29 22:11:55.485 16108-16108/com.alex.kotlin.jni E/After Jni=====: 3 //不變
複製代碼
由上面的運行結果知道,在ReleaseIntArrayElements()傳入0時,Java層傳入的數組在執行方法後被改變,從而驗證了將數據回寫到Java的結論,在使用JNI_ABORT時,Java中數據並未發生改變;函數
jbyte* data = env->GetByteArrayElements(javaArray, NULL);
if (data != NULL) {
memcpy(buffer, data, len);
env->ReleaseByteArrayElements(javaArray, data, JNI_ABORT);
}
複製代碼
env->GetByteArrayRegion(javaArray, 0, len, buffer);
複製代碼
public class Action {
public static void actionStatic(){
Log.e("=======","Static method from action in Java ") ;
}
}
//Jni方法
JNIEXPORT void JNICALL Java_com_alex_kotlin_jni_JniTest_test
(JNIEnv *env, jobject) {
jclass cls = NULL;
jmethodID method_Id = NULL;
cls = env->FindClass("com/alex/kotlin/jni/Action」); //查找Action類文件
method_Id = env->GetStaticMethodID(cls, "actionStatic", "()V」); //根據類、方法名、參數條件獲取MethodId
env->CallStaticVoidMethod(cls,method_Id); //調用靜態方法
env->DeleteGlobalRef(cls); //釋放class
}
2019-04-27 17:25:27.580 10961-10961/com.alex.kotlin.jni E/=======: Static method from action in Java
複製代碼
public class Action {
public void action(){
Log.e("=======","Method from action in Java ") ;
}
}
//Jni中代碼
jclass clss = NULL;
jmethodID method_Id = NULL;
jmethodID construct_Id = NULL;
jobject obj = NULL;
clss = env->FindClass("com/alex/kotlin/jni/Action」); //查找Action類
construct_Id = env->GetMethodID(clss, "<init>", "()V」); //獲取構造函數方法的Id
obj = env->NewObject(clss, construct_Id); //建立構造Action實例
method_Id = env->GetMethodID(clss, "action", "()V」); //建立action()方法的Id
env->CallVoidMethod(obj,method_Id); //調用action()方法
2019-04-27 17:42:31.774 11880-11880/com.alex.kotlin.jni E/=======: Method from action in Java
複製代碼
public class Action {
private static int number = 100;
public int getNumber() {
return number;
}
public void setNumber(int number) {
Action.number = number;
}
}
複製代碼
public native void getStaticInstance();
複製代碼
cls = env->FindClass("com/alex/kotlin/jni/Action」); //獲取功能類
field_id = env->GetStaticFieldID(cls, "number", "I」); //獲取靜態變量Id
number = env->GetStaticIntField(cls, field_id); //從類中獲取靜態變量
jint num = 555;
env->SetStaticIntField(cls, field_id, num); //爲靜態變量賦值
複製代碼
Action action = new Action();
action.setMessage("Message in Java");
action.setNumber(123);
Log.e("Before======", action.getNumber() + "");
test.getStaticInstance();
Log.e("After Jni======", action.getNumber() + "");
2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/Before======: 123
2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/After Jni======: 555
複製代碼
public class Action {
private String message = null;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
複製代碼
public native void getInstance(Action action);
複製代碼
cls = env->GetObjectClass(obj); //從參數object中獲取class
field_id = env->GetFieldID(cls, "message", "Ljava/lang/String;」); //根據參數名和類型獲取Field_Id
str = static_cast<jstring>(env->GetObjectField(obj, field_id)); //根據field_Id從obj中獲取實例
new_str = env->NewStringUTF("Message in Jni」); //建立新的String
env->SetObjectField(obj, field_id, new_str); //設置Obj中的數值
複製代碼
Log.e("Before======", action.getMessage() + "");
test.getInstance(action);
Log.e("After Jni======", action.getMessage() + "");
2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/Before======: Message in Java
2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/After Jni======: Message in Jni
複製代碼
public class Action {
public void action(){
Log.e("==========","Action in Action");
}
}
public class ChildAction extends Action {
@Override
public void action() {
Log.e("==========","Action in ChildAction");
}
}
cls = env->FindClass("com.alex.kotlin.jni.Action」); //一、
jmethodID1 = env->GetMethodID(cls, "<init>", "()V」); //二、
jobject1 = env->NewObject(cls, jmethodID1); //三、
cls_parent = env->FindClass("com.alex.kotlin.jni.ChildAction」); //四、
jmethodID2 = env->GetMethodID(cls_parent, "action()", "()V」); //五、
env->CallNonvirtualVoidMethod(jobject1, cls_parent, jmethodID2); //六、
複製代碼
static {
System.loadLibrary("AccessCache");
initIDs();
}
複製代碼
//Java 拋出異常代碼
public void actionException() throws Exception {
throw new Exception("Java 拋出異常");
}
//C文件中調用
jmethodID1 = env->GetMethodID(cls, "actionException", "()V");
env->CallVoidMethod(obj, jmethodID1);
if (env->ExceptionCheck()) { //檢查並拋出異常
env->ExceptionDescribe();
env->ExceptionClear();
env->ThrowNew(env->FindClass("java/lang/Exception"), "Jni 拋出異常");
}
//運行結果
2019-04-29 19:12:54.919 32350-32350/com.alex.kotlin.jni W/System.err: java.lang.Exception: Java 拋出異常
2019-04-29 19:12:54.920 32350-32350/com.alex.kotlin.jni E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.alex.kotlin.jni, PID: 32350
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.alex.kotlin.jni/com.alex.kotlin.jni.MainActivity}: java.lang.Exception: Jni 拋出異常
複製代碼
env->CallVoidMethod(obj, jmethodID1);
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
env->ExceptionClear();
env->ThrowNew(env->FindClass("java/lang/Exception"), "Jni 拋出異常");
}
複製代碼
if ((*env)->ExceptionCheck(env)) { /* 異常檢查 */
(*env)->ReleaseStringChars(env, jstr, cstr); // 發生異常後釋放前面所分配的內存
return;
}
複製代碼
對於咱們正常開發來講,直接使用Jni的場景不多,通常Jni的方法都會封裝在So庫中供Java層調用,如今就根據上面的Jni知識利用AS生成一個So庫。工具
ndk.dir=/Users/wuliangliang/Library/Android/sdk/ndk-bundle
複製代碼
cmake_minimum_required(VERSION 3.4.1) :配置So庫的最小版本
add_library( //每一個C文件具有一個add_library
jni-lib // 配置So庫名稱
SHARED //設置So庫設置爲Shared共享庫
test.cpp) // 源C文件的相對路徑
find_library( //,
log-lib
log)
target_link_libraries(
jni-lib // 指定目標庫
${log-lib}) // 將目標庫鏈接到NDK的日誌庫
複製代碼
上面配置中:學習
android{
externalNativeBuild {
cmake {
path "src/main/jni/CMakeLists.txt」 //配置CMakeList的文件路徑
}
}
sourceSets { main { jni.srcDirs = ['src/main/jni', 'src/main/jni/'] } } //配置Jni文件輸出路徑
}
複製代碼
public class JniTest {
static {
System.loadLibrary("jni-lib」); //引入so庫
}
public static native String test(); //配置native方法
}
複製代碼
JNIEXPORT void JNICALL Java_com_alex_kotlin_jni_JniTest_test (JNIEnv *env, jobject) {
jclass cls = NULL;
jmethodID method_Id = NULL;
cls = env->FindClass("com/alex/kotlin/jni/Action");
if (cls == NULL) {
return;
}
method_Id = env->GetStaticMethodID(cls, "test", "()V");
env->CallStaticVoidMethod(cls, method_Id);
env->DeleteGlobalRef(cls);
}
複製代碼
這裏調用FindClass()根據類名獲取Class對象,而後使用全局變量保存Class對象,而後查找並調用actionStatic()
JniTest test = new JniTest();
tv.setText(test.test());
複製代碼
關於Jni的基本知識點和用法基本介紹完了,在通常的開發中可能使用的很少,但想作進一步的功能或優化時就常常會使用到,因此Jni也成爲Android高級開發這必備基礎知識,但願此篇文章的總結對須要的同窗有所幫助;