Android JNI與Android NDK掃盲

引言

什么是JNI和?Android NDK

  • JNIJava Native Interface的縮寫,它提供了若干的API實現了Java和其他語言的通信(主要是C&C++).
    就是說我們使用在Java代碼里面去使用其他語言現有的API.JNI不局限與Android平臺.
  • Android NDK是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Google稱為NDK
    它是Google公司給我們提供的一套開發工具,就是為了使Android程序能夠執行C&C++等部分原生代碼.

使用JNI有什么用

  1. 代碼的保護,由于APK的Java層代碼很容易被反編譯,而C/C++庫反匯難度較大。
  2. 在NDK中調用第三方C/C++庫,因為大部分的開源庫都是用C/C++代碼編寫的。
  3. 便于移植,用C/C++寫的庫可以方便在其他的嵌入式平臺上再次使用。
  4. 因為Android應用程序是跑在虛擬機上面,所以有些底層的硬件調用需要使用更底層的語言去調用.

環境

本機的環境

  • 操作系統: OSX 10.11.5
  • Java版本: build 1.8.0_51-b16
  • NDK版本: r11c
  • IDE: Eclipse Mars.1 Release (4.5.1)
  • Android工程:
  • minSDKVersion : 19
  •  targetSDKVersion    : `19`
    
  •  buildToolsVersion : `19`
    

具體的自己去下載,這里給一下NDK的下載地址
PS:自備梯子

配置NDK的執行路徑

解壓NDK,路徑信息不要出現中文

配置環境變量(OSX為例)

我的NDK目錄路徑為/Users/August/android-ndk-r11c,下面自行修改NDK目錄

  1. 編輯profile文件: 使用自己熟悉的編輯器打開~/.profile文件
  2. ~/.profile添加: export PATH="/Users/August/android-ndk-r11c:$PATH"
  3. 保存~/.profile文件.
  4. 使配置文件生效: source ~/.profile

如果是Windows系統的話,跟上面雷同,不過是直接修改環境變量,而不是去修改profile文件.
Windows用戶可以這里

配置Eclipse環境

這里假設你已經開始在Eclipse上面做過?Android開發了

  1. 選擇Eclipse的設置,設置里搜索NDK,選擇下面的NDK然后在右邊選項卡設置NDK的路徑(/Users/August/android-ndk-r11c)
  2. 然后我們右鍵項目,選擇Android Tools->Add native support->輸入C/C++的源文件名(這里是用test)
  3. 然后我們發現工程里面多了一個jni的文件夾,我們打開后發現新建了test.cpp,和Android.mk.我們右鍵test.cpp->rename->改成test.c,并且把Android.mk里面的test.cpp改成test.c.
  4. 你有沒有發現,你的jni文件中#include<jni.h>Unresolved inclusion: <jni.h>的錯誤了?沒事,右鍵項目->Properties->C/C++ General->Paths and Symbols->Add->File System->選擇/Users/August/android-ndk-r11c/platforms/android-19/arch-arm/usr/include.因為這里使用的Android工程師API19,所以這里我們對應選擇android-19的arm平臺

Android.mk

LOCAL_PATH := $(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_MODULE    := test
    LOCAL_SRC_FILES :=  test.c

    include $(BUILD_SHARED_LIBRARY)

其中test就是模塊名,test.c就是需要編譯的源文件

Demo1:Hello world

native方法聲明

我們在MainActivity中聲明方法public native String getHelloFromC();,這里getHelloFromC()主要實現我們會在C語言里面實現.

方法調用

很簡單,getHelloFromC()就能夠得到函數返回的值.你可以打個Log或者彈個吐司.

使用javah

我們可以使用javah 包名.類名去生成native函數的C語言聲明

編寫函數

  1. 命令后環境切換到項目的src目錄下面
  2. 輸入javah com.example.jniproject.MainActivity
  3. 回到Eclipse中,右鍵src文件夾,選擇refresh操作
  4. 我們看到多了一個文件,找到對應的方法聲明JNIEXPORT jstring JNICALL Java_com_example_jniproject_MainActivity_getHelloFromC (JNIEnv *, jobject);后,我們拷貝聲明代碼到test.c
  5. 最后把test.c的代碼修改成
#include <jni.h>
jstring Java_com_example_jniproject_MainActivity_getHelloFromC(JNIEnv *env,
       jobject obj) {
   char* cstr = "Hello World!";
   jstring jstr = (*env)->NewStringUTF(env, cstr);
   return jstr;
}

修改的4->5修改的內容:

  • 去掉JNIEXPORT,JNICALL.
  • 增加JNIEnv *參數和jobjectenvobj參數名
    env變量: Java虛擬機環境
    obj: 調用該代碼的主體

變量類型

上面我們可以看到jstring這種類型.是什么鬼?
通過源文件查看(Windows下按住ctrl鍵鼠標左鍵點擊jstring)我們可以看到.無論是JNIEnv還是jstring都是在jni.h里面有兩份定義.第一份是C語言,第二份是C++

  • 公用類型

#ifdef HAVE_INTTYPES_H
# include <inttypes.h>      /* C99 */
typedef uint8_t         jboolean;       /* unsigned 8 bits */
typedef int8_t          jbyte;          /* signed 8 bits */
typedef uint16_t        jchar;          /* unsigned 16 bits */
typedef int16_t         jshort;         /* signed 16 bits */
typedef int32_t         jint;           /* signed 32 bits */
typedef int64_t         jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */
#else
typedef unsigned char   jboolean;       /* unsigned 8 bits */
typedef signed char     jbyte;          /* signed 8 bits */
typedef unsigned short  jchar;          /* unsigned 16 bits */
typedef short           jshort;         /* signed 16 bits */
typedef int             jint;           /* signed 32 bits */
typedef long long       jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */
#endif

  • C的類型
typedef void*           jobject;
typedef jobject         jclass;
typedef jobject         jstring;
typedef jobject         jarray;
typedef jarray          jobjectArray;
typedef jarray          jbooleanArray;
typedef jarray          jbyteArray;
typedef jarray          jcharArray;
typedef jarray          jshortArray;
typedef jarray          jintArray;
typedef jarray          jlongArray;
typedef jarray          jfloatArray;
typedef jarray          jdoubleArray;
typedef jobject         jthrowable;
typedef jobject         jweak;
  • C++的類型
typedef _jobject*       jobject;
typedef _jclass*        jclass;
typedef _jstring*       jstring;
typedef _jarray*        jarray;
typedef _jobjectArray*  jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray*    jbyteArray;
typedef _jcharArray*    jcharArray;
typedef _jshortArray*   jshortArray;
typedef _jintArray*     jintArray;
typedef _jlongArray*    jlongArray;
typedef _jfloatArray*   jfloatArray;
typedef _jdoubleArray*  jdoubleArray;
typedef _jthrowable*    jthrowable;
typedef _jobject*       jweak;

通過變量類型名稱的字面意思,我們也可以跟Java里面的類型對應上.
需要注意的是,例如Java中傳遞String類型參數,在C語言里面需要把String轉換成char[]等類型才能操作.
同時我們也看下NewStringUTF這個函數

  • C++版本
jstring NewStringUTF(const char* bytes) {
    return functions->NewStringUTF(this, bytes);
}
  • C版本
jstring (*NewStringUTF)(JNIEnv*, const char*);

this實參我們不難看出,C++使用了面向對象的方法.而C語言的僅僅是結構體包裝.所以當我們使用的時候對env這個變量使用也不一樣.在C版本中是(*env)->xxx,在C++版本中是env->xxx.

更多JNI函數用法參考<<The Java(TM) Native Interface–Programmer’s Guide and Specification>>中的JNI FUnctions章節.網上有電子版

加載模塊

雖然現在模塊是還沒編譯出來,但是我們運行程序的時候.NDK會自動幫我們編譯.我們先寫入加載的代碼:

public class MainActivity extends Activity {
    static {
        System.loadLibrary("test");
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toast.makeText(this, getHelloFromC(), Toast.LENGTH_SHORT).show();
    }
    
    public native String getHelloFromC();
    
}

System.loadLibrary的參數是模塊的名字,關于名字有兩個常用的方法識別:

  • 自己的模塊:直接查看Android.mkLOCAL_MODULE參數
  • 別人的模塊:例如libtest.so,把lib.so去掉.剩下的test就是模塊名稱了

走起

直接運行程序,自動編譯模塊

Demo2:字符串傳參

我們來實現一個移位的加解密
具體步驟上面都有,所以就簡要說一下步驟.

定義函數

```java
public native String encode(String str, int length);

public native String decode(String str, int length);
```

生成native函數版本的函數聲明

```
javah com.example.jniproject.MainActivity
```

修改函數聲明并添加到test.c

```c
jstring Java_com_example_jniproject_MainActivity_encode(JNIEnv *env,
        jobject obj, jstring orgin, jint length) {
    jstring encrypt;

    return encrypt;
}

jstring Java_com_example_jniproject_MainActivity_decode(JNIEnv * env,
        jobject obj, jstring encrypt, jint length) {
    jstring orgin;
    return orgin;
}
```

編寫test.c具體代碼

jstring Java_com_example_jniproject_MainActivity_encode(JNIEnv *env,
        jobject obj, jstring orgin, jint length) {

    jstring encrypt;

    char *cstr = (*env)->GetStringUTFChars(env, orgin, 0);

    int i;

    for (i = 0; i < length; i++) {
        cstr[i] += 1;
    }

    encrypt = (*env)->NewStringUTF(env, cstr);

    return encrypt;
}

jstring Java_com_example_jniproject_MainActivity_decode(JNIEnv * env,
        jobject obj, jstring encrypt, jint length) {
    jstring orgin;
    char *cstr = (*env)->GetStringUTFChars(env, encrypt, 0);

    int i;

    for (i = 0; i < length; i++) {
        cstr[i] -= 1;
    }

    orgin = (*env)->NewStringUTF(env, cstr);
    return orgin;
}

從上面例子(模板)可以看到,需要操作類型的時候,傳參返回值都要轉換成對應的語言的類型.
這篇博客大概了解一下什么是JNI,其實我也是剛在學習.就mark一下吧.希望大家多多交流.

配置Eclipse的javah

其實我們也可以去配置Eclipse的啟動配置,不用手動切換目錄去生成C/C++的文件聲明

第一步

導航菜單->Run->External Tools->External Tools Configurations->Program->選項卡左上角加號->出現新配置項

第二步

說一下幾個需要填寫的東西

  • Name: 啟動配置名稱
  • Location : 啟動項的外部文件路徑
  • Working Directory : 外部文件的工作目錄
  • Arguments : 外部文件運行的參數

下面是我的參考配置,除了Location外,其他可以直接拷貝

  • Name: javah
  • Location : /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/javah
  • Working Directory : ${project_loc}/src
  • Arguments : -d ${project_loc}/jni ${java_type_name}

apply上面配置后切換到Refresh選項卡,然后選中The project containing the selected resourceapply一次就ok了

第三步

假設現在我們要為MainActity生成對應的C/C++聲明文件.

  1. 選中或者打開MainActity.java,一定要保證當前鼠標焦點在需要生成的Java文件中
  2. Run->External Tools->javah,直接就看到jni目錄下面生成了頭文件
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容