Android JNI與Android NDK掃盲

引言

什么是JNI和?Android NDK

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

使用JNI有什么用

  1. 代碼的保護(hù),由于APK的Java層代碼很容易被反編譯虎眨,而C/C++庫(kù)反匯難度較大技潘。
  2. 在NDK中調(diào)用第三方C/C++庫(kù)襟齿,因?yàn)榇蟛糠值?code>開(kāi)源庫(kù)都是用C/C++代碼編寫的铸本。
  3. 便于移植喇潘,用C/C++寫的庫(kù)可以方便在其他的嵌入式平臺(tái)上再次使用体斩。
  4. 因?yàn)锳ndroid應(yīng)用程序是跑在虛擬機(jī)上面,所以有些底層的硬件調(diào)用需要使用更底層的語(yǔ)言去調(diào)用.

環(huán)境

本機(jī)的環(huán)境

  • 操作系統(tǒng): 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的執(zhí)行路徑

解壓NDK,路徑信息不要出現(xiàn)中文

配置環(huán)境變量(OSX為例)

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

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

如果是Windows系統(tǒng)的話,跟上面雷同,不過(guò)是直接修改環(huán)境變量,而不是去修改profile文件.
Windows用戶可以這里

配置Eclipse環(huán)境

這里假設(shè)你已經(jīng)開(kāi)始在Eclipse上面做過(guò)?Android開(kāi)發(fā)了

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

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方法聲明

我們?cè)?code>MainActivity中聲明方法public native String getHelloFromC();,這里getHelloFromC()主要實(shí)現(xiàn)我們會(huì)在C語(yǔ)言里面實(shí)現(xiàn).

方法調(diào)用

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

使用javah

我們可以使用javah 包名.類名去生成native函數(shù)的C語(yǔ)言聲明

編寫函數(shù)

  1. 命令后環(huán)境切換到項(xiàng)目的src目錄下面
  2. 輸入javah com.example.jniproject.MainActivity
  3. 回到Eclipse中,右鍵src文件夾,選擇refresh操作
  4. 我們看到多了一個(gè)文件,找到對(duì)應(yīng)的方法聲明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修改的內(nèi)容:

  • 去掉JNIEXPORT,JNICALL.
  • 增加JNIEnv *參數(shù)和jobjectenvobj參數(shù)名
    env變量: Java虛擬機(jī)環(huán)境
    obj: 調(diào)用該代碼的主體

變量類型

上面我們可以看到jstring這種類型.是什么鬼?
通過(guò)源文件查看(Windows下按住ctrl鍵鼠標(biāo)左鍵點(diǎn)擊jstring)我們可以看到.無(wú)論是JNIEnv還是jstring都是在jni.h里面有兩份定義.第一份是C語(yǔ)言,第二份是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;

通過(guò)變量類型名稱的字面意思,我們也可以跟Java里面的類型對(duì)應(yīng)上.
需要注意的是,例如Java中傳遞String類型參數(shù),在C語(yǔ)言里面需要把String轉(zhuǎn)換成char[]等類型才能操作.
同時(shí)我們也看下NewStringUTF這個(gè)函數(shù)

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

this實(shí)參我們不難看出,C++使用了面向?qū)ο蟮姆椒?而C語(yǔ)言的僅僅是結(jié)構(gòu)體包裝.所以當(dāng)我們使用的時(shí)候?qū)?code>env這個(gè)變量使用也不一樣.在C版本中是(*env)->xxx,在C++版本中是env->xxx.

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

加載模塊

雖然現(xiàn)在模塊是還沒(méi)編譯出來(lái),但是我們運(yùn)行程序的時(shí)候.NDK會(huì)自動(dòng)幫我們編譯.我們先寫入加載的代碼:

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的參數(shù)是模塊的名字,關(guān)于名字有兩個(gè)常用的方法識(shí)別:

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

走起

直接運(yùn)行程序,自動(dòng)編譯模塊

Demo2:字符串傳參

我們來(lái)實(shí)現(xiàn)一個(gè)移位的加解密
具體步驟上面都有,所以就簡(jiǎn)要說(shuō)一下步驟.

定義函數(shù)

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

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

生成native函數(shù)版本的函數(shù)聲明

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

修改函數(shù)聲明并添加到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;
}

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

配置Eclipse的javah

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

第一步

導(dǎo)航菜單->Run->External Tools->External Tools Configurations->Program->選項(xiàng)卡左上角加號(hào)->出現(xiàn)新配置項(xiàng)

第二步

說(shuō)一下幾個(gè)需要填寫的東西

  • Name: 啟動(dòng)配置名稱
  • Location : 啟動(dòng)項(xiàng)的外部文件路徑
  • Working Directory : 外部文件的工作目錄
  • Arguments : 外部文件運(yùn)行的參數(shù)

下面是我的參考配置,除了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選項(xiàng)卡,然后選中The project containing the selected resourceapply一次就ok了

第三步

假設(shè)現(xiàn)在我們要為MainActity生成對(duì)應(yīng)的C/C++聲明文件.

  1. 選中或者打開(kāi)MainActity.java,一定要保證當(dāng)前鼠標(biāo)焦點(diǎn)在需要生成的Java文件中
  2. Run->External Tools->javah,直接就看到jni目錄下面生成了頭文件
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末颖低,一起剝皮案震驚了整個(gè)濱河市絮吵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌忱屑,老刑警劉巖蹬敲,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異莺戒,居然都是意外死亡伴嗡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門从铲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瘪校,“玉大人,你說(shuō)我怎么就攤上這事名段≡伲” “怎么了赏寇?”我有些...
    開(kāi)封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)价认。 經(jīng)常有香客問(wèn)我嗅定,道長(zhǎng),這世上最難降的妖魔是什么用踩? 我笑而不...
    開(kāi)封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任渠退,我火速辦了婚禮,結(jié)果婚禮上脐彩,老公的妹妹穿的比我還像新娘碎乃。我一直安慰自己,他們只是感情好惠奸,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布梅誓。 她就那樣靜靜地躺著,像睡著了一般佛南。 火紅的嫁衣襯著肌膚如雪梗掰。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天嗅回,我揣著相機(jī)與錄音及穗,去河邊找鬼。 笑死绵载,一個(gè)胖子當(dāng)著我的面吹牛埂陆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播娃豹,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼焚虱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了懂版?” 一聲冷哼從身側(cè)響起著摔,我...
    開(kāi)封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎定续,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體禾锤,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡私股,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了恩掷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倡鲸。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖黄娘,靈堂內(nèi)的尸體忽然破棺而出峭状,到底是詐尸還是另有隱情克滴,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布优床,位于F島的核電站劝赔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏胆敞。R本人自食惡果不足惜着帽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望移层。 院中可真熱鬧仍翰,春花似錦、人聲如沸观话。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)频蛔。三九已至灵迫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間帽驯,已是汗流浹背龟再。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留尼变,地道東北人利凑。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像嫌术,于是被迫代替她去往敵國(guó)和親哀澈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容