引言
什么是JNI和?Android NDK
JNI
是Java 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有什么用
- 代碼的保護(hù),由于APK的Java層代碼很容易被
反編譯
虎眨,而C/C++庫(kù)反匯難度較大技潘。 - 在NDK中調(diào)用第三方C/C++庫(kù)襟齿,因?yàn)榇蟛糠值?code>開(kāi)源庫(kù)都是用C/C++代碼編寫的铸本。
- 便于
移植
喇潘,用C/C++寫的庫(kù)可以方便在其他的嵌入式平臺(tái)上再次使用体斩。 - 因?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目錄
- 編輯
profile
文件: 使用自己熟悉的編輯器打開(kāi)~/.profile
文件 - 在
~/.profile
添加:export PATH="/Users/August/android-ndk-r11c:$PATH"
- 保存
~/.profile
文件. - 使配置文件生效:
source ~/.profile
如果是
Windows系統(tǒng)
的話,跟上面雷同,不過(guò)是直接修改環(huán)境變量,而不是去修改profile
文件.
Windows用戶可以
戳這里
配置Eclipse環(huán)境
這里假設(shè)你已經(jīng)開(kāi)始在Eclipse上面做過(guò)?Android開(kāi)發(fā)了
- 選擇Eclipse的設(shè)置,設(shè)置里搜索
NDK
,選擇下面的NDK
然后在右邊選項(xiàng)卡設(shè)置NDK的路徑(/Users/August/android-ndk-r11c
) - 然后我們右鍵項(xiàng)目,選擇
Android Tools
->Add native support
->輸入C/C++的源文件名(這里是用test)
- 然后我們發(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
. - 你有沒(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ù)
-
命令后環(huán)境
切換到項(xiàng)目的src目錄
下面 - 輸入
javah com.example.jniproject.MainActivity
- 回到Eclipse中,右鍵
src
文件夾,選擇refresh
操作 - 我們看到多了一個(gè)文件,找到對(duì)應(yīng)的方法聲明
JNIEXPORT jstring JNICALL Java_com_example_jniproject_MainActivity_getHelloFromC (JNIEnv *, jobject);
后,我們拷貝聲明代碼到test.c
- 最后把
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ù)和jobject
的env
和obj
參數(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.mk
的LOCAL_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 resource
再apply
一次就ok了
第三步
假設(shè)現(xiàn)在我們要為
MainActity
生成對(duì)應(yīng)的C/C++
聲明文件.
- 選中或者打開(kāi)
MainActity.java
,一定要保證當(dāng)前鼠標(biāo)焦點(diǎn)在需要生成的Java文件中
-
Run
->External Tools
->javah
,直接就看到jni
目錄下面生成了頭文件