在上一篇文章中,咱們已經介紹了一個JNI工程的大體結構,接下來本文將對這個工程中的一些細節進行介紹。java
對於通常的JNI工程,編譯、安裝、運行的流程大體以下:linux
編譯:android
安裝:windows
運行:bash
System.loadLibrary
進行加載動態庫,解析其中的符號java.lang.UnsatisfiedLinkError
,並在日誌中打印相關信息dlsym
函數檢查是否重寫了JNI_OnLoad
函數,若是重寫了該函數,則執行該函數JNI_OnLoad
函數中進行動態註冊的函數,則能夠直接找到對應的函數運行;對於未進行動態註冊的函數,會按照Java_包名_類名_函數名
的規則去尋找native函數,固然了,函數的參數、回傳值也是要進行驗證的以上大體就是使用的一個流程,接下來回到工程,對這個默認的工程的一些細節進行解釋。app
Java部分
Java部分主要作了兩件事情:ide
static {
System.loadLibrary("native-lib");
}
複製代碼
public native String stringFromJNI();
複製代碼
native部分
native函數的實現:
函數標識:extern "C" JNIEXPORT
回傳值類型:jstring
參數類型:自動添加了JNIEnv*
和jobject
函數
extern "C" JNIEXPORT jstring JNICALL Java_com_wsy_jnidemo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */){
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
複製代碼
CMakeLists部分工具
# 聲明最低的cmake版本
cmake_minimum_required(VERSION 3.4.1)
# 添加一個名稱叫native-lib的動態庫,該庫的源文件爲src/main/native-lib.cpp
add_library( native-lib # 庫的名稱
SHARED # SHARED:動態庫、STATIC:靜態庫
src/main/native-lib.cpp # 源文件,能夠是多個
)
# 尋找系統中的log庫,保存在log-lib變量中
find_library( log-lib
log )
# native-lib這個庫會去依賴log-lib這個庫
target_link_libraries( native-lib
${log-lib} )
複製代碼
咱們仔細看一下這個native函數,雖然它的實現很簡單,可是這些亂七八糟的符號是什麼鬼?
oop
extern "C"
extern "C"
說明
在進行靜態註冊時,是要加上extern "C"
的,它的做用主要是爲了讓編譯器以C
的方式去編譯它,而不是C++
,咱們知道,C++是一門面向對象的語言,是支持函數重載的,以下:
而對於C
語言,它是不支持函數重載的,若進行重載,在Android Studio
下編譯器就會報錯,提示Duplicate declaration of function xxx
:
那若是去掉extern "C"
,效果會怎樣?
首先,編譯器會很友好地提示你:
若是不接受建議,效果會怎樣?
那就是crash
Process: com.wsy.jnidemo, PID: 27921
java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.wsy.jnidemo.MainActivity.stringFromJNI()
(tried Java_com_wsy_jnidemo_MainActivity_stringFromJNI and
Java_com_wsy_jnidemo_MainActivity_stringFromJNI__)
at com.wsy.jnidemo.MainActivity.stringFromJNI(Native Method)
at com.wsy.jnidemo.MainActivity.onCreate(MainActivity.java:22)
at android.app.Activity.performCreate(Activity.java:7815)
at android.app.Activity.performCreate(Activity.java:7804)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1318)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3349)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3513)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2109)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7682)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:516)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
複製代碼
這個提示是在運行後點擊按鈕出現的,也就是說,動態庫加載成功了,那隻能說明函數名被改了。
函數名被修改爲了什麼?
工具路徑
咱們能夠使用NDK
中提供的nm
進行分析,假如咱們的動態庫是armeabi-v7a
的,那麼咱們選擇的nm
工具最好是toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin
下的arm-linux-androideabi-nm.exe
,而不要選擇其餘目錄下的,雖然可能會存在兼容,可是有些是不兼容的。
工具使用方式
nm -D 動態庫文件
分析加extern 'C'
和不加extern 'C'
的兩個動態庫
執行gradlew externalNativeBuildRelease
獲取動態庫,進行解析
extern 'C'
的動態庫
extern 'C'
的動態庫對於靜態註冊的函數而言,函數名發生了變動,按照原有的規則找不到對應的函數,所以就會報java.lang.UnsatisfiedLinkError
JNIEXPORT
宏定義以下
#define JNIEXPORT __attribute__ ((visibility ("default")))
複製代碼
JNIEXPORT
描述了其可見性爲default,所以在不進行native代碼混淆的狀況下,其實咱們去掉也沒有問題,可是若是咱們把visibility
修改成hidden
效果又如何?
咱們來試試,將函數聲明修改成:
extern "C" __attribute__ ((visibility ("hidden"))) jstring JNICALL Java_com_wsy_jnidemo_MainActivity_stringFromJNI
複製代碼
運行:
JNICALL
宏這個宏的定義以下
#define JNICALL
複製代碼
沒錯是空的,這個可用於標記,編譯時標記函數用了這個宏,至於做用,至今未發現,有了解的朋友麻煩告知下。
函數聲明:
extern "C" JNIEXPORT jstring JNICALL
Java_com_wsy_jnidemo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
複製代碼
extern "C"
、 JNIEXPORT
、JNICALL
都已在上述進行說明,接下來看下這個函數的函數名和返回值。
JNI提供了一套規則來實現靜態註冊時的函數名查找,方式就是:
Java_包名_類名_函數名
順便一提,在咱們未進行動態註冊時,函數的註冊是在首次調用這個函數時進行的,所以在動態庫加載完成時,一個native函數的首次調用耗時會高於後續的調用耗時。咱們能夠驗證一下:
for (int i = 0; i < 100; i++) {
long start = System.nanoTime();
stringFromJNI();
long end = System.nanoTime();
Log.i(TAG, "onCreate: " + (end - start));
}
複製代碼
JNI函數會自動添加兩個參數:JNIEnv *
和jobject
第一個參數是JNIEnv *env
,咱們平時的JNI操做也基本都依賴這個變量來實現;
第二個參數在Java函數是靜態函數時是jclass
,是成員函數時是jobject
,其中jclass
是jobject
的子類。
對於Java函數而言,它須要的返回值是一個java.lang.String
對象,與之對應的是native的jstring
對象。