JNI,是Java Native Interface的縮寫碗降,中文為Java本地調(diào)用脖苏。通俗地說,JNI是一種技術(shù)滤祖,通過這種技術(shù)可以做到以下兩點: · Java程序中的函數(shù)可以調(diào)用Native語言寫的函數(shù)筷狼,Native一般指的是C/C++編寫的函數(shù)。 · Native程序中的函數(shù)可以調(diào)用Java層的函數(shù)匠童,也就是在C/C++程序中可以調(diào)用Java的函數(shù)埂材。
交叉編譯
- 在一個平臺下,編譯出另一個平臺能夠執(zhí)行的二進制代碼* 平臺:windows , mac os, linux* 處理器:x86(主要廠商英特爾和英偉達汤求,pc一般用這個),arm(嵌入式設(shè)備俏险,手機用這個),mips(開源的一個處理器架構(gòu)柬批,很多廠商對它進行修改)
- 原理:
- 源代碼->編譯->鏈接->可執(zhí)行程序
- 模擬其他平臺特性
- 交叉編譯的工具鏈
- 多個工具集合口锭,一個工具使用完后接著調(diào)用下一個工具
- 常見工具
- NDK:模擬其他平臺特性來編譯代碼的工具
- CDT:C/C++ Development Tools(高亮顯示C語言關(guān)鍵字)
- cygwin:模擬器凿宾,可以在windows下運行l(wèi)inux指令
NDK介紹
- NDK目錄結(jié)構(gòu)
- build/tools:linux的批處理文件
- platforms:編譯C代碼需要使用的頭文件和類庫
- prebuild:預(yù)編譯使用的二進制可執(zhí)行文件
- python-pachages:
- sources:NDK的源碼
- toolchains:工具鏈
- ndk-build.cmd:編譯打包C代碼的一個指令雏亚,肯定會調(diào)用toolchains開發(fā)人員不用管
使用JNI
- 在項目根目錄下創(chuàng)建 jni文件夾
- 在jni文件夾中創(chuàng)建一個 C 文件
- 除了兩個標(biāo)準(zhǔn)頭文件在包含
<jni.h>
- 在Java代碼中創(chuàng)建一個本地方法helloFromC
public native String helloFromC(); - 在JNI中定義函數(shù)實現(xiàn)這個方法龄广,函數(shù)必須這么寫
Java_com_hk_hellojni_MainActivity_helloFromC(JNIEnv * env,jobject obj)
- 其中 Java是必須的關(guān)鍵字炉菲,后面跟著包名類名方法名疲陕,中間用_隔開
- 參數(shù)必須是JNIEnv* env和jobject obj鞠值。
- env是一個二級指針墓赴,指向存放Java虛擬機的內(nèi)存地址的內(nèi)存塊
- obj表示那個對象調(diào)用該方法
- 可以使用javah指令自動生成:javah 包名.類名
- JDK1.7 在src目錄下執(zhí)行
- JDK1.6 在bin/classes目錄下執(zhí)行
- 結(jié)果在src下會生成一個c++文件竞膳,文件中有這么個方法:
jstring JNICALL Java_com_hk_hellojni_MainActivity_helloFromC(JNIEnv *, jobject);`
直接復(fù)制黏貼加參數(shù)名就可以在我們的C或C++文件中用了,保證無誤诫硕。
- 返回一個字符串坦辟,用C定義一個字符串
char* cstr = "hello from C"; - 將C字符串轉(zhuǎn)化成Java字符串
jstring jstr = (*env)->NewStringUTF(env,cstr);
- NewStringUTF是env所指向的指針?biāo)赶虻慕Y(jié)構(gòu)體中的函數(shù)指針變量
- 在jni文件夾中創(chuàng)建Android.mk文件,文件內(nèi)容如下
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
#編譯生成的類庫叫什么名字
LOCAL_MODULE:= hello
#要編譯的C文件
LOCAL_SRC_FILES:= Hello.c
include $(BUILD_SHARED_LIBRARY) - 在jni文件夾下執(zhí)行ndk-build.cmd指令(要提前配置到環(huán)境變量中)
- Java代碼中加載so類庫章办,調(diào)用本地方法
System.loadLibrary("hello");//一般在靜態(tài)代碼塊中執(zhí)行
注意:ndk-build.cmd指令執(zhí)行默認生成arm架構(gòu)的類庫长窄,如果需要支持其他cpu架構(gòu)需要在jni文件夾下創(chuàng)建Application.mk文件里面加入需要支持的架構(gòu),如纲菌,加入x86的支持
APP_ABI :=armeabi armeabi-v7a x86
如果需要支持cpu全部架構(gòu)挠日,把等號右邊換成all
JNI常見錯誤
- findLibrary returned null (類庫加載失敗)
- CPU平臺不匹配
- 加載類庫時翰舌,寫錯類庫名字
- 本地方法找不到
- 忘記加載類庫
- C代碼中的和Java本地方法對應(yīng)的方法名寫錯
Eclipse配置NDK開發(fā)環(huán)境
- 指定NDK位置:Windows->Preferences->Android->NDK->Browser到NDK所放位置下的build文件夾
- 關(guān)聯(lián)jni.h:選中項目右鍵->Properties->C/C++ General->Paths and Symbols->Add->選擇NDK目錄下的platforms選擇對應(yīng)的SDK版本號嚣潜,選擇cpu架構(gòu)選擇usr選擇include(例如:D:\android\android-ndk-r11c\platforms\android-21\arch-arm\usr\include)->點擊完成> 配置完成后可以直接運行Android Application,在編譯的時候會自動生成類庫
C語言使用Logcat(調(diào)試)
-
Android.mk
文件增加LOCAL_LDLIBS += -llog
- C代碼中增加
#include <android/log.h> #define LOG_TAG "hvcker"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, VA_ARGS)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, VA_ARGS) - 在代碼中就可以用了
LOGI("info\n");
LOGD("debug\n");
在C中使用反射調(diào)用Java方法
/**
* 得到Java字節(jié)碼對象內(nèi)存地址
*/
//jclass (*FindClass)(JNIEnv*, const char*);
jclass clazz = (*env)->FindClass(env,"com/example/jniccj/MainActivity");
/**
* 得到Java方法椅贱,最后一個參數(shù)是方法簽名懂算,可用javap -s 獲取
*/
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID methodID = (*env)->GetMethodID(env,clazz,"show","(Ljava/lang/String;)V");
/**
* 調(diào)用方法只冻,CallXxxMethod,Xxx為方法返回值,最后一個參數(shù)是可變參數(shù)计技,傳入方法參數(shù)
*/
//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
(*env)->CallVoidMethod(env,obj,methodID,(*env)->NewStringUTF(env,"hello Java"));
- javap -s 要在projectName/bin/classes目錄下執(zhí)行
- 執(zhí)行代碼:javap -s packageName.classnName
調(diào)用C++代碼
#include <jni.h> //包含同一個文件夾下的聲明函數(shù)的頭文件
#include "com_example_jnicpp_MainActivity.h"
JNIEXPORT jstring JNICALL
Java_com_example_jnicpp_MainActivity_helloCpp( JNIEnv * env, jobject obj) {
char * cstr = "hello from cpp"; //C++的env只是一個一級指針
return env->NewStringUTF(cstr);
}
- 包含頭文件(用javah 生成的那個在src目錄下的文件)
- C的env和C++的env的區(qū)別
- 在C中JNIEnv是一個結(jié)構(gòu)體指針
typedef const struct JNINativeInterface* JNIEnv;
喜德,后面再加一個就表示是一個二級指針,所以用的時候要先取出env所指向的地址垮媒,該地址是指向JNINativeInterface結(jié)構(gòu)體的指針舍悯,所以要調(diào)用JNINativeInterface里面的方法要(env)->xxx - 在C++中JNIEnv是一個里面含有
JNINativeInterface
的結(jié)構(gòu)體
typedef _JNIEnv JNIEnv;
struct _JNIEnv {
/* do not rename this;
it does not seem to be entirely opaque
/
const struct JNINativeInterface functions;
...
}
所以C++的 JNIEnv* 是一個一級指針,所以調(diào)用的時候直接env->xxx就可以了睡雇。
- 在C中JNIEnv是一個結(jié)構(gòu)體指針
還有一點要注意的是:C++是面向?qū)ο蟮拿瘸模运趫?zhí)行類似newStringUTF方法時不需要傳入本身env,直接env->newStringUTF("hello from c");底層也是調(diào)用
JNINativeInterface
中的函數(shù)jstring NewStringUTF(const char* bytes) { return functions->NewStringUTF(this, bytes); }
分支C進程
int pid = fork();
//如果pid = 0分支成功
if(pid == 0){
while(1){
LOGD("fork C");
sleep(1);
}
}
- 分支出來的C進程在不同型號的手機會有不同的表現(xiàn)形式它抱,例如在魅族手機秕豫,依賴于Android進程,程序管理里面強制停止观蓄,C進程隨之停止混移,但在有些型號手機中,C進程怎么殺也殺不死侮穿,只能用adb shell kill [pid]來刪除
- 可以分支出兩個守護進程歌径,這樣怎么殺也殺不掉(可以給線程做保活)
常用代碼(C部分)
中文亂碼
jstring ctojstring(JNIEnv env, char tmpstr) {
jclass Class_string;
jmethodID mid_String, mid_getBytes;
jbyteArray bytes;
jbyte* log_utf8;
jstring codetype, jstr;
Class_string = (env)->FindClass(env, "java/lang/String"); //獲取class
//先將gbk字符串轉(zhuǎn)為java里的string格式
mid_String = (env)->GetMethodID(env, Class_string, "<init>", "([BLjava/lang/String;)V");
bytes = (env)->NewByteArray(env, strlen(tmpstr));
(env)->SetByteArrayRegion(env, bytes, 0, strlen(tmpstr), (jbyte) tmpstr);
codetype = (env)->NewStringUTF(env, "gbk");
jstr = (jstring) (env)->NewObject(env, Class_string, mid_String, bytes,codetype);
(env)->DeleteLocalRef(env, bytes);
//再將string變utf-8字符串撮珠。
mid_getBytes = (env)->GetMethodID(env, Class_string, "getBytes", "(Ljava/lang/String;)[B"); codetype = (env)->NewStringUTF(env, "utf-8");
bytes = (jbyteArray) (env)->CallObjectMethod(env, jstr, mid_getBytes, codetype);
log_utf8 = (env)->GetByteArrayElements(env, bytes, JNI_FALSE);
return (*env)->NewStringUTF(env, log_utf8);
}Java字符串轉(zhuǎn)C
char* _JString2CStr(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = (env)->FindClass(env, "java/lang/String");
jstring strencode = (env)->NewStringUTF(env,"GB2312");
jmethodID mid = (env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)(env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312");
jsize alen = (env)->GetArrayLength(env, barr);
jbyte ba = (env)->GetByteArrayElements(env, barr, JNI_FALSE);
if(alen > 0) {
rtn = (char)malloc(alen+1); //"\0" memcpy(rtn, ba, alen);
rtn[alen]=0;
}
(*env)->ReleaseByteArrayElements(env, barr, ba,0);
return rtn;
}C語言操作Java數(shù)組
//拿到整形數(shù)據(jù)的長度和整形數(shù)組的指針
//jsize (GetArrayLength)(JNIEnv, jarray);
int length = (env)->GetArrayLength(env,jintarr);
//jint (GetIntArrayElements)(JNIEnv, jintArray, jboolean);
int * arrp = (env)->GetIntArrayElements(env,jintarr,0);
int i;
for(i = 0;i<length;i++){
*(arrp+i)+=20;
}