*** 說明:本文不代表博主觀點戒悠,均是由以下資料整理的讀書筆記烦衣。 ***
【參考資料】
1嚷闭、向您的Android Studio項目添加C/C++代碼
2纵寝、Google開發(fā)者文檔 -- 添加C++代碼到現(xiàn)有Android Studio項目中
3论寨、JNI Tips 英文原版
4、JNI Tips 中文
5爽茴、極客學(xué)院 JNI/NDK 開發(fā)指南
6葬凳、極客學(xué)院 深入理解 JNI
7、使用CMake構(gòu)建JNI環(huán)境
8室奏、使用C和C++的區(qū)別
9火焰、Google官方 NDK 文檔
10、極客學(xué)院 NDK開發(fā)課程
11胧沫、ndk-build 構(gòu)建 JNI 環(huán)境
12昌简、開發(fā)自己的NDK程序
13、JNI/NDK開發(fā)教程
14绒怨、JNI層修改參數(shù)值
15纯赎、JNI引用和垃圾回收
16、《Android高級進階》-- 顧浩鑫
17南蹂、《Android C++ 高級編程 -- 使用 NDK》 -- Onur Cinar
一犬金、概述
1.1 JNI
Java Native Interface(Java 本地接口),它是為了方便Java調(diào)用C、C++等本地代碼所封裝的一層接口佑附。
1.2 NDK
Native Development Kit(本地開發(fā)工具包)樊诺,通過NDK可以在Android中更加方便的通過JNI來訪問本地代碼;并提供眾多平臺庫音同,讓您可以管理原生 Activity 和訪問物理設(shè)備組件词爬,例如傳感器和觸摸輸入,比如百度開放平臺提供的定位服務(wù)权均、搜索服務(wù)顿膨、LBS 服務(wù)、推送服務(wù)的Android SDK叽赊,除了Java接口的jar包之外恋沃,還有一個.so文件,這個.so就是實現(xiàn)了Java層定義的native接口的動態(tài)庫必指。NDK 能自動將 so 和 Java 應(yīng)用一起打包成 apk囊咏,它集成了交叉編譯器,并提供了相應(yīng)的 mk 文件隔離 CPU塔橡、平臺梅割、ABI 等差異,開發(fā)人員只需要簡單修改 mk 文件(指出“哪些文件需要編譯”葛家、“編譯特性要求”等)户辞,就可以創(chuàng)建出 so。
1.3 JNI 開發(fā)的平臺差異
開發(fā) JNI 程序會受到系統(tǒng)環(huán)境的限制癞谒,因為用 C/C++ 語言寫出來的代碼或模塊底燎,編譯過程當(dāng)中要依賴當(dāng)前操作系統(tǒng)環(huán)境所提供的一些庫函數(shù),并和本地庫鏈接在一起弹砚。而且編譯后生成的二進制代碼只能在本地操作系統(tǒng)環(huán)境下運行双仍,因為不同的操作系統(tǒng)環(huán)境,有自己的本地庫和 CPU 指令集迅栅,而且各個平臺對標(biāo)準(zhǔn) C/C++ 的規(guī)范和標(biāo)準(zhǔn)庫函數(shù)實現(xiàn)方式也有所區(qū)別殊校。這就造成使用了 JNI 接口的 JAVA 程序,不再像以前那樣自由的跨平臺读存。如果要實現(xiàn)跨平臺为流,就必須將本地代碼在不同的操作系統(tǒng)平臺下編譯出相應(yīng)的動態(tài)庫。
二让簿、Android中C/C++項目的兩種構(gòu)建方式
2.1 CMake
這是Android Studio的默認(rèn)方式敬察。通過CMakeLists.txt和gradle來構(gòu)建原生代碼。
2.2 ndk-build
可以將現(xiàn)有的ndk-build庫導(dǎo)入到Android Studio項目中尔当。
PS:如果是創(chuàng)建新的C++庫莲祸,建議使用CMake蹂安。
2.3 調(diào)試
Android Studio 可以使用 LLDB 工具來調(diào)試C++代碼。
2.4 使用Android Studio創(chuàng)建支持C/C++的CMake項目
** 使用2.2或以上版本的Android Studio可以創(chuàng)建CMake項目锐帜,與創(chuàng)建普通的AS項目類似田盈,需要以下幾個額外步驟: **
1、在向?qū)У?Configure your new project 部分缴阎,選中 Include C++ Support 復(fù)選框允瞧。
2、在向?qū)У?Customize C++ Support 部分蛮拔,您可以使用下列選項自定義項目:
(1)C++ Standard:使用下拉列表選擇您希望使用哪種 C++ 標(biāo)準(zhǔn)述暂。選擇 Toolchain Default 會使用默認(rèn)的 CMake 設(shè)置。
(2)Exceptions Support:如果您希望啟用對 C++ 異常處理的支持建炫,請選中此復(fù)選框畦韭。如果啟用此復(fù)選框,Android Studio 會將 -fexceptions 標(biāo)志添加到模塊級 build.gradle 文件的 cppFlags 中肛跌,Gradle 會將其傳遞到 CMake艺配。
(3)Runtime Type Information Support:如果您希望支持 RTTI,請選中此復(fù)選框惋砂。如果啟用此復(fù)選框妒挎,Android Studio 會將 -frtti 標(biāo)志添加到模塊級 build.gradle 文件的 cppFlags 中,Gradle 會將其傳遞到 CMake西饵。
** 創(chuàng)建好的新項目,會比普通的AS項目多了 cpp 組和 External Build Files 組: **
1鳞芙、在 cpp 組中眷柔,可以找到屬于項目的所有原生源文件、標(biāo)頭和預(yù)構(gòu)建庫原朝。對于新項目驯嘱,Android Studio 會創(chuàng)建一個示例 C++ 源文件 native-lib.cpp。默認(rèn)模板提供了一個 C++ 函數(shù) stringFromJNI()喳坠,以返回字符串“Hello from C++”鞠评。
2、在 External Build Files 組中壕鹉,可以找到 CMake 構(gòu)建腳本剃幌。與 build.gradle 文件指示 Gradle 如何構(gòu)建應(yīng)用一樣,CMake 需要一個構(gòu)建腳本來了解如何構(gòu)建原生庫晾浴。對于新項目负乡,Android Studio 會創(chuàng)建一個 CMake 構(gòu)建腳本 CMakeLists.txt,并將其置于模塊的根目錄中脊凰。
** 構(gòu)建和運行的過程 **
1抖棘、Gradle 調(diào)用外部構(gòu)建腳本 CMakeLists.txt。
2、CMake 按照構(gòu)建腳本中的命令將 C++ 源文件 native-lib.cpp 編譯到共享的對象庫中切省,并命名為 libnative-lib.so最岗,Gradle 隨后會將其打包到 APK 中。
3朝捆、運行時仑性,應(yīng)用的 MainActivity 會使用 System.loadLibrary() 加載原生庫,加載成功之后右蹦,應(yīng)用就可以使用庫的原生函數(shù) stringFromJNI() 了诊杆。
4、MainActivity.onCreate() 調(diào)用 stringFromJNI()何陆,這將返回“Hello from C++”晨汹。
2.5 向現(xiàn)有項目添加 C/C++ 代碼
1、創(chuàng)建新的 C/C++ 源文件或提供現(xiàn)有的原生庫贷盲,并將其添加到的 Android Studio 項目中淘这。
2、創(chuàng)建 CMake 構(gòu)建腳本或 ndk-build 構(gòu)建腳本巩剖,將原生源代碼構(gòu)建到庫中铝穷。
- ** CMake **
CMake 構(gòu)建腳本是一個純文本文件,必須將其命名為 CMakeLists.txt佳魔,在 gradle 的 externalNativeBuild 中指向其路徑曙聂。下面是幾個常見的CMake命令:(詳見:https://developer.android.google.cn/studio/projects/add-native-code.html#existing-project)
(1)cmake_minimum_required:指定最低版本號。
(2)add_library:包含三個參數(shù)鞠鲜,分別是共享庫的名稱宁脊,設(shè)置為共享庫或靜態(tài)庫,入口文件的路徑贤姆;在 Java 代碼中使用 System.loadLibrary(“native-lib”) 來加載該共享庫榆苞。可以使用多個 add_library 命令關(guān)聯(lián)多個共享庫霞捡。
(3)find_library坐漏、target_link_libraries:這兩個命令可以將已有的的 NDK 庫關(guān)聯(lián)到 CMake中,默認(rèn)實例中添加了原生的 log 庫碧信。
(4)例子:NDK 還以源代碼的形式包含一些庫赊琳,可以使用 add_library() 命令,將源代碼編譯到原生庫中音婶。要提供本地 NDK 庫的路徑慨畸,使用 ANDROID_NDK 路徑變量,Android Studio 會自動為你定義此變量衣式。以下命令可以指示 CMake 構(gòu)建 android_native_app_glue.c寸士,后者會將 NativeActivity 生命周期事件和觸摸輸入置于靜態(tài)庫中并將靜態(tài)庫關(guān)聯(lián)到 native-lib檐什。
add_library( app-glue
STATIC
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )
# You need to link static libraries against your shared native library.
target_link_libraries( native-lib app-glue ${log-lib} )
- ** ndk-build **
提供一個指向您的 Android.mk 文件的路徑,將 Gradle 關(guān)聯(lián)到原生庫弱卡。使用ndk-build構(gòu)建請參考這兩篇:
NDK筆記(二)-在Android Studio中使用ndk-build
JNI/NDK開發(fā)指南 - 開發(fā)自己的 NDK 程序
下面是Android.mk和pplication.mk文件的語法說明:
如果要使用stl乃正,則需要建立一個Application.mk,里面寫上:
APP_STL := stlport_shared
APP_STL := stlport_static
3婶博、提供一個指向 CMake 或 ndk-build 腳本文件的路徑瓮具,將 Gradle 關(guān)聯(lián)到原生庫。Gradle 使用構(gòu)建腳本將源代碼導(dǎo)入 Android Studio 項目并將原生庫(SO 文件)打包到 APK 中凡人。
三名党、JNI頭文件解釋
看這個最簡單的例子:
extern "C"
JNIEXPORT jstring JNICALL
Java_com_scu_miomin_learncmake_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++ " + getStringValue();
return env->NewStringUTF(hello.c_str());
}
3.1 JNIEXPORT 和 JNICALL
JNIEXPORT 和 JNICALL 是定義在跨平臺頭文件目錄(Mac os x系統(tǒng)下的目錄名為 darwin,在 Windows 下目錄名為 win32挠轴,linux 下目錄名為 linux)传睹,用于標(biāo)識函數(shù)用途的兩個宏。從 Linux 下的jni_md.h頭文件可以看出來岸晦,JNIEXPORT 和 JNICALL 是一個空定義欧啤,所以在 Linux 下 JNI 函數(shù)聲明可以省略這兩個宏。
3.2 JNIEnv*
是定義任意 native 函數(shù)的第一個參數(shù)启上,指向 JVM 函數(shù)表的指針邢隧,函數(shù)表中的每一個入口指向一個 JNI 函數(shù),每個函數(shù)用于訪問 JVM 中特定的數(shù)據(jù)結(jié)構(gòu)冈在。
3.3 jobject
調(diào)用 Java 中 native 方法的實例或 Class 對象倒慧,如果這個 native 方法是實例方法,則該參數(shù)是 jobject讥邻,如果是靜態(tài)方法迫靖,則是 jclass
3.4 JavaVM 及 JNIEnv
JNI定義了兩種關(guān)鍵數(shù)據(jù)結(jié)構(gòu),“JavaVM”和“JNIEnv”兴使。它們本質(zhì)上都是指向函數(shù)表指針的指針(在C++版本中,它們被定義為類照激,該類包含一個指向函數(shù)表的指針发魄,以及一系列可以通過這個函數(shù)表間接地訪問對應(yīng)的JNI函數(shù)的成員函數(shù))。JavaVM提供“調(diào)用接口(invocation interface)”函數(shù), 允許你創(chuàng)建和銷毀一個JavaVM俩垃。理論上你可以在一個進程中擁有多個JavaVM對象励幼,但安卓只允許一個。
JNIEnv提供了大部分JNI功能口柳。你定義的所有本地函數(shù)都會接收J(rèn)NIEnv作為第一個參數(shù)苹粟。
JNIEnv是用作線程局部存儲。因此跃闹,你不能在線程間共享一個JNIEnv變量嵌削。如果在一段代碼中沒有其它辦法獲得它的JNIEnv毛好,你可以共享JavaVM對象,使用GetEnv來取得該線程下的JNIEnv(如果該線程有一個JavaVM的話苛秕;見下面的AttachCurrentThread)肌访。
3.5 jclass, jmethodID, jfieldID
如果你想在本地代碼中訪問一個對象的字段(field),你可以像下面這樣做:
- 對于類,使用FindClass獲得類對象的引用
- 對于字段艇劫,使用GetFieldId獲得字段ID
- 使用對應(yīng)的方法(例如GetIntField)獲取字段下面的值
如果性能是你看重的吼驶,那么一旦查找出這些值之后在你的本地代碼中緩存這些結(jié)果是非常有用的。因為每個進程當(dāng)中的JavaVM是存在限制的店煞,存儲這些數(shù)據(jù)到本地靜態(tài)數(shù)據(jù)結(jié)構(gòu)中是非常合理的蟹演。類引用(class reference),字段ID(field ID)以及方法ID(method ID)在類被卸載前都是有效的顷蟀。如果與一個類加載器(ClassLoader)相關(guān)的所有類都能夠被垃圾回收酒请,但是這種情況在安卓上是罕見甚至不可能出現(xiàn),只有這時類才被卸載衩椒。注意雖然jclass是一個類引用蚌父,但是必須要調(diào)用NewGlobalRef保護起來(見后文)。
當(dāng)一個類被加載時如果你想緩存些ID毛萌,而后當(dāng)這個類被卸載后再次載入時能夠自動地更新這些緩存ID苟弛,正確做法是在對應(yīng)的類中添加一段像下面的代碼來初始化這些ID,當(dāng)這個類被初始化時這段代碼將會執(zhí)行一次阁将。當(dāng)這個類被卸載后而后再次載入時膏秫,這段代碼將會再次執(zhí)行:
/*
* 我們在一個類初始化時調(diào)用本地方法來緩存一些字段的偏移信息
* 這個本地方法查找并緩存你感興趣的class/field/method ID
* 失敗時拋出異常
*/
private static native void nativeInit();
static {
nativeInit();
}
四、JNI 與 Java 的數(shù)據(jù)類型映射
在調(diào)用 Java native 方法將實參傳遞給 C/C++ 函數(shù)的時候做盅,會自動將 java 形參的數(shù)據(jù)類型自動轉(zhuǎn)換成 C/C++ 相應(yīng)的數(shù)據(jù)類型缤削,所以我們在寫 JNI 程序的時候,必須要明白它們之間數(shù)據(jù)類型的對應(yīng)關(guān)系吹榴。
4.1 基礎(chǔ)類型
boolean -- jboolean
byte -- jbyte
char -- bchar
short -- jshort
int -- jint
long -- jlong
float -- jfloat
double -- jdouble
4.2 引用類型
JNI 如果使用 C++ 語言編寫的話亭敢,所有引用類型派生自 jobject,使用 C++ 的繼承結(jié)構(gòu)特性图筹,使用相應(yīng)的類型帅刀。如下所示:
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
JNI 把 Java 中的所有對象當(dāng)作一個C指針傳遞到本地方法中,這個指針指向 JVM 中的內(nèi)部數(shù)據(jù)結(jié)構(gòu)远剩,而內(nèi)部的數(shù)據(jù)結(jié)構(gòu)在內(nèi)存中的存儲方式是不可見的扣溺。只能從 JNIEnv 指針指向的函數(shù)表中選擇合適的 JNI 函數(shù)來操作 JVM 中的數(shù)據(jù)結(jié)構(gòu)。
注意:JNI 層拿到的參數(shù)數(shù)據(jù)是以拷貝的形式存在瓜晤,所以修改修改 JNI 中形參的值锥余,不會引起 Java 層實參值的變化。如果一定要修改實參的值痢掠,必須以對象的方式傳遞參數(shù)驱犹,JNI 層對 jobject 參數(shù)進行修改嘲恍,具體實現(xiàn)參考:http://www.cnblogs.com/CCBB/p/3980856.html
五、JNI 字符串處理
一個簡單的例子:
extern "C"
JNIEXPORT jstring JNICALL
Java_com_scu_miomin_learncmake_NativeLib_stringFromJNI(
JNIEnv *env,
jclass /* this */,
jstring j_str_first,
jstring j_str_test) {
jstring j_second = env->NewStringUTF(getStringValue().c_str());
char *c_second = Jstring2CStr(env, j_second);
char *c_str = Jstring2CStr(env, j_str_first);
const char *c_str_test = env->GetStringUTFChars(j_str_test, NULL);
if (c_second == NULL || c_str == NULL || c_str_test == NULL) {
return NULL;
}
strcat(c_str, c_second);
LOGV("j_str_test = %s", c_str_test);
env->ReleaseStringUTFChars(j_str_test, c_str_test);
return env->NewStringUTF(c_str);
}
這段代碼從 Java 傳遞 String 參數(shù)止 JNI 層着绷,JNI 再從 C++ 庫獲得另一個 string蛔钙,在JNI層將這兩個字符串拼接后返回給 Java。
5.1 訪問字符串
在這個例子中荠医,訪問 java.lang.String 對應(yīng)的 JNI 類型 jstring 時吁脱,沒有像訪問基本數(shù)據(jù)類型一樣直接使用,因為它在 Java 是一個引用類型彬向,所以在本地代碼中只能通過 GetStringUTFChars 這樣的 JNI 函數(shù)來訪問字符串的內(nèi)容兼贡。
GetStringUTFChars() 和 Jstring2CStr() 都是用于將 jstring 轉(zhuǎn)換成 char* 類型的函數(shù),前者是系統(tǒng)提供娃胆,后者是自己實現(xiàn)遍希,區(qū)別在于返回值是否為 const。
5.2 異常檢查
調(diào)用完 GetStringUTFChars 之后不要忘記安全檢查里烦,因為 JVM 需要為新誕生的字符串分配內(nèi)存空間凿蒜,當(dāng)內(nèi)存空間不夠分配的時候,會導(dǎo)致調(diào)用失敗胁黑,失敗后 GetStringUTFChars 會返回 NULL废封,并拋出一個OutOfMemoryError 異常。JNI 的異常和 Java 中的異常處理流程是不一樣的丧蘸,Java 遇到異常如果沒有捕獲漂洋,程序會立即停止運行。而 JNI 遇到未決的異常不會改變程序的運行流程力喷,也就是程序會繼續(xù)往下走刽漂,這樣后面針對這個字符串的所有操作都是非常危險的,因此弟孟,我們需要用 return 語句跳過后面的代碼贝咙,并立即結(jié)束當(dāng)前方法。
5.3 釋放字符串
在調(diào)用 GetStringUTFChars 函數(shù)從 JVM 內(nèi)部獲取一個字符串之后拂募,JVM 內(nèi)部會分配一塊新的內(nèi)存颈畸,用于存儲源字符串的拷貝,以便本地代碼訪問和修改没讲。即然有內(nèi)存分配,用完之后馬上釋放是一個編程的好習(xí)慣礁苗。通過調(diào)用ReleaseStringUTFChars 函數(shù)通知 JVM 這塊內(nèi)存已經(jīng)不使用了爬凑,可以清除了。注意:這兩個函數(shù)是配對使用的试伙,用了 GetXXX 就必須調(diào)用 ReleaseXXX嘁信,而且這兩個函數(shù)的命名也有規(guī)律于样,除了前面的 Get 和 Release 之外,后面的都一樣潘靖。
5.4 創(chuàng)建字符串
通過調(diào)用 NewStringUTF 函數(shù)穿剖,會構(gòu)建一個新的 java.lang.String 字符串對象捐凭。這個新創(chuàng)建的字符串會自動轉(zhuǎn)換成 Java 支持的 Unicode 編碼页畦。如果 JVM 不能為構(gòu)造 java.lang.String 分配足夠的內(nèi)存,NewStringUTF 會拋出一個 OutOfMemoryError 異常涮帘,并返回 NULL单寂。一般來說不必檢查它的返回值贬芥,如果NewStringUTF 創(chuàng)建 java.lang.String 失敗,OutOfMemoryError 會在 Java 調(diào)用層中被拋出宣决。如果 NewStringUTF 創(chuàng)建 java.lang.String 成功蘸劈,則返回一個 JNI 引用,這個引用指向新創(chuàng)建的java.lang.String 對象尊沸。
5.5 其它字符串處理函數(shù)
(1)GetStringChars和ReleaseStringChars
這對函數(shù)和 Get/ReleaseStringUTFChars 函數(shù)功能差不多威沫,用于獲取和釋放以 Unicode 格式編碼的字符串。而后者是用于獲取和釋放 UTF-8 編碼的字符串洼专。
(2)GetStringLength
由于 UTF-8 編碼的字符串以'\0'結(jié)尾棒掠,而 Unicode 字符串不是。如果想獲取一個指向 Unicode 編碼的 jstring 字符串長度壶熏,在 JNI 中可通過這個函數(shù)獲取句柠。
(3)GetStringUTFLength
獲取 UTF-8 編碼字符串的長度,也可以通過標(biāo)準(zhǔn) C 函數(shù) strlen 獲取棒假。
(4)GetStringCritical和ReleaseStringCritical
Get/ReleaseStringChars 和 Get/ReleaseStringUTFChars 這對函數(shù)返回的源字符串會后分配內(nèi)存溯职,如果有一個字符串內(nèi)容相當(dāng)大,有 1M 左右帽哑,而且只需要讀取里面的內(nèi)容打印出來谜酒,用這兩對函數(shù)就有些不太合適了。此時用 Get/ReleaseStringCritical 可直接返回源字符串的指針應(yīng)該是一個比較合適的方式妻枕。不過這對函數(shù)有一個很大的限制僻族,在這兩個函數(shù)之間的本地代碼不能調(diào)用任何會讓線程阻塞或等待 JVM 中其它線程的本地函數(shù)或 JNI 函數(shù)。因為通過 GetStringCritical 得到的是一個指向 JVM 內(nèi)部字符串的直接指針屡谐,獲取這個直接指針后會導(dǎo)致暫停 GC 線程述么,當(dāng) GC 被暫停后,如果其它線程觸發(fā) GC 繼續(xù)運行的話愕掏,都會導(dǎo)致阻塞調(diào)用者度秘。所以在 Get/ReleaseStringCritical 這對函數(shù)中間的任何本地代碼都不可以執(zhí)行導(dǎo)致阻塞的調(diào)用或為新對象在 JVM 中分配內(nèi)存,否則饵撑,JVM 有可能死鎖剑梳。另外一定要記住檢查是否因為內(nèi)存溢出而導(dǎo)致它的返回值為 NULL唆貌,因為 JVM 在執(zhí)行 GetStringCritical 這個函數(shù)時,仍有發(fā)生數(shù)據(jù)復(fù)制的可能性垢乙,尤其是當(dāng) JVM 內(nèi)部存儲的數(shù)組不連續(xù)時锨咙,為了返回一個指向連續(xù)內(nèi)存空間的指針,JVM 必須復(fù)制所有數(shù)據(jù)追逮。下面代碼演示這對函數(shù)的正確用法:
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_Sample_sayHello
(JNIEnv *env, jclass cls, jstring j_str) {
const jchar* c_str= NULL;
char buff[128] = "hello ";
char* pBuff = buff + 6;
/*
* 在GetStringCritical/RealeaseStringCritical之間是一個關(guān)鍵區(qū)酪刀。
* 在這關(guān)鍵區(qū)之中,絕對不能呼叫JNI的其他函數(shù)和會造成當(dāng)前線程中斷或是會讓當(dāng)前線程等待的任何本地代碼,
* 否則將造成關(guān)鍵區(qū)代碼執(zhí)行區(qū)間垃圾回收器停止運作羊壹,任何觸發(fā)垃圾回收器的線程也會暫停蓖宦。
* 其他觸發(fā)垃圾回收器的線程不能前進直到當(dāng)前線程結(jié)束而激活垃圾回收器。
*/
// 返回源字符串指針的可能性
c_str = (*env)->GetStringCritical(env,j_str,NULL);
// 驗證是否因為字符串拷貝內(nèi)存溢出而返回NULL
if (c_str == NULL) {
return NULL;
}
while(*c_str) {
*pBuff++ = *c_str++;
}
(*env)->ReleaseStringCritical(env,j_str,c_str);
return (*env)->NewStringUTF(env,buff);
}
JNI 中沒有 Get/ReleaseStringUTFCritical 這樣的函數(shù)油猫,因為在進行編碼轉(zhuǎn)換時很可能會促使 JVM 對數(shù)據(jù)進行復(fù)制稠茂,因為 JVM 內(nèi)部表示的字符串是使用 Unicode 編碼的。
(5)GetStringRegion和GetStringUTFRegion
分別表示獲取 Unicode 和 UTF-8 編碼字符串指定范圍內(nèi)的內(nèi)容情妖。這對函數(shù)會把源字符串復(fù)制到一個預(yù)先分配的緩沖區(qū)內(nèi)睬关。下面代碼用 GetStringUTFRegion 重新實現(xiàn) sayHello 函數(shù):
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_Sample_sayHello
(JNIEnv *env, jclass cls, jstring j_str) {
jsize len = (*env)->GetStringLength(env,j_str); // 獲取unicode字符串的長度
printf("str_len:%d\n",len);
char buff[128] = "hello ";
char* pBuff = buff + 6;
// 將JVM中的字符串以utf-8編碼拷入C緩沖區(qū),該函數(shù)內(nèi)部不會分配內(nèi)存空間
(*env)->GetStringUTFRegion(env,j_str,0,len,pBuff);
return (*env)->NewStringUTF(env,buff);
}
GetStringUTFRegion 這個函數(shù)會做越界檢查,如果檢查發(fā)現(xiàn)越界了毡证,會拋出StringIndexOutOfBoundsException 異常电爹,這個方法與 GetStringUTFChars 比較相似,不同的是料睛,GetStringUTFRegion 內(nèi)部不分配內(nèi)存丐箩,不會拋出內(nèi)存溢出異常。
注意:GetStringUTFRegion 和 GetStringRegion 這兩個函數(shù)由于內(nèi)部沒有分配內(nèi)存恤煞,所以 JNI 沒有提供ReleaseStringUTFRegion 和 ReleaseStringRegion 這樣的函數(shù)屎勘。
5.6 字符串操作建議
- 對于小字符串來說,GetStringRegion 和 GetStringUTFRegion 這兩對函數(shù)是最佳選擇居扒,因為緩沖區(qū)可以被編譯器提前分配概漱,而且永遠(yuǎn)不會產(chǎn)生內(nèi)存溢出的異常。當(dāng)你需要處理一個字符串的一部分時喜喂,使用這對函數(shù)也是不錯瓤摧。因為它們提供了一個開始索引和子字符串的長度值。另外玉吁,復(fù)制少量字符串的消耗 也是非常小的照弥。
- 使用 GetStringCritical 和 ReleaseStringCritical 這對函數(shù)時,必須非常小心进副。一定要確保在持有一個由 GetStringCritical 獲取到的指針時产喉,本地代碼不會在 JVM 內(nèi)部分配新對象,或者做任何其它可能導(dǎo)致系統(tǒng)死鎖的阻塞性調(diào)用。
- 獲取 Unicode 字符串和長度曾沈,使用 GetStringChars 和 GetStringLength 函數(shù)。
- 獲取 UTF-8 字符串的長度鸥昏,使用 GetStringUTFLength 函數(shù)塞俱。
- 創(chuàng)建 Unicode 字符串,使用 NewStringUTF 函數(shù)吏垮。
- 從 Java 字符串轉(zhuǎn)換成 C/C++ 字符串障涯,使用 GetStringUTFChars 函數(shù)。
- 通過 GetStringUTFChars膳汪、GetStringChars唯蝶、GetStringCritical 獲取字符串,這些函數(shù)內(nèi)部會分配內(nèi)存遗嗽,必須調(diào)用相對應(yīng)的 ReleaseXXXX 函數(shù)釋放內(nèi)存粘我。