JNI與NDK

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

  1. 在項目根目錄下創(chuàng)建 jni文件夾
  2. 在jni文件夾中創(chuàng)建一個 C 文件
  3. 除了兩個標(biāo)準(zhǔn)頭文件在包含<jni.h>
  4. 在Java代碼中創(chuàng)建一個本地方法helloFromC
    public native String helloFromC();
  5. 在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++文件中用了,保證無誤诫硕。
  1. 返回一個字符串坦辟,用C定義一個字符串
    char* cstr = "hello from C";
  2. 將C字符串轉(zhuǎn)化成Java字符串
    jstring jstr = (*env)->NewStringUTF(env,cstr);
  • NewStringUTF是env所指向的指針?biāo)赶虻慕Y(jié)構(gòu)體中的函數(shù)指針變量
  1. 在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)
  2. 在jni文件夾下執(zhí)行ndk-build.cmd指令(要提前配置到環(huán)境變量中)
  3. 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常見錯誤

  1. findLibrary returned null (類庫加載失敗)
  • CPU平臺不匹配
  • 加載類庫時翰舌,寫錯類庫名字
  1. 本地方法找不到
  • 忘記加載類庫
  • 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++是面向?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;
    }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市金矛,隨后出現(xiàn)的幾起案子芯急,更是在濱河造成了極大的恐慌,老刑警劉巖驶俊,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娶耍,死亡現(xiàn)場離奇詭異,居然都是意外死亡饼酿,警方通過查閱死者的電腦和手機榕酒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來故俐,“玉大人想鹰,你說我怎么就攤上這事∫┌妫” “怎么了辑舷?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長槽片。 經(jīng)常有香客問我何缓,道長肢础,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任碌廓,我火速辦了婚禮传轰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谷婆。我一直安慰自己慨蛙,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布波材。 她就那樣靜靜地躺著股淡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪廷区。 梳的紋絲不亂的頭發(fā)上唯灵,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機與錄音隙轻,去河邊找鬼埠帕。 笑死,一個胖子當(dāng)著我的面吹牛玖绿,可吹牛的內(nèi)容都是我干的敛瓷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼斑匪,長吁一口氣:“原來是場噩夢啊……” “哼呐籽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚀瘸,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤狡蝶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贮勃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贪惹,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年寂嘉,在試婚紗的時候發(fā)現(xiàn)自己被綠了奏瞬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡泉孩,死狀恐怖硼端,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情寓搬,我是刑警寧澤显蝌,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響曼尊,放射性物質(zhì)發(fā)生泄漏酬诀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一骆撇、第九天 我趴在偏房一處隱蔽的房頂上張望瞒御。 院中可真熱鬧,春花似錦神郊、人聲如沸肴裙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜻懦。三九已至,卻和暖如春夕晓,著一層夾襖步出監(jiān)牢的瞬間宛乃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工蒸辆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留征炼,地道東北人。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓躬贡,卻偏偏與公主長得像谆奥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拂玻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

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