項目決定移植一款C++開源項目到Android平臺,開始對JNI深刻研究。css
JNI是什麼?html
JNI(Java Native Interface)意爲JAVA本地調用,它容許Java代碼和其餘語言寫的代碼進行交互,簡單的說,一種在Java虛擬機控制下執行代碼的標準機制。
java
NDK是什麼?android
Android NDK(Native Development Kit )是一套工具集合,容許你用像C/C++語言那樣實現應用程序的一部分。c++
爲何要用NDK?
程序員
一、安全性,java是半解釋型語言,很容易被反彙編後拿到源代碼文件,咱們能夠在重要的交互功能使用C語言代替。
二、效率,C語言比起java來講效率要高出不少。
編程
JNI和NDK的區別?
api
從工具上說,NDK其實多了一個把.so和.apk打包的工具,而JNI開發並無打包,只是把.so文件放到文件系統的特定位置。
從編譯庫說,NDK開發C/C++只能能使用NDK自帶的有限的頭文件,而使用JNI則可使用文件系統中帶的頭文件。
從編寫方式說,它們同樣。數組
一、JNI組織結構安全
JNI函數表的組成就像C++的虛函數表,虛擬機能夠運行多張函數表。
JNI接口指針僅在當前線程中起做用,指針不能從一個線程進入另外一個線程,但能夠在不一樣的線程中調用本地方法。
二、原始數據
Jobject 對象 引用類型
Java類型 | 本地類型(JNI) | 描述 |
boolean(布爾型) | jboolean | 無符號8個比特 |
byte(字節型) | jbyte | 有符號8個比特 |
char(字符型) | jchar | 無符號16個比特 |
short(短整型) | jshort | 有符號16個比特 |
int(整型) | jint | 有符號32個比特 |
long(長整型) | jlong | 有符號64個比特 |
float(浮點型) | jfloat | 32個比特 |
double(雙精度浮點型) | jdouble | 64個比特 |
void(空型) | void | N/A |
函數操做
函數 | Java 數組類型 | 本地類型 | 說明 |
GetBooleanArrayElements | jbooleanArray | jboolean | ReleaseBooleanArrayElements 釋放 |
GetByteArrayElements | jbyteArray | jbyte | ReleaseByteArrayElements 釋放 |
GetCharArrayElements | jcharArray | jchar | ReleaseShortArrayElements 釋放 |
GetShortArrayElements | jshortArray | jshort | ReleaseBooleanArrayElements 釋放 |
GetIntArrayElements | jintArray | jint | ReleaseIntArrayElements 釋放 |
GetLongArrayElements | jlongArray | jlong | ReleaseLongArrayElements 釋放 |
GetFloatArrayElements | jfloatArray | jfloat | ReleaseFloatArrayElements 釋放 |
GetDoubleArrayElements | jdoubleArray | jdouble | ReleaseDoubleArrayElements 釋放 |
GetObjectArrayElement | 自定義對象 | object | |
SetObjectArrayElement | 自定義對象 | object | |
GetArrayLength | 獲取數組大小 | ||
New<Type>Array | 建立一個指定長度的原始數據類型的數組 | ||
GetPrimitiveArrayCritical | 獲得指向原始數據類型內容的指針,該方法可能使垃圾回收不能執行,該方法可能返回數組的拷貝,所以必須釋放此資源。 | ||
ReleasePrimitiveArrayCritical | 釋放指向原始數據類型內容的指針,該方法可能使垃圾回收不能執行,該方法可能返回數組的拷貝,所以必須釋放此資源。 | ||
NewStringUTF | jstring類型的方法轉換 | ||
GetStringUTFChars | jstring類型的方法轉換 | ||
DefineClass | 從原始類數據的緩衝區中加載類 | ||
FindClass | 該函數用於加載本地定義的類。它將搜索由CLASSPATH 環境變量爲具備指定名稱的類所指定的目錄和 zip文件 | ||
GetObjectClass | 經過對象獲取這個類。該函數比較簡單,惟一注意的是對象不能爲NULL,不然獲取的class確定返回也爲NULL | ||
GetSuperclass | 獲取父類或者說超類 。 若是 clazz 表明類class而非類 object,則該函數返回由 clazz 所指定的類的超類。 若是 clazz指定類 object 或表明某個接口,則該函數返回NULL | ||
IsAssignableFrom | 肯定 clazz1 的對象是否可安全地強制轉換爲clazz2 | ||
Throw | 拋出 java.lang.Throwable 對象 | ||
ThrowNew | 利用指定類的消息(由 message 指定)構造異常對象並拋出該異常 | ||
ExceptionOccurred | 肯定是否某個異常正被拋出。在平臺相關代碼調用 ExceptionClear() 或 Java 代碼處理該異常前,異常將始終保持拋出狀態 | ||
ExceptionDescribe | 將異常及堆棧的回溯輸出到系統錯誤報告信道(例如 stderr)。該例程可便利調試操做 | ||
ExceptionClear | 清除當前拋出的任何異常。若是當前無異常,則此例程不產生任何效果 | ||
FatalError | 拋出致命錯誤而且不但願虛擬機進行修復。該函數無返回值 | ||
NewGlobalRef | 建立 obj 參數所引用對象的新全局引用。obj 參數既能夠是全局引用,也能夠是局部引用。全局引用經過調用DeleteGlobalRef() 來顯式撤消。 | ||
DeleteGlobalRef | 刪除 globalRef 所指向的全局引用 | ||
DeleteLocalRef | 刪除 localRef所指向的局部引用 | ||
AllocObject | 分配新 Java 對象而不調用該對象的任何構造函數。返回該對象的引用。clazz 參數務必不要引用數組類。 | ||
getObjectClass | 返回對象的類 | ||
IsSameObject | 測試兩個引用是否引用同一 Java 對象 | ||
NewString | 利用 Unicode 字符數組構造新的 java.lang.String 對象 | ||
GetStringLength | 返回 Java 字符串的長度(Unicode 字符數) | ||
GetStringChars | 返回指向字符串的 Unicode 字符數組的指針。該指針在調用 ReleaseStringchars() 前一直有效 | ||
ReleaseStringChars | 通知虛擬機平臺相關代碼無需再訪問 chars。參數chars 是一個指針,可經過 GetStringChars() 從 string 得到 | ||
NewStringUTF | 利用 UTF-8 字符數組構造新 java.lang.String 對象 | ||
GetStringUTFLength | 以字節爲單位返回字符串的 UTF-8 長度 | ||
GetStringUTFChars | 返回指向字符串的 UTF-8 字符數組的指針。該數組在被ReleaseStringUTFChars() 釋放前將一直有效 | ||
ReleaseStringUTFChars | 通知虛擬機平臺相關代碼無需再訪問 utf。utf 參數是一個指針,可利用 GetStringUTFChars() 得到 | ||
NewObjectArray | 構造新的數組,它將保存類 elementClass 中的對象。全部元素初始值均設爲 initialElement | ||
Set<PrimitiveType>ArrayRegion | 將基本類型數組的某一區域從緩衝區中複製回來的一組函數 | ||
GetFieldID | 返回類的實例(非靜態)域的屬性 ID。該域由其名稱及簽名指定。訪問器函數的 Get<type>Field 及 Set<type>Field系列使用域 ID 檢索對象域。GetFieldID() 不能用於獲取數組的長度域。應使用GetArrayLength()。 |
||
Get<type>Field | 該訪問器例程系列返回對象的實例(非靜態)域的值。要訪問的域由經過調用GetFieldID() 而獲得的域 ID 指定。 | ||
Set<type>Field | 該訪問器例程系列設置對象的實例(非靜態)屬性的值。要訪問的屬性由經過調用 SetFieldID() 而獲得的屬性 ID指定。 |
||
GetStaticFieldID GetStatic<type>Field SetStatic<type>Field |
同上,只不過是靜態屬性。 | ||
GetMethodID | 返 回類或接口實例(非靜態)方法的方法 ID。方法可在某個 clazz 的超類中定義,也可從 clazz 繼承。該方法由其名稱和簽名決定。 GetMethodID() 可以使未初始化的類初始化。要得到構造函數的方法 ID,應將<init> 做爲方法名,同時將void (V) 做爲返回類型。 | ||
CallVoidMethod | |||
CallObjectMethod | |||
CallBooleanMethod | |||
CallByteMethod | |||
CallCharMethod | |||
CallShortMethod | |||
CallIntMethod | |||
CallLongMethod | |||
CallFloatMethod | |||
CallDoubleMethod | |||
GetStaticMethodID | 調用靜態方法 | ||
Call<type>Method | |||
RegisterNatives | 向 clazz 參數指定的類註冊本地方法。methods 參數將指定 JNINativeMethod 結構的數組,其中包含本地方法的名稱、簽名和函數指針。nMethods 參數將指定數組中的本地方法數。 | ||
UnregisterNatives | 取消註冊類的本地方法。類將返回到連接或註冊了本地方法函數前的狀態。該函數不該在常規平臺相關代碼中使用。相反,它能夠爲某些程序提供一種從新加載和從新連接本地庫的途徑。 | ||
域描述符
域 | Java 語言 |
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
引用類型則爲 L + 該類型類描述符 + 。
數組,其爲 : [ + 其類型的域描述符 + 。
多維數組則是 n個[ +該類型的域描述符 , N表明的是幾維數組。
[html] view plaincopy
String類型的域描述符爲 Ljava/lang/String;
[ + 其類型的域描述符 + ;
int[ ] 其描述符爲[I
float[ ] 其描述符爲[F
String[ ] 其描述符爲[Ljava/lang/String;
Object[ ]類型的域描述符爲[Ljava/lang/Object;
int [ ][ ] 其描述符爲[[I
float[ ][ ] 其描述符爲[[F
將參數類型的域描述符按照申明順序放入一對括號中後跟返回值類型的域描述符,規則以下: (參數的域描述符的疊加)返回類型描述符。對於,沒有返回值的,用V(表示void型)表示。
舉例以下:
[html] view plaincopy
Java層方法 JNI函數簽名
String test ( ) Ljava/lang/String;
int f (int i, Object object) (ILjava/lang/Object;)I
void set (byte[ ] bytes) ([B)V
JNIEnv與JavaVM
JNIEnv 概念 : 是一個線程相關的結構體, 該結構體表明瞭 Java 在本線程的運行環境 ;
JNIEnv 與 JavaVM : 注意區分這兩個概念;
-- JavaVM : JavaVM 是 Java虛擬機在 JNI 層的表明, JNI 全局只有一個;
-- JNIEnv : JavaVM 在線程中的表明, 每一個線程都有一個, JNI 中可能有不少個 JNIEnv;
JNIEnv 做用 :
-- 調用 Java 函數 : JNIEnv 表明 Java 運行環境, 可使用 JNIEnv 調用 Java 中的代碼;
-- 操做 Java 對象 : Java 對象傳入 JNI 層就是 Jobject 對象, 須要使用 JNIEnv 來操做這個 Java 對象;
JNIEnv 體系結構
線程相關 : JNIEnv 是線程相關的, 即 在 每一個線程中 都有一個 JNIEnv 指針, 每一個JNIEnv 都是線程專有的, 其它線程不能使用本線程中的 JNIEnv, 線程 A 不能調用 線程 B 的 JNIEnv;
JNIEnv 不能跨線程 :
-- 當前線程有效 : JNIEnv 只在當前線程有效, JNIEnv 不能在 線程之間進行傳遞, 在同一個線程中, 屢次調用 JNI層方法, 傳入的 JNIEnv 是相同的;
-- 本地方法匹配多JNIEnv : 在 Java 層定義的本地方法, 能夠在不一樣的線程調用, 所以 能夠接受不一樣的 JNIEnv;
JNIEnv 結構 : 由上面的代碼能夠得出, JNIEnv 是一個指針, 指向一個線程相關的結構, 線程相關結構指向 JNI 函數指針 數組, 這個數組中存放了大量的 JNI 函數指針, 這些指針指向了具體的 JNI 函數;
注 意:JNIEnv只在當前線程中有效。本地方法不能將JNIEnv從一個線程傳遞到另外一個線程中。相同的 Java 線程中對本地方法屢次調用時,傳遞給該本地方法的JNIEnv是相同的。可是,一個本地方法可被不一樣的 Java 線程所調用,所以能夠接受不一樣的 JNIEnv。
UTF-8編碼
JNI使用改進的UTF-8字符串來表示不一樣的字符類型。Java使用UTF-16編碼。UTF-8編碼主要使用於C語言,由於它的編碼用\u000表示爲0xc0,而不是一般的0×00。非空ASCII字符改進後的字符串編碼中能夠用一個字節表示。
錯誤
JNI不會檢查NullPointerException、IllegalArgumentException這樣的錯誤,緣由是:致使性能降低。
在絕大多數C的庫函數中,很難避免錯誤發生。
JNI容許用戶使用Java異常處理。大部分JNI方法會返回錯誤代碼但自己並不會報出異常。所以,頗有必要在代碼自己進行處理,將異常拋給Java。在JNI內部,首先會檢查調用函數返回的錯誤代碼,以後會調用ExpectOccurred()返回一個錯誤對象。
[html] view plaincopy
jthrowable ExceptionOccurred(JNIEnv *env);
例如:一些操做數組的JNI函數不會報錯,所以能夠調用ArrayIndexOutofBoundsException或ArrayStoreExpection方法報告異常。
一、*.so的入口函數
JNI_OnLoad()與JNI_OnUnload()
當Android的VM(Virtual Machine)執行到System.loadLibrary()函數時,首先會去執行C組件裏的JNI_OnLoad()函數。它的用途有二:
(1) 告訴VM此C組件使用那一個JNI版本。若是你的*.so檔沒有提供JNI_OnLoad()函數,VM會默認該*.so檔是使用最老的JNI 1.1版本。因爲新版的JNI作了許多擴充,若是須要使用JNI的新版功能,例如JNI 1.4的java.nio.ByteBuffer,就必須藉由JNI_OnLoad()函數來告知VM。
(2)因爲VM執行到System.loadLibrary()函數時,就會當即先呼叫JNI_OnLoad(),因此C組件的開發者能夠藉由JNI_OnLoad()來進行C組件內的初期值之設定(Initialization) 。
二、返回值
[html] view plaincopy
jstring str = env->newStringUTF("HelloJNI"); //直接使用該JNI構造一個jstring對象返回
return str ;
[html] view plaincopy
jobjectArray ret = 0;
jsize len = 5;
jstring str;
string value("hello");
ret = (jobjectArray)(env->NewObjectArray(len, env->FindClass("java/lang/String"), 0));
for(int i = 0; i < len; i++)
{
str = env->NewStringUTF(value..c_str());
env->SetObjectArrayElement(ret, i, str);
}
return ret; 返回數組
[html] view plaincopy
jclass m_cls = env->FindClass("com/ldq/ScanResult");
jmethodID m_mid = env->GetMethodID(m_cls,"<init>","()V");
jfieldID m_fid_1 = env->GetFieldID(m_cls,"ssid","Ljava/lang/String;");
jfieldID m_fid_2 = env->GetFieldID(m_cls,"mac","Ljava/lang/String;");
jfieldID m_fid_3 = env->GetFieldID(m_cls,"level","I");
jobject m_obj = env->NewObject(m_cls,m_mid);
env->SetObjectField(m_obj,m_fid_1,env->NewStringUTF("AP1"));
env->SetObjectField(m_obj,m_fid_2,env->NewStringUTF("00-11-22-33-44-55"));
env->SetIntField(m_obj,m_fid_3,-50);
return m_obj; 返回自定義對象
[html] view plaincopy
jclass list_cls = env->FindClass("Ljava/util/ArrayList;");//得到ArrayList類引用
if(listcls == NULL)
{
cout << "listcls is null \n" ;
}
jmethodID list_costruct = env->GetMethodID(list_cls , "<init>","()V"); //得到得構造函數Id
jobject list_obj = env->NewObject(list_cls , list_costruct); //建立一個Arraylist集合對象
//或得Arraylist類中的 add()方法ID,其方法原型爲: boolean add(Object object) ;
jmethodID list_add = env->GetMethodID(list_cls,"add","(Ljava/lang/Object;)Z");
jclass stu_cls = env->FindClass("Lcom/feixun/jni/Student;");//得到Student類引用
//得到該類型的構造函數 函數名爲 <init> 返回類型必須爲 void 即 V
jmethodID stu_costruct = env->GetMethodID(stu_cls , "<init>", "(ILjava/lang/String;)V");
for(int i = 0 ; i < 3 ; i++)
{
jstring str = env->NewStringUTF("Native");
//經過調用該對象的構造函數來new 一個 Student實例
jobject stu_obj = env->NewObject(stucls , stu_costruct , 10,str); //構造一個對象
env->CallBooleanMethod(list_obj , list_add , stu_obj); //執行Arraylist類實例的add方法,添加一個stu對象
}
return list_obj ; 返回對象集合
三、操做Java層的類
[html] view plaincopy
//得到jfieldID 以及 該字段的初始值
jfieldID nameFieldId ;
jclass cls = env->GetObjectClass(obj); //得到Java層該對象實例的類引用,即HelloJNI類引用
nameFieldId = env->GetFieldID(cls , "name" , "Ljava/lang/String;"); //得到屬性句柄
if(nameFieldId == NULL)
{
cout << " 沒有獲得name 的句柄Id \n;" ;
}
jstring javaNameStr = (jstring)env->GetObjectField(obj ,nameFieldId); // 得到該屬性的值
const char * c_javaName = env->GetStringUTFChars(javaNameStr , NULL); //轉換爲 char *類型
string str_name = c_javaName ;
cout << "the name from java is " << str_name << endl ; //輸出顯示
env->ReleaseStringUTFChars(javaNameStr , c_javaName); //釋放局部引用
//構造一個jString對象
char * c_ptr_name = "I come from Native" ;
jstring cName = env->NewStringUTF(c_ptr_name); //構造一個jstring對象
env->SetObjectField(obj , nameFieldId , cName); // 設置該字段的值
四、回調Java層方法
[html] view plaincopy
jstring str = NULL;
jclass clz = env->FindClass("cc/androidos/jni/JniTest");
//獲取clz的構造函數並生成一個對象
jmethodID ctor = env->GetMethodID(clz, "<init>", "()V");
jobject obj = env->NewObject(clz, ctor);
// 若是是數組類型,則在類型前加[,如整形數組int[] intArray,則對應類型爲[I,整形數組String[] strArray對應爲[Ljava/lang/String;
jmethodID mid = env->GetMethodID(clz, "sayHelloFromJava", "(Ljava/lang/String;II[I)I");
if (mid)
{
LOGI("mid is get");
jstring str1 = env->NewStringUTF("I am Native");
jint index1 = 10;
jint index2 = 12;
//env->CallVoidMethod(obj, mid, str1, index1, index2);
// 數組類型轉換 testIntArray能不能不申請內存空間
jintArray testIntArray = env->NewIntArray(10);
jint *test = new jint[10];
for(int i = 0; i < 10; ++i)
{
*(test+i) = i + 100;
}
env->SetIntArrayRegion(testIntArray, 0, 10, test);
jint javaIndex = env->CallIntMethod(obj, mid, str1, index1, index2, testIntArray);
LOGI("javaIndex = %d", javaIndex);
delete[] test;
test = NULL;
}
[html] view plaincopy
static void event_callback(int eventId,const char* description) { //主進程回調能夠,線程中回調失敗。
if (gEventHandle == NULL)
return;
JNIEnv *env;
bool isAttached = false;
if (myVm->GetEnv((void**) &env, JNI_VERSION_1_2) < 0) { //獲取當前的JNIEnv
if (myVm->AttachCurrentThread(&env, NULL) < 0)
return;
isAttached = true;
}
jclass cls = env->GetObjectClass(gEventHandle); //獲取類對象
if (!cls) {
LOGE("EventHandler: failed to get class reference");
return;
}
jmethodID methodID = env->GetStaticMethodID(cls, "callbackStatic",
"(ILjava/lang/String;)V"); //靜態方法或成員方法
if (methodID) {
jstring content = env->NewStringUTF(description);
env->CallVoidMethod(gEventHandle, methodID,eventId,
content);
env->ReleaseStringUTFChars(content,description);
} else {
LOGE("EventHandler: failed to get the callback method");
}
if (isAttached)
myVm->DetachCurrentThread();
}
線程中回調
把c/c++中全部線程的建立,由pthread_create函數替換爲由Java層的建立線程的函數AndroidRuntime::createJavaThread。
[html] view plaincopy
static pthread_t create_thread_callback(const char* name, void (*start)(void *), void* arg)
{
return (pthread_t)AndroidRuntime::createJavaThread(name, start, arg);
}
static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { //異常檢測和排除
if (env->ExceptionCheck()) {
LOGE("An exception was thrown by callback '%s'.", methodName);
LOGE_EX(env);
env->ExceptionClear();
}
}
static void receive_callback(unsigned char *buf, int len) //回調
{
int i;
JNIEnv* env = AndroidRuntime::getJNIEnv();
jcharArray array = env->NewCharArray(len);
jchar *pArray ;
if(array == NULL){
LOGE("receive_callback: NewCharArray error.");
return;
}
pArray = (jchar*)calloc(len, sizeof(jchar));
if(pArray == NULL){
LOGE("receive_callback: calloc error.");
return;
}
//copy buffer to jchar array
for(i = 0; i < len; i++)
{
*(pArray + i) = *(buf + i);
}
//copy buffer to jcharArray
env->SetCharArrayRegion(array,0,len,pArray);
//invoke java callback method
env->CallVoidMethod(mCallbacksObj, method_receive,array,len);
//release resource
env->DeleteLocalRef(array);
free(pArray);
pArray = NULL;
checkAndClearExceptionFromCallback(env, __FUNCTION__);
}
public void Receive(char buffer[],int length){ //java層函數
String msg = new String(buffer);
msg = "received from jni callback" + msg;
Log.d("Test", msg);
}
[html] view plaincopy
jclass cls = env->GetObjectClass(obj);//得到Java類實例
jmethodID callbackID = env->GetMethodID(cls , "callback" , "(Ljava/lang/String;)V") ;//或得該回調方法句柄
if(callbackID == NULL)
{
cout << "getMethodId is failed \n" << endl ;
}
jstring native_desc = env->NewStringUTF(" I am Native");
env->CallVoidMethod(obj , callbackID , native_desc); //回調該方法,而且
五、傳對象到JNI調用
[html] view plaincopy
jclass stu_cls = env->GetObjectClass(obj_stu); //或得Student類引用
if(stu_cls == NULL)
{
cout << "GetObjectClass failed \n" ;
}
//下面這些函數操做,咱們都見過的。O(∩_∩)O~
jfieldID ageFieldID = env->GetFieldID(stucls,"age","I"); //得到得Student類的屬性id
jfieldID nameFieldID = env->GetFieldID(stucls,"name","Ljava/lang/String;"); // 得到屬性ID
jint age = env->GetIntField(objstu , ageFieldID); //得到屬性值
jstring name = (jstring)env->GetObjectField(objstu , nameFieldID);//得到屬性值
const char * c_name = env->GetStringUTFChars(name ,NULL);//轉換成 char *
string str_name = c_name ;
env->ReleaseStringUTFChars(name,c_name); //釋放引用
cout << " at Native age is :" << age << " # name is " << str_name << endl ;
六、與C++互轉
jbytearray轉c++byte數組
[html] view plaincopy
jbyte * arrayBody = env->GetByteArrayElements(data,0);
jsize theArrayLengthJ = env->GetArrayLength(data);
BYTE * starter = (BYTE *)arrayBody;
jbyteArray 轉 c++中的BYTE[]
[html] view plaincopy
jbyte * olddata = (jbyte*)env->GetByteArrayElements(strIn, 0);
jsize oldsize = env->GetArrayLength(strIn);
BYTE* bytearr = (BYTE*)olddata;
int len = (int)oldsize;
C++中的BYTE[]轉jbyteArray
[html] view plaincopy
jbyte *by = (jbyte*)pData;
jbyteArray jarray = env->NewByteArray(nOutSize);
env->SetByteArrayRegin(jarray, 0, nOutSize, by);
jbyteArray 轉 char *
[html] view plaincopy
char* data = (char*)env->GetByteArrayElements(strIn, 0);
char* 轉jstring
[html] view plaincopy
jstring WindowsTojstring(JNIEnv* env, char* str_tmp)
{
jstring rtn=0;
int slen = (int)strlen(str_tmp);
unsigned short* buffer=0;
if(slen == 0)
{
rtn = env->NewStringUTF(str_tmp);
}
else
{
int length = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, NULL, 0);
buffer = (unsigned short*)malloc(length*2+1);
if(MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, (LPWSTR)buffer, length) > 0)
{
rtn = env->NewString((jchar*)buffer, length);
}
}
if(buffer)
{
free(buffer);
}
return rtn;
}
char* jstring互轉
[html] view plaincopy
JNIEXPORT jstring JNICALL Java_com_explorer_jni_SambaTreeNative_getDetailsBy
(JNIEnv *env, jobject jobj, jstring pc_server, jstring server_user, jstring server_passwd)
{
const char *pc = env->GetStringUTFChars(pc_server, NULL);
const char *user = env->GetStringUTFChars(server_user, NULL);
const char *passwd = env->GetStringUTFChars(server_passwd, NULL);
const char *details = smbtree::getPara(pc, user, passwd);
jstring jDetails = env->NewStringUTF(details);
return jDetails;
}
Android.mk文件是GNU Makefile的一小部分,它用來對Android程序進行編譯,Android.mk中的變量都是全局的,解析過程會被定義。
一個Android.mk文件能夠編譯多個模塊,模塊包括:APK程序、JAVA庫、C\C++應用程序、C\C++靜態庫、C\C++共享庫。
簡單實例以下:
[html] view plaincopy
LOCAL_PATH := $(call my-dir) #表示是當前文件的路徑
include $(CLEAR_VARS) #指定讓GNU MAKEFILE該腳本爲你清除許多 LOCAL_XXX 變量
LOCAL_MODULE:= helloworld #標識你在 Android.mk 文件中描述的每一個模塊
MY_SOURCES := foo.c #自定義變量
ifneq ($(MY_CONFIG_BAR),)
MY_SOURCES += bar.c
endif
LOCAL_SRC_FILES += $(MY_SOURCES) #包含將要編譯打包進模塊中的 C 或 C++源代碼文件
include $(BUILD_SHARED_LIBRARY) #根據LOCAL_XXX系列變量中的值,來編譯生成共享庫(動態連接庫)
GNU Make系統變量
變量 | 描述 |
CLEAR_VARS | 指 向一個編譯腳本,幾乎全部未定義的 LOCAL_XXX 變量都在"Module-description"節中列出。必須在開始一個新模塊以前包含這個腳本:include$(CLEAR_VARS),用於重 置除LOCAL_PATH變量外的,全部LOCAL_XXX系列變量。 |
BUILD_SHARED_LIBRARY | 指向編譯腳本,根據全部的在 LOCAL_XXX 變量把列出的源代碼文件編譯成一個共享庫。 |
BUILD_STATIC_LIBRARY | 一個 BUILD_SHARED_LIBRARY 變量用於編譯一個靜態庫。靜態庫不會複製到的APK包中,可是可以用於編譯共享庫。 |
TARGET_ARCH | 目標 CPU平臺的名字, 和 android 開放源碼中指定的那樣。若是是arm,表示要生成 ARM 兼容的指令,與 CPU架構的修訂版無關。 |
TARGET_PLATFORM | Android.mk 解析的時候,目標 Android 平臺的名字.詳情可參考/development/ndk/docs/stable- apis.txt. |
TARGET_ARCH_ABI | 支持目標平臺 |
TARGET_ABI | 目標平臺和 ABI 的組合,它事實上被定義成$(TARGET_PLATFORM)-$(TARGET_ARCH_ABI) ,在想要在真實的設備中針對一個特別的目標系統進行測試時,會有用。在默認的狀況下,它會是'android-3-arm'。 |
模塊描述變量
變量 | 描述 |
LOCAL_PATH | 這個變量用於給出當前文件的路徑。必須在 Android.mk 的開頭定義,能夠這樣使用:LOCAL_PATH := $(call my-dir) 這個變量不會被$(CLEAR_VARS)清除,所以每 個 Android.mk 只須要定義一次(即便在一個文件中定義了幾個模塊的狀況下)。 |
LOCAL_MODULE | 這 是模塊的名字,它必須是惟一的,並且不能包含空格。必須在包含任一的$(BUILD_XXXX)腳本以前定義它。模塊的名字決定了生成文件的名字。例如, 若是一個一個共享庫模塊的名字是,那麼生成文件的名字就是 lib.so。可是,在的 NDK 生成文件中(或者 Android.mk 或者 Application.mk),應該只涉及(引用)有正常名字的其餘模塊。 |
LOCAL_SRC_FILES | 這是要編譯的源代碼文件列表。只要列出要傳遞給編譯器的文件,由於編譯系統自動計算依賴。注意源代碼文件名稱都是相對於 LOCAL_PATH的,你可使用路徑部分。 |
LOCAL_CPP_EXTENSION | 這是一個可選變量, 用來指定C++代碼文件的擴展名,默認是'.cpp',可是能夠改變它。 |
LOCAL_C_INCLUDES | 可選變量,表示頭文件的搜索路徑。 |
LOCAL_CFLAGS | 可選的編譯器選項,在編譯 C 代碼文件的時候使用。 |
LOCAL_CXXFLAGS | 與 LOCAL_CFLAGS同理,針對 C++源文件。 |
LOCAL_CPPFLAGS | 與 LOCAL_CFLAGS同理,可是對 C 和 C++ source files都適用。 |
LOCAL_STATIC_LIBRARIES | 表示該模塊須要使用哪些靜態庫,以便在編譯時進行連接。 |
LOCAL_SHARED_LIBRARIES | 表示模塊在運行時要依賴的共享庫(動態庫),在連接時就須要,以便在生成文件時嵌入其相應的信息。注意:它不會附加列出的模塊到編譯圖,也就是仍然須要在Application.mk 中把它們添加到程序要求的模塊中。 |
LOCAL_LDLIBS | 編譯模塊時要使用的附加的連接器選項。這對於使用‘-l’前綴傳遞指定庫的名字是有用的。 |
LOCAL_ALLOW_UNDEFINED_SYMBOLS | 默認狀況下, 在試圖編譯一個共享庫時,任何未定義的引用將致使一個「未定義的符號」錯誤。 |
LOCAL_ARM_MODE | 默認狀況下, arm目標二進制會以 thumb 的形式生成(16 位),你能夠經過設置這個變量爲 arm若是你但願你的 module 是以 32 位指令的形式。 |
LOCAL_MODULE_PATH 和 LOCAL_UNSTRIPPED_PATH | 在 Android.mk 文件中, 還能夠用LOCAL_MODULE_PATH 和LOCAL_UNSTRIPPED_PATH指定最後的目標安裝路徑. 不一樣的文件系統路徑用如下的宏進行選擇: TARGET_ROOT_OUT:表示根文件系統。 TARGET_OUT:表示 system文件系統。 TARGET_OUT_DATA:表示 data文件系統。 用法如:LOCAL_MODULE_PATH :=$(TARGET_ROOT_OUT) 至於LOCAL_MODULE_PATH 和LOCAL_UNSTRIPPED_PATH的區別,暫時還不清楚。 |
GNU Make 功能宏
變量 | 描述 |
my-dir | 返回當前 Android.mk 所在的目錄的路徑,相對於 NDK 編譯系統的頂層。 |
all-subdir-makefiles | 返回一個位於當前'my-dir'路徑的子目錄中的全部Android.mk的列表。 |
this-makefile | 返回當前Makefile 的路徑(即這個函數調用的地方) |
parent-makefile | 返回調用樹中父 Makefile 路徑。即包含當前Makefile的Makefile 路徑。 |
grand-parent-makefile | 返回調用樹中父Makefile的父Makefile的路徑 |
範例:
二、
編譯一個簡單的APK
[html] view plaincopy
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Build all java files in the java subdirectory
LOCAL_SRC_FILES := $(call all-subdir-java-files)
# Name of the APK to build
LOCAL_PACKAGE_NAME := LocalPackage
# Tell it to build an APK
include $(BUILD_PACKAGE)
編譯一個依賴靜態.jar文件的APK
[html] view plaincopy
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# List of static libraries to include in the package
LOCAL_STATIC_JAVA_LIBRARIES := static-library
# Build all java files in the java subdirectory
LOCAL_SRC_FILES := $(call all-subdir-java-files)
# Name of the APK to build
LOCAL_PACKAGE_NAME := LocalPackage
# Tell it to build an APK
include $(BUILD_PACKAGE)
注:LOCAL_STATIC_JAVA_LIBRARIES 後面應是你的APK程序所須要的JAVA庫的JAR文件名。
編譯一個須要platform key簽名的APK
[html] view plaincopy
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Build all java files in the java subdirectory
LOCAL_SRC_FILES := $(call all-subdir-java-files)
# Name of the APK to build
LOCAL_PACKAGE_NAME := LocalPackage
LOCAL_CERTIFICATE := platform
# Tell it to build an APK
include $(BUILD_PACKAGE)
注:LOCAL_CERTIFICATE 後面應該是簽名文件的文件名
編譯一個須要特殊vendor key簽名的APK
[html] view plaincopy
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Build all java files in the java subdirectory
LOCAL_SRC_FILES := $(call all-subdir-java-files)
# Name of the APK to build
LOCAL_PACKAGE_NAME := LocalPackage
LOCAL_CERTIFICATE := vendor/example/certs/app
# Tell it to build an APK
include $(BUILD_PACKAGE)
裝載一個普通的第三方APK
[html] view plaincopy
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Module name should match apk name to be installed.
LOCAL_MODULE := LocalModuleName
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := platform
include $(BUILD_PREBUILT)
裝載須要.so(動態庫)的第三方apk
[html] view plaincopy
LOCAL_PATH := $(my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := baiduinput_android_v1.1_1000e
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := platform
include $(BUILD_PREBUILT)
#################################################################
####### copy the library to /system/lib #########################
#################################################################
include $(CLEAR_VARS)
LOCAL_MODULE := libinputcore.so
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)
LOCAL_SRC_FILES := lib/$(LOCAL_MODULE)
OVERRIDE_BUILD_MODULE_PATH := $(TARGET_OUT_INTERMEDIATE_LIBRARIES)
include $(BUILD_PREBUILT)
編譯一個靜態java庫
[html] view plaincopy
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Build all java files in the java subdirectory
LOCAL_SRC_FILES := $(call all-subdir-java-files)
# Any libraries that this library depends on
LOCAL_JAVA_LIBRARIES := android.test.runner
# The name of the jar file to create
LOCAL_MODULE := sample
# Build a static jar file.
include $(BUILD_STATIC_JAVA_LIBRARY)
注:LOCAL_JAVA_LIBRARIES表示生成的java庫的jar文件名。
編譯C/C++應用程序模板
[html] view plaincopy
LOCAL_PATH := $(call my-dir)
#include $(CLEAR_VARS)
LOCAL_SRC_FILES := main.c
LOCAL_MODULE := test_exe
#LOCAL_C_INCLUDES :=
#LOCAL_STATIC_LIBRARIES :=
#LOCAL_SHARED_LIBRARIES :=
include $(BUILD_EXECUTABLE)
注:‘:=’是賦值的意思,'+='是追加的意思,‘$’表示引用某變量的值
LOCAL_SRC_FILES中加入源文件路徑,LOCAL_C_INCLUDES中加入須要的頭文件搜索路徑
LOCAL_STATIC_LIBRARIES 加入所須要連接的靜態庫(*.a)的名稱,
LOCAL_SHARED_LIBRARIES 中加入所須要連接的動態庫(*.so)的名稱,
LOCAL_MODULE表示模塊最終的名稱,BUILD_EXECUTABLE 表示以一個可執行程序的方式進行編譯。
(4)編譯C\C++靜態庫
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
helloworld.c
LOCAL_MODULE:= libtest_static
#LOCAL_C_INCLUDES :=
#LOCAL_STATIC_LIBRARIES :=
#LOCAL_SHARED_LIBRARIES :=
include $(BUILD_STATIC_LIBRARY)
和上面類似,BUILD_STATIC_LIBRARY 表示編譯一個靜態庫。
編譯C\C++動態庫的模板
[html] view plaincopy
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := helloworld.c
LOCAL_MODULE := libtest_shared
TARGET_PRELINK_MODULES := false
#LOCAL_C_INCLUDES :=
#LOCAL_STATIC_LIBRARIES :=
#LOCAL_SHARED_LIBRARIES :=
include $(BUILD_SHARED_LIBRARY)
和上面類似,BUILD_SHARED_LIBRARY 表示編譯一個共享庫。
以上三者的生成結果分別在以下目錄中,generic 依具體 target 會變:
out/target/product/generic/obj/APPS
out/target/product/generic/obj/JAVA_LIBRARIES
out/target/product/generic/obj/EXECUTABLE
out/target/product/generic/obj/STATIC_LIBRARY
out/target/product/generic/obj/SHARED_LIBRARY
每一個模塊的目標文件夾分別爲:
1)APK程序:XXX_intermediates
2)JAVA庫程序:XXX_intermediates
這裏的XXX
3)C\C++可執行程序:XXX_intermediates
4)C\C++靜態庫: XXX_static_intermediates
5)C\C++動態庫: XXX_shared_intermediates
實例:
[html] view plaincopy
LOCAL_PATH := $(call my-dir) #項目地址
include $(CLEAR_VARS) #清除變量
LOCAL_MODULE := libvlcjni #庫
#源文件
LOCAL_SRC_FILES := libvlcjni.c libvlcjni-util.c libvlcjni-track.c libvlcjni-medialist.c aout.c vout.c libvlcjni-equalizer.c native_crash_handler.c
LOCAL_SRC_FILES += thumbnailer.c pthread-condattr.c pthread-rwlocks.c pthread-once.c eventfd.c sem.c
LOCAL_SRC_FILES += pipe2.c
LOCAL_SRC_FILES += wchar/wcpcpy.c
LOCAL_SRC_FILES += wchar/wcpncpy.c
LOCAL_SRC_FILES += wchar/wcscasecmp.c
LOCAL_SRC_FILES += wchar/wcscat.c
LOCAL_SRC_FILES += wchar/wcschr.c
LOCAL_SRC_FILES += wchar/wcscmp.c
LOCAL_SRC_FILES += wchar/wcscoll.c
LOCAL_SRC_FILES += wchar/wcscpy.c
LOCAL_SRC_FILES += wchar/wcscspn.c
LOCAL_SRC_FILES += wchar/wcsdup.c
LOCAL_SRC_FILES += wchar/wcslcat.c
LOCAL_SRC_FILES += wchar/wcslcpy.c
LOCAL_SRC_FILES += wchar/wcslen.c
LOCAL_SRC_FILES += wchar/wcsncasecmp.c
LOCAL_SRC_FILES += wchar/wcsncat.c
LOCAL_SRC_FILES += wchar/wcsncmp.c
LOCAL_SRC_FILES += wchar/wcsncpy.c
LOCAL_SRC_FILES += wchar/wcsnlen.c
LOCAL_SRC_FILES += wchar/wcspbrk.c
LOCAL_SRC_FILES += wchar/wcsrchr.c
LOCAL_SRC_FILES += wchar/wcsspn.c
LOCAL_SRC_FILES += wchar/wcsstr.c
LOCAL_SRC_FILES += wchar/wcstok.c
LOCAL_SRC_FILES += wchar/wcswidth.c
LOCAL_SRC_FILES += wchar/wcsxfrm.c
LOCAL_SRC_FILES += wchar/wmemchr.c
LOCAL_SRC_FILES += wchar/wmemcmp.c
LOCAL_SRC_FILES += wchar/wmemcpy.c
LOCAL_SRC_FILES += wchar/wmemmove.c
LOCAL_SRC_FILES += wchar/wmemset.c
LOCAL_C_INCLUDES := $(VLC_SRC_DIR)/include #包含頭
ARCH=$(ANDROID_ABI) #變量 平臺
CPP_STATIC=$(ANDROID_NDK)/sources/cxx-stl/gnu-libstdc++$(CXXSTL)/libs/$(ARCH)/libgnustl_static.a #應用靜態庫
LOCAL_CFLAGS := -std=gnu99 #編譯器標識
ifeq ($(ARCH), armeabi)
LOCAL_CFLAGS += -DHAVE_ARMEABI
# Needed by ARMv6 Thumb1 (the System Control coprocessor/CP15 is mandatory on ARMv6)
# On newer ARM architectures we can use Thumb2
LOCAL_ARM_MODE := arm
endif
ifeq ($(ARCH), armeabi-v7a)
LOCAL_CFLAGS += -DHAVE_ARMEABI_V7A
endif
LOCAL_LDLIBS := -L$(VLC_CONTRIB)/lib \ #使用本地庫
$(VLC_MODULES) \
$(VLC_BUILD_DIR)/lib/.libs/libvlc.a \
$(VLC_BUILD_DIR)/src/.libs/libvlccore.a \
$(VLC_BUILD_DIR)/compat/.libs/libcompat.a \
-ldl -lz -lm -llog \
-ldvbpsi -lebml -lmatroska -ltag \
-logg -lFLAC -ltheora -lvorbis \
-lmpeg2 -la52 \
-lavformat -lavcodec -lswscale -lavutil -lpostproc -lgsm -lopenjpeg \
-lliveMedia -lUsageEnvironment -lBasicUsageEnvironment -lgroupsock \
-lspeex -lspeexdsp \
-lxml2 -lpng -lgnutls -lgcrypt -lgpg-error \
-lnettle -lhogweed -lgmp \
-lfreetype -liconv -lass -lfribidi -lopus \
-lEGL -lGLESv2 -ljpeg \
-ldvdnav -ldvdread -ldvdcss \
$(CPP_STATIC)
include $(BUILD_SHARED_LIBRARY) #編譯成動態庫
include $(CLEAR_VARS) #清除變量
LOCAL_MODULE := libiomx-gingerbread
LOCAL_SRC_FILES := ../$(VLC_SRC_DIR)/modules/codec/omxil/iomx.cpp
LOCAL_C_INCLUDES := $(VLC_SRC_DIR)/modules/codec/omxil $(ANDROID_SYS_HEADERS_GINGERBREAD)/frameworks/base/include $(ANDROID_SYS_HEADERS_GINGERBREAD)/system/core/include
LOCAL_CFLAGS := -Wno-psabi
LOCAL_LDLIBS := -L$(ANDROID_LIBS) -lgcc -lstagefright -lmedia -lutils -lbinder
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libiomx-hc
LOCAL_SRC_FILES := ../$(VLC_SRC_DIR)/modules/codec/omxil/iomx.cpp
LOCAL_C_INCLUDES := $(VLC_SRC_DIR)/modules/codec/omxil $(ANDROID_SYS_HEADERS_HC)/frameworks/base/include $(ANDROID_SYS_HEADERS_HC)/frameworks/base/native/include $(ANDROID_SYS_HEADERS_HC)/system/core/include $(ANDROID_SYS_HEADERS_HC)/hardware/libhardware/include
LOCAL_CFLAGS := -Wno-psabi
LOCAL_LDLIBS := -L$(ANDROID_LIBS) -lgcc -lstagefright -lmedia -lutils -lbinder
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libiomx-ics
LOCAL_SRC_FILES := ../$(VLC_SRC_DIR)/modules/codec/omxil/iomx.cpp
LOCAL_C_INCLUDES := $(VLC_SRC_DIR)/modules/codec/omxil $(ANDROID_SYS_HEADERS_ICS)/frameworks/base/include $(ANDROID_SYS_HEADERS_ICS)/frameworks/base/native/include $(ANDROID_SYS_HEADERS_ICS)/system/core/include $(ANDROID_SYS_HEADERS_ICS)/hardware/libhardware/include
LOCAL_CFLAGS := -Wno-psabi
LOCAL_LDLIBS := -L$(ANDROID_LIBS) -lgcc -lstagefright -lmedia -lutils -lbinder
include $(BUILD_SHARED_LIBRARY)
Application.mk目的是描述在你的應用程序中所須要的模塊(即靜態庫或動態庫)。
變量 | 描述 |
APP_PROJECT_PATH | 這個變量是強制性的,而且會給出應用程序工程的根目錄的一個絕對路徑。 |
APP_MODULES | 這 個變量是可選的,若是沒有定義,NDK將由在Android.mk中聲明的默認的模塊編譯,而且包含全部的子文件(makefile文件)若是 APP_MODULES定義了,它不準是一個空格分隔的模塊列表,這個模塊名字被定義在Android.mk文件中的LOCAL_MODULE中。 |
APP_OPTIM | 這個變量是可選的,用來義「release」或"debug"。在編譯您的應用程序模塊的時候,能夠用來改變優先級。 |
APP_CFLAGS | 當編譯模塊中有任何C文件或者C++文件的時候,C編譯器的信號就會被髮出。 |
APP_CXXFLAGS | APP_CPPFLAGS的別名,已經考慮在將在將來的版本中廢除了 |
APP_CPPFLAGS | 當編譯的只有C++源文件的時候,能夠經過這個C++編譯器來設置 |
APP_BUILD_SCRIPT | 默認狀況下,NDK編譯系統會在$(APP_PROJECT_PATH)/jni目錄下尋找名爲Android.mk文件: $(APP_PROJECT_PATH)/jni/Android.mk |
APP_ABI | 默認狀況下,NDK的編譯系統回味"armeabi"ABI生成機器代碼。 |
APP_STL | 默認狀況下,NDK的編譯系統爲最小的C++運行時庫(/system/lib/libstdc++.so)提供C++頭文件。然而,NDK的C++的實現,可讓你使用或着連接在本身的應用程序中。 例如: APP_STL := stlport_static --> static STLport library APP_STL := stlport_shared --> shared STLport library APP_STL := system --> default C++ runtime library |
實例:
[html] view plaincopy
APP_OPTIM := release //調試版仍是發行版
APP_PLATFORM := android-8 //平臺
APP_STL := gnustl_static //C++運行時庫
APP_CPPFLAGS += -frtti //編譯標識
APP_CPPFLAGS += -fexceptions //編譯標識 異常
APP_CPPFLAGS += -DANDROID //編譯標識
APP_MODULES := test //靜態模塊
[html] view plaincopy
[html] view plaincopy
JAVA 編程中的內存泄漏,從泄漏的內存位置角度能夠分爲兩種:JVM 中 Java Heap 的內存泄漏;JVM 內存中 native memory 的內存泄漏。
Java 對象存儲在 JVM 進程空間中的 Java Heap 中,Java Heap 能夠在 JVM 運行過程當中動態變化。若是 Java 對象愈來愈多,佔據 Java Heap 的空間也愈來愈大,JVM 會在運行時擴充 Java Heap 的容量。若是 Java Heap 容量擴充到上限,而且在 GC 後仍然沒有足夠空間分配新的 Java 對象,便會拋出 out of memory 異常,致使 JVM 進程崩潰。
Java Heap 中 out of memory 異常的出現有兩種緣由①程序過於龐大,導致過多 Java 對象的同時存在;②程序編寫的錯誤致使 Java Heap 內存泄漏。
從操做系統角度看,JVM 在運行時和其它進程沒有本質區別。在系統級別上,它們具備一樣的調度機制,一樣的內存分配方式,一樣的內存格局。
JVM 進程空間中,Java Heap 之外的內存空間稱爲 JVM 的 native memory。進程的不少資源都是存儲在 JVM 的 native memory 中,例如載入的代碼映像,線程的堆棧,線程的管理控制塊,JVM 的靜態數據、全局數據等等。也包括 JNI 程序中 native code 分配到的資源。
在 JVM 運行中,多數進程資源從 native memory 中動態分配。當愈來愈多的資源在 native memory 中分配,佔據愈來愈多 native memory 空間而且達到 native memory 上限時,JVM 會拋出異常,使 JVM 進程異常退出。而此時 Java Heap 每每尚未達到上限。
多種緣由可能致使 JVM 的 native memory 內存泄漏。
例如:
JVM 在運行中過多的線程被建立,而且在同時運行。
JVM 爲線程分配的資源就可能耗盡 native memory 的容量。
JNI 編程錯誤也可能致使 native memory 的內存泄漏。
JNI 編程首先是一門具體的編程語言,或者 C 語言,或者 C++,或者彙編,或者其它 native 的編程語言。每門編程語言環境都實現了自身的內存管理機制。所以,JNI 程序開發者要遵循 native 語言自己的內存管理機制,避免形成內存泄漏。以 C 語言爲例,當用 malloc() 在進程堆中動態分配內存時,JNI 程序在使用完後,應當調用 free() 將內存釋放。總之,全部在 native 語言編程中應當注意的內存泄漏規則,在 JNI 編程中依然適應。
Native 語言自己引入的內存泄漏會形成 native memory 的內存,嚴重狀況下會形成 native memory 的 out of memory。
JNI 編程還要同時遵循 JNI 的規範標準,JVM 附加了 JNI 編程特有的內存管理機制。
JNI 中的 Local Reference 只在 native method 執行時存在,當 native method 執行完後自動失效。這種自動失效,使得對 Local Reference 的使用相對簡單,native method 執行完後,它們所引用的 Java 對象的 reference count 會相應減 1。不會形成 Java Heap 中 Java 對象的內存泄漏。
而 Global Reference 對 Java 對象的引用一直有效,所以它們引用的 Java 對象會一直存在 Java Heap 中。程序員在使用 Global Reference 時,須要仔細維護對 Global Reference 的使用。若是必定要使用 Global Reference,務必確保在不用的時候刪除。就像在 C 語言中,調用 malloc() 動態分配一塊內存以後,調用 free() 釋放同樣。不然,Global Reference 引用的 Java 對象將永遠停留在 Java Heap 中,形成 Java Heap 的內存泄漏。
Local Reference 在 native method 執行完成後,會自動被釋放,彷佛不會形成任何的內存泄漏。但這是錯誤的。
泄漏實例1:建立大量的 JNI Local Reference
[html] view plaincopy
Java 代碼部分
class TestLocalReference {
private native void nativeMethod(int i);
public static void main(String args[]) {
TestLocalReference c = new TestLocalReference();
//call the jni native method
c.nativeMethod(1000000);
}
static {
//load the jni library
System.loadLibrary("StaticMethodCall");
}
}
JNI 代碼,nativeMethod(int i) 的 C 語言實現
#include<stdio.h>
#include<jni.h>
#include"TestLocalReference.h"
JNIEXPORT void JNICALL Java_TestLocalReference_nativeMethod
(JNIEnv * env, jobject obj, jint count)
{
jint i = 0;
jstring str;
for(; i<count; i++)
str = (*env)->NewStringUTF(env, "0");
}
運行結果
JVMCI161: FATAL ERROR in native method: Out of memory when expanding
local ref table beyond capacity
at TestLocalReference.nativeMethod(Native Method)
at TestLocalReference.main(TestLocalReference.java:9)
泄漏實例2:創建一個 String 對象,返回給調用函數。
[html] view plaincopy
JNI 代碼,nativeMethod(int i) 的 C 語言實現
#include<stdio.h>
#include<jni.h>
#include"TestLocalReference.h"
jstring CreateStringUTF(JNIEnv * env)
{
return (*env)->NewStringUTF(env, "0");
}
JNIEXPORT void JNICALL Java_TestLocalReference_nativeMethod
(JNIEnv * env, jobject obj, jint count)
{
jint i = 0;
for(; i<count; i++)
{
str = CreateStringUTF(env);
}
}
運行結果
JVMCI161: FATAL ERROR in native method: Out of memory when expanding local ref
table beyond capacity
at TestLocalReference.nativeMethod(Native Method)
at TestLocalReference.main(TestLocalReference.java:9)
編譯問題:SLES/OpenSLES.h: No such file or directory
解決方法:ndk-build TARGET_PLATFORM=android-9
編譯斷點問題:有沒有好用的斷點工具
解決方法:visualGDB 神器