如何将.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類:
package com.okwap.testjni;
public final class myjni {
//native方法,
public static native string sayhello(string name);
}
2、通過javah指令生成.h檔案,内容如下(com_okwap_testjni.h檔案):
#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檔案中實作以上方法:
#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()函數。先看示例代碼:
//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方法,而無需遵循特定的方法命名格式。來看一段示例代碼吧:
//定義目标類名稱
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
參照上文示例中初始化該結構的代碼:
其中比較難以了解的是第二個參數——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等宏值,示例代碼如下:
#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 ,示例代碼如下:
#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