在Android開發中,因爲種種緣由咱們須要調用C/C++代碼, 這個時候就要用到Android開發者都據說過的JNI(Java Native Interface)了, 在調用JNI相關方法以前, 要對java中native關鍵字定義的方法進行註冊, 註冊方式有兩種: 靜態註冊和動態註冊, 二者優缺點以下:java
此註冊方法是初學者常常用到的, 比較常見, 這裏簡單說下流程,
1.編寫一個java類,在裏面加載對應的so庫而且經過native關鍵字定義須要調用的函數android
package com.example.wenzhe.myjni; /** * Created by wenzhe on 16-1-27. */ public class JniTest { public native int getRandomNum(); public native String getNativeString(); static { System.loadLibrary("HelloJni"); } }
2.在命令行下輸入 javac JniTest.java 生成JniTest.class文件
而後在src目錄下經過 javah com.example.wenzhe.myjni.JniTest 生成 com_example_wenzhe_myjni_JniTest.h 頭文件
3.將頭文件拷貝到jni目錄下(eclipse在src同級目錄創建文件夾,Android studio 在java同級目錄創建文件夾)
4.編寫C/C++源代碼 並把剛拷貝的頭文件包含進去 ,複製頭文件中函數的定義部分,並實現其中的你想要的功能ios
而後編寫Android.mk Application.mk(Application.mk主要用來定義適應的平臺,x86 arm等)數組
Android.mk以下:架構
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := HelloJni LOCAL_SRC_FILES := HelloJni.cpp include $(BUILD_SHARED_LIBRARY)
Application.mk以下:dom
#支持標準C++特性 APP_STL := gnustl_static APP_CPPFLAGS := -frtti -fexceptions #支持的CPU架構 APP_ABI := armeabi-v7a #Android 版本 APP_PLATFORM := android-22 include $(BUILD_SHARED_LIBRARY)
其中LOCAL_MODULE定義的名字就是生成的so庫名字,so庫前面都會有個lib前綴,上面生產的so應該爲 libHelloJni.soeclipse
5.在命令行中進入jni目錄,輸入ndk-build 便可生產對應so庫,會自動放在libs文件夾下 至此就能夠運行程序了函數
動態註冊基本思想是在JNI_Onload()函數中經過JNI中提供的RegisterNatives()方法來將C/C++方法和java方法對應起來(註冊), 咱們在調用 System.loadLibrary的時候,會在C/C++文件中回調一個名爲 JNI_OnLoad ()的函數,在這個函數中通常是作一些初始化相關操做, 咱們能夠在這個方法裏面註冊函數, 註冊總體流程以下:工具
示例代碼以下:ui
// jni頭文件 #include <jni.h> #include <cassert> #include <cstdlib> #include <iostream> using namespace std; //native 方法實現 jint get_random_num(){ return rand(); } /*須要註冊的函數列表,放在JNINativeMethod 類型的數組中, 之後若是須要增長函數,只需在這裏添加就好了 參數: 1.java中用native關鍵字聲明的函數名 2.簽名(傳進來參數類型和返回值類型的說明) 3.C/C++中對應函數的函數名(地址) */ static JNINativeMethod getMethods[] = { {"getRandomNum","()I",(void*)get_random_num}, }; //此函數經過調用RegisterNatives方法來註冊咱們的函數 static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,int methodsNum){ jclass clazz; //找到聲明native方法的類 clazz = env->FindClass(className); if(clazz == NULL){ return JNI_FALSE; } //註冊函數 參數:java類 所要註冊的函數數組 註冊函數的個數 if(env->RegisterNatives(clazz,getMethods,methodsNum) < 0){ return JNI_FALSE; } return JNI_TRUE; } static int registerNatives(JNIEnv* env){ //指定類的路徑,經過FindClass 方法來找到對應的類 const char* className = "com/example/wenzhe/myjni/JniTest"; return registerNativeMethods(env,className,getMethods, sizeof(getMethods)/ sizeof(getMethods[0])); } //回調函數 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){ JNIEnv* env = NULL; //獲取JNIEnv if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } assert(env != NULL); //註冊函數 registerNatives ->registerNativeMethods ->env->RegisterNatives if(!registerNatives(env)){ return -1; } //返回jni 的版本 return JNI_VERSION_1_6; }
上面的代碼就能實現動態註冊JNI了 之後要增長函數只需在java文件中聲明native方法,在C/C++文件中實現,
並在getMethods數組添加一個元素並指明對應關係,經過ndk-build 生成so庫就能夠運行了
其中JNI版本能夠在jni.h頭文件中去查看支持哪些版本,通常定義在文件最後幾行
動態註冊中 JNINativeMethod 結構體中第二個參數需注意
括號內表明傳入參數的簽名符號,爲空能夠不寫,括號外表明返回參數的簽名符號,爲空填寫 V,對應關係入下表
簽名符號 | C/C++ | java |
---|---|---|
V | void | void |
Z | jboolean | boolean |
I | jint | int |
J | jlong | long |
D | jdouble | double |
F | jfloat | float |
B | jbyte | byte |
C | jchar | char |
S | jshort | short |
[Z | jbooleanArray | boolean[] |
[I | jintArray | int[] |
[J | jlongArray | long[] |
[D | jdoubleArray | double[] |
[F | jfloatArray | float[] |
[B | jbyteArray | byte[] |
[C | jcharArray | char[] |
[S | jshortArray | short[] |
L完整包名加類名; | jobject | class |
舉個例子:
傳入的java參數有兩個 分別是 int 和 long[] 函數返回值爲 String 即函數的定義爲:String getString(int a ,long[] b)
簽名就應該是 :"(I[J)Ljava/lang/String;"(不要漏掉英文分號)
若是有內部類 則用 $ 來分隔 如:Landroid/os/FileUtils$FileStatus;
當熟悉動態註冊後, 動態註冊無疑是註冊函數的更好方式, 惟一要注意的是註冊函數時, 須要額外當心, 別把類名,函數名和簽名寫錯了, 否則loadLibraries時會致使應用Crash, 關於JNI部分知識, 我還會寫一篇關於如何高效傳遞數據以及JNI開發過程當中的一些坑的總結.
做者:smewise 連接:https://www.jianshu.com/p/1d6ec5068d05 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。