天天看點

Android--JNI程式設計詳解

如何将.so檔案打包到.apk

讓我們 先 從最簡單的情況開始,假如已有一個jni實作——libxxx.so檔案,那麼如何在apk中使用它呢?

實作步驟如下:

1、在你的項目根目錄下建立libs/armeabi目錄;

2、将libxxx.so檔案copy到 libs/armeabi/下;

3、此時adt插件自動編譯輸出的.apk檔案中已經包括.so檔案了;

4、安裝apk檔案,即可直接使用jni中的方法;

我想還需要簡單說明一下libxxx.so的命名規則,沿襲linux傳統,lib.so是類庫檔案名稱的格式,但在java的system.loadlibrary(" something ")方法中指定庫名稱時,不能包括 字首—— lib,以及字尾——.so。

準備編寫自己的jni子產品

你一定想知道如何編寫自己的xxx.so,不過這涉及了太多有關jni的知識。簡單的說:jni是java平台定義的用于和宿主平台上的本地代碼進行互動的“java标準”,它通常有兩個使用場景:1.使用(之前使用c/c++、delphi開發的)遺留代碼;2.為了更好、更直接地與硬體互動并獲得更高性能 。

1、首先建立含有native方法的java類:

Android--JNI程式設計詳解

package com.okwap.testjni;  

public final class myjni {  

//native方法,  

public static native string sayhello(string name);  

}  

2、通過javah指令生成.h檔案,内容如下(com_okwap_testjni.h檔案):

Android--JNI程式設計詳解

#include  

#ifndef _included_com_okwap_testjni_myjni  

#define _included_com_okwap_testjni_myjni  

#ifdef __cplusplus  

extern "c" {  

#endif  

jniexport jstring jnicall java_com_okwap_testjni_myjni_sayhello  

(jnienv *, jclass, jstring);  

這是一個标準的c語言頭檔案,其中的jniexport、jnicall是jni關鍵字(事實上它是沒有任何内容的宏,僅用于訓示性說明),而jint、jstring是jni環境下對int及java.lang.string類型的映射。這些關鍵字的定義都可以在jni.h中看到。

3、在 com_okwap_testjni.c檔案中實作以上方法:

Android--JNI程式設計詳解

#include "com_okwap_testjni.h"  

jniexport jstring jnicall java_com_okwap_testjni_myjni_sayhello(jnienv* env, jclass, jstring str){  

//從jstring類型取得c語言環境下的char*類型  

const char* name = (*env)->getstringutfchars(env, str, 0);  

//本地常量字元串  

char* hello = "你好,";  

//動态配置設定目标字元串空間  

char* result = malloc((strlen(name) + strlen(hello) + 1)*sizeof(char));  

memset(result,0,sizeof(result));  

//字元串連結  

strcat(result,hello);  

strcat(result,name);  

//釋放jni配置設定的記憶體  

(*env)->releasestringutfchars(env,str,name);  

//生成傳回值對象  

str = (*env)->newstringutf(env, "你好 jni~!");  

//釋放動态配置設定的記憶體  

free(result);  

//  

return str;  

}   

4、編譯——兩種不同的編譯環境

以上的c語言代碼要編譯成最終.so動态庫檔案,有兩種途徑:

android ndk :全稱是native developer kit,是用于編譯本地jni源碼的工具,為開發人員将本地方法整合到android應用中提供了友善。事實上ndk和完整源碼編譯環境一樣,都使用android的編譯系統——即通過android.mk檔案控制編譯。ndk可以運作在linux、mac、window(+cygwin)三個平台上。有關ndk的使用方法及更多細節請參考以下資料:

不管你選擇以上兩種方法的哪一個,都必須編寫自己的android.mk檔案,有關該檔案的編寫請參考相關文檔。

jni元件的入口函數——jni_onload()、jni_onunload()

jni元件被成功加載和解除安裝時,會進行函數回調,當vm執行到system.loadlibrary(xxx)函數時,首先會去執行jni元件中的jni_onload()函數,而當vm釋放該元件時會呼叫jni_onunload()函數。先看示例代碼:

Android--JNI程式設計詳解

//onload方法,在system.loadlibrary()執行時被調用  

jint jni_onload(javavm* vm, void* reserved){  

logi("jni_onload startup~~!");  

return jni_version_1_4;  

//onunload方法,在jni元件被釋放時調用  

void jni_onunload(javavm* vm, void* reserved){  

loge("call jni_onunload ~~!!");  

jni_onload()有兩個重要的作用:

指定jni版本:告訴vm該元件使用那一個jni版本(若未提供jni_onload()函數,vm會預設該使用最老的jni 1.1版),如果要使用新版本的jni,例如jni 1.4版,則必須由jni_onload()函數傳回常量jni_version_1_4(該常量定義在jni.h中) 來告知vm。

初始化設定,當vm執行到system.loadlibrary()函數時,會立即先呼叫jni_onload()方法,是以在該方法中進行各種資源的初始化操作最為恰當。

jni_onunload()的作用與jni_onload()對應,當vm釋放jni元件時會呼叫它,是以在該方法中進行善後清理,資源釋放的動作最為合适。

使用registernativemethods方法

對java程式員來說,可能我們總是會遵循:1.編寫帶有native方法的java類;--->2.使用javah指令生成.h頭檔案;--->3.編寫代碼實作頭檔案中的方法,這樣的“官方” 流程,但也許有人無法忍受那“醜陋”的方法名稱,registernatives方法能幫助你把c/c++中的方法隐射到java中的native方法,而無需遵循特定的方法命名格式。來看一段示例代碼吧:

Android--JNI程式設計詳解

//定義目标類名稱  

static const char *classname = "com/okwap/testjni/myjni";  

//定義方法隐射關系  

static jninativemethod methods[] = {  

{"sayhello", "(ljava/lang/string;)ljava/lang/string;", (void*)sayhello},  

};  

//聲明變量  

jint result = jni_err;  

jnienv* env = null;  

jclass clazz;  

int methodslenght;  

//擷取jni環境對象  

if ((*vm)->getenv(vm, (void**) &env, jni_version_1_4) != jni_ok) {  

loge("error: getenv failed\n");  

return jni_err;  

assert(env != null);  

//注冊本地方法.load 目标類  

clazz = (*env)->findclass(env,classname);  

if (clazz == null) {  

loge("native registration unable to find class '%s'", classname);  

//建立方法隐射關系  

//取得方法長度 www.2cto.com  

methodslenght = sizeof(methods) / sizeof(methods[0]);  

if ((*env)->registernatives(env,clazz, methods, methodslenght) < 0) {  

loge("registernatives failed for '%s'", classname);  

result = jni_version_1_4;  

return result;  

建立c/c++方法和java方法之間映射關系的關鍵是 jninativemethod 結構,該結構定義在jni.h中,具體定義如下:  

typedef struct {  

const char* name;//java方法名稱  

const char* signature; //java方法簽名  

void* fnptr;//c/c++的函數指針  

} jninativemethod  

參照上文示例中初始化該結構的代碼:

Android--JNI程式設計詳解

其中比較難以了解的是第二個參數——signature字段的取值,實際上這些字元與函數的參數類型/傳回類型一一對應,其中"()" 中的字元表示參數,後面的則代表傳回值。例如"()v" 就表示void func(),"(ii)v" 表示 void func(int, int),具體的每一個字元的對應關系如下:

字元 java類型 c/c++類型

v void void

z jboolean boolean

i jint int

j jlong long

d jdouble double

f jfloat float

b jbyte byte

c jchar char

s jshort short

數組則以"["開始,用兩個字元表示:

[z jbooleanarray boolean[]

[i jintarray int[]

[f jfloatarray float[]

[b jbytearray byte[]

[c jchararray char[]

[s jshortarray short[]

[d jdoublearray double[]

[j jlongarray long[]

上面的都是基本類型,如果參數是java類,則以"l"開頭,以";"結尾,中間是用"/"隔開包及類名,而其對應的c函數的參數則為jobject,一個例外是string類,它對應c類型jstring,例如:ljava/lang /string; 、ljava/net/socket; 等,如果java函數位于一個嵌入類(也被稱為内部類),則用$作為類名間的分隔符,例如:"landroid/os/fileutils$filestatus;"。

使用registernativemethods方法不僅僅是為了改變那醜陋的長方法名,最重要的是可以提高效率,因為當java類别透過vm呼叫到本地函數時,通常是依靠vm去動态尋找.so中的本地函數(是以它們才需要特定規則的命名格式),如果某方法需要連續呼叫很多次,則每次都要尋找一遍,是以使用registernatives将本地函數向vm進行登記,可以讓其更有效率的找到函數。

registernativemethods方法的另一個重要用途是,運作時動态調整本地函數與java函數值之間的映射關系,隻需要多次調用registernativemethods()方法,并傳入不同的映射表參數即可。

jni中的日志輸出

你一定非常熟悉在java代碼中使用log.x(tag,“message”)系列方法,在c/c++代碼中也一樣,不過首先你要include相關頭檔案。遺憾的是你使用不同的編譯環境( 請參考上文中兩種編譯環境的介紹) ,對應的頭檔案略有不同。。

如果是在完整源碼編譯環境下,隻要include 頭檔案,就可以使用對應的logi、logd等方法了,同時請定義log_tag,log_ndebug等宏值,示例代碼如下:

Android--JNI程式設計詳解

#define log_tag "hellojni"  

#define log_ndebug 0  

#define log_nidebug 0  

#define log_nddebug 0  

jstring java_com_inc_android_ime_hellojni_stringfromjni(jnienv* env,jobject thiz){  

logi("call stringfromjni!\n");  

return (*env)->newstringutf(env, "hello from jni (中文)!");  

與日志相關的.h頭檔案,在以下源碼路徑:

myeclair\frameworks\base\include\utils\log.h

myeclair\system\core\include\cutils\log.h

如果你是在ndk環境下編譯,則需要#include ,示例代碼如下:

Android--JNI程式設計詳解

#ifndef __jnilogger_h_  

#define __jnilogger_h_  

#ifdef _cplusplus  

#ifndef log_tag  

#define log_tag "my_log_tag"  

#define logd(...) __android_log_print(android_log_debug,log_tag,__va_args__)  

#define logi(...) __android_log_print(android_log_info,log_tag,__va_args__)  

#define logw(...) __android_log_print(android_log_warn,log_tag,__va_args__)  

#define loge(...) __android_log_print(android_log_error,log_tag,__va_args__)  

#define logf(...) __android_log_print(android_log_fatal,log_tag,__va_args__)  

ifeq ($(host_os),windows)

#ndk環境下

local_ldlibs := -llog

else

#完整

轉載:http://blog.csdn.net/chaoyu168/article/details/51209699