Android JNI&NDK編程小結及建議


前言

由於網上關於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方法流程如下:

  1. 在Java中定義一個靜態方法供JNI調用,注意要是靜態的。
  2. 在JNI中利用env來調用Java中定義的靜態方法
  3. 調用聲明好的靜態方法

可能流程說的比較抽象,用代碼簡單說明一下:

  1. 定義靜態方法:

    //對應包名:com.mrljdx.jni.HelloJNI public static void helloJava() { System.out.println("Hello JavaCode"); } 
  2. 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); } 
  3. 調用聲明好的靜態方法:

    static_helloJava(env); 

在AndroidStudio中NDK編程配置注意事項:

  1. 在項目的gradle.properties中添加ndk支持:

    android.useDeprecatedNdk=true 
  2. 配置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" } } 
  3. 在AndroidStudio中寫JNI代碼有一個比較爽的地方,就是Android.mk系統會在編譯時自動幫你生成,你只需要配置build.gradle就行了。注意jni相關代碼需要放在src/main/jni目錄下。如果對gradle配置不了解可以參考我的博客:Gradle實戰及學習建議

小結

在我們做產品的時候,應該考慮該用JNI&NDK的時候就用,一切出發點是基於用戶的體驗和數據安全,我覺得在以下幾種情況下建議使用NDK:

  1. 重用現有的代碼,比如C/C++的代碼在Android中的重用。
  2. 數據安全,比如將Http的請求加密和解密算法放在NDK中去實現,這樣可以提高應用的安全。
  3. 提升性能,由於Android設備制造商在手機中給每個應用分配了可用的最大RAM,有時候為了性能考慮,可以通過Native代碼向系統來“借”一些內存,盡量少的使用系統分配給應用的內存。(參考Infoq:Android內存優化)

參考文章及書籍

END


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM