前言
由於網上關於JNI/NDK相關的知識點介紹的比較零散而且不具備參照性,所以寫了這篇JNI/NDK筆記,便於作為隨時查閱的工具類型的文章,本文主要的介紹了在平時項目中常用的命令、JNI數據類型、簽名等,便於查閱相關資料。文末相關參考資料比較適合剛接觸或者不熟悉Android NDK開發的朋友參閱。
常用命令
javac 編譯java源文件生成.class文件
由於JNI對應的頭文件由javah工具根據對應的.class文件生成,所以在進行JNI編程之前,寫好Java代碼后需要先編譯,在使用javah生成對應的頭文件
javah -jni自動生成頭文件
舉例說明:
-
生成普通的JNI頭文件
javah -classpath path -jni -d outputdirpath com.mrljdx.JavaNativeCode
-
在Java函數中包含Android相關的參數代碼,則需要在classpath中添加android.jar包的絕對路徑地址
javah -classpath path:$ANDROID_HOME/path/android.jar -jni -d outputdirpath com.mrljdx.JavaNativeCodeWithAndroid
javap -s -p 查看函數簽名
-s: 顯示簽名(只顯示public類型的簽名) -p:顯示所有函數、成員變量的簽名
舉例說明:
javap -classpath pacakage_path_dir -s -p com.mrljdx.JavaCode
JNI數據類型和類型簽名
數據類型
JNI的數據類型包括:基本類型和引用類型。這一點和Java的語言特性一致,基本類型包括jboolean、jchar、jint、jlong、jbyte、jshort、jfloat、jdouble、void,與Java類型的對應關系如下:
JNI類型 | Java類型 | 描述 |
---|---|---|
jboolean | boolean | 無符號8位整型 |
jbyte | byte | 有符號8位整型 |
jchar | char | 無符號16位整型 |
jshort | short | 有符號16位整型 |
jint | int | 32位整型 |
jlong | long | 64位整型 |
jfloat | float | 32位整型 |
jdouble | double | 64位整型 |
void | void | 無類型 |
JNI中引用類型主要有類、對象和數組,這點也上符合Java的語法規范,對應的關系如下:
JNI 類型 | Java引用類型 | 描述 |
---|---|---|
jobject | Object | Object類型 |
jclass | Class | Class類型 |
jstring | String | String類型 |
jobjectArray | Object[] | 對象數組 |
jbooleanArray | boolean[] | boolean數組 |
jbyteArray | byte[] | byte數組 |
jcharArray | char[] | char數組 |
jshortArray | short[] | short數組 |
jintArray | int[] | int數組 |
jlongArray | long[] | long數組 |
jfloatArray | float[] | float數組 |
jdoubleArray | double[] | double數組 |
jthrowable | Throwable | Throwable |
JNI類型簽名
JNI的類型簽名標識了一個特定的Java類型,這個類型可以是類和方法,也可以是數據類型。
- 類型簽名
類的簽名采用”L+包名+類名+;”標識,包名中將.
替換為/
即可。
比如String類的簽名:Ljava/lang/String;
注意末尾的;
屬於簽名的一部分。
再比如Android中Context類的簽名:Landroid/content/Context;
- 基本數據類型簽名
基本數據類型的簽名采用一系列大寫字母來標識,如下:
Java類型 | 簽名 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
void | V |
可以發現除了 long
基本數據類型的簽名為J
之外其他的都比較容易辨識,估計是由於之前的類類型的簽名開頭為L+包名+類名+;
設計者為了區分所以簽名為J
- 數組的類型簽名
數組的類型簽名比起類類型和基本數據類型的要稍微復雜一點,不過還是很好理解的。對於數組來說,它的簽名為[+類型簽名
,舉例說明:
String[] 數組類型對應的簽名:[Ljava/lang/String;
可以發現,就是在String的類簽名前加了個[
同理基本數據類型簽名int[]的簽名:[I
注意這里基本類型后面是不帶分號的。
那么多維數組呢?可以類推,int[][] 的簽名為[[I
,而String[][]的簽名為[[Ljava/lang/String;
- 方法的簽名
在JNI中會經常需要在C/C++代碼中調用Java的函數,這時候就會用到方法的簽名。方法的簽名為(+參數類型簽名+)+返回值類型簽名
,比如:
方法:boolean login(String username,String password)
的方法簽名如下:(Ljava/lang/String;Ljava/lang/String)B
如果這里不理解的話,請再去看看之前關於基本類型,類類型的簽名部分內容。
小技巧:使用 類似於
javap -classpath pathdir -s -p com.sample.JavaCode
的 javap -s -p 命令也可以幫助查看一些類中各種方法和成員變量的簽名。
JNI相關命名解釋
- 函數名的格式遵循規則:
Java_包名_類名_方法名
- JNIEXPORT、JNICALL、JNIEnv和jobject 都是JNI標准中所定義的類型或者宏
- JNIEnv * : 指向JNI環境的指針,可以通過JNIEnv * 訪問JNI提供的接口方法
- JNIEXPORT、JNICALL:是jni.h中所定義的宏。
注:JNIEnv * 可以簡單的理解為Java和C/C++ 之間相互調用的橋梁,我們可以通過JNIEnv * 調用C/C++定義的方法,也可以在C/C++中通過JNIEnv * 來調用Java類中的方法。下面將會講到C/C++中調用Java的方法,注意JNIEnv *的作用。
在C/C++中調用Java方法
首先說明一點,在Android開發過程中使用NDK主要是為了提高代碼的安全性,有些游戲公司可能是為了方便利用已有的C/C++開源庫來進行平台移植,其實在性能提升方面,NDK的作用並不是很明顯。所以有時候一些在Java中實現起來非常簡單的代碼放在JNI里面做會顯得吃力不討好,所以干脆就直接在JNI中調用Java的方法,我們只把加密和驗證的一些邏輯寫到JNI層就行了。
在JNI中調用Java方法流程如下:
- 在Java中定義一個靜態方法供JNI調用,注意要是靜態的。
- 在JNI中利用env來調用Java中定義的靜態方法
- 調用聲明好的靜態方法
可能流程說的比較抽象,用代碼簡單說明一下:
-
定義靜態方法:
//對應包名:com.mrljdx.jni.HelloJNI public static void helloJava() { System.out.println("Hello JavaCode"); }
-
JNI聲明靜態方法:
static void static_helloJava(JNIEnv *env){ jclass clazz = env->FindClass("com/mrljdx/jni/HelloJNI"); jmethodID mid = env->GetStaticMethodID(clazz, "helloJava", "()V"); env->CallStaticVoidMethod(clazz, mid); }
-
調用聲明好的靜態方法:
static_helloJava(env);
在AndroidStudio中NDK編程配置注意事項:
-
在項目的
gradle.properties
中添加ndk支持:android.useDeprecatedNdk=true
-
配置
build.gradle
看代碼注釋:defaultConfig { minSdkVersion 9 targetSdkVersion 23 versionCode 1 versionName "1.0" //配置ndk 支持 ndk { //編譯的so庫名稱 libsecurity.so moduleName "security" //指定編譯后的庫支持的平台 abiFilters "armeabi", "mips", "x86", "armeabi-v7a" //用於指定應用應該使用哪個標准庫,此處添加c++庫支持 stl "stlport_static" } }
-
在AndroidStudio中寫JNI代碼有一個比較爽的地方,就是Android.mk系統會在編譯時自動幫你生成,你只需要配置build.gradle就行了。注意jni相關代碼需要放在
src/main/jni
目錄下。如果對gradle配置不了解可以參考我的博客:Gradle實戰及學習建議
小結
在我們做產品的時候,應該考慮該用JNI&NDK的時候就用,一切出發點是基於用戶的體驗和數據安全,我覺得在以下幾種情況下建議使用NDK:
- 重用現有的代碼,比如C/C++的代碼在Android中的重用。
- 數據安全,比如將Http的請求加密和解密算法放在NDK中去實現,這樣可以提高應用的安全。
- 提升性能,由於Android設備制造商在手機中給每個應用分配了可用的最大RAM,有時候為了性能考慮,可以通過Native代碼向系統來“借”一些內存,盡量少的使用系統分配給應用的內存。(參考Infoq:Android內存優化)
參考文章及書籍
- 《Android性能優化》 : 一本不厚的書,關於Android相關的性能優化建議都是滿滿的干貨,適合有一定開發經驗的人參考。
- 翻譯:JNI Tips in Android Training Course : 關於google官方的培訓課程提出了在使用NDK做本地開發時的一些優化建議。
- JNI/NDK開發指南 : 一個系列的文章,適合剛接觸JNI/NDK開發的朋友學習。
- 《Android開發藝術探索》 :關於JNI的介紹比少,如果你對Android開發想深入理解一些View沖突、屬性動畫那么可以推薦一看。如果只想學習JNI相關內容,推薦看 JNI/NDK開發指南 的內容。