隨着App功能的不斷增多,Native層的代碼規模也在迅速膨脹,爲了Native層的代碼結構清晰,會按照模塊分別構建成獨立的so庫,使用一個JNI層so庫引用其餘實現具體功能的功能實現so庫,Java層只加載這個JNI層so庫,來間接調用功能實現so庫。數據結構
so庫之間經過引用頭文件和運行時指定共享庫依賴的方式造成了依賴關係。可是這樣也會有一些問題。函數
這個時候就須要在Native層直接動態加載so庫,由JNI層so庫動態加載功能實現so庫。以下圖所示,會有一個統一接口so庫,在這個庫中定義好不可輕易修改的接口函數,調用方只須要知道這些接口便可,不須要依賴頭文件就能調用這些函數,這樣調用方和so庫之間就不存在直接的依賴,具體的工做就能夠交給統一接口so庫完成,它經過動態調用再去執行功能so庫中的函數。性能
在Native層的C/C++代碼環境,so庫動態加載是使用dlopen()
、dlsym()
和dlclose()
這三個函數實現的。它們的做用分別是:dlopen()
打開一個動態連接庫,返回一個動態連接庫的句柄;dlsym()
根據動態連接庫句柄和符號名,返回動態連接庫內的符號地址,這個地址既能夠是變量指針,也能夠是函數指針;dlclose()
關閉動態連接庫句柄,並對動態連接庫的引用計數減1,當這個庫的引用計數爲0,這個庫將會被系統卸載。spa
通常使用C/C++實現so庫動態加載的流程以下:指針
dlopen()
函數,這個函數所需的參數,一個是so庫的路徑,一個是加載模式。通常使用的加載模式有兩個:RTLD_NOW
在返回前解析出全部未定義符號,若是解析不出來,dlopen()
返回NULL
;RTLD_LAZY
則只解析當前須要的符號(只對函數生效,變量定義仍然是所有解析)。顯然對於動態加載,加載方只需知道當前被加載的so庫裏面本身須要用的函數和變量定義,因此這裏選擇的是後者。若是這個調用成功將返回一個so庫的句柄;dlsym()
函數,傳入so庫句柄和所需的函數或變量名稱,返回相應的函數指針或變量指針;加載方這時就可使用返回的指針調用被加載so庫之中定義的函數和數據結構;dlclose()
函數關閉卸載so庫;dlerror()
函數獲取具體的錯誤緣由。好比,在硬件功能so庫中有一個int test_open(int port)
的函數,該如何最終調用到這個方法呢?code
//一、聲明函數接口
typedef int (*Func_test_open)(int);
int open(int port){
//二、獲取so句柄
void *handle = dlopen("libtest.so",RTLD_LAZY);
if(!handle ){
LOGE("%s",dlerror());
return -1;
}
//三、獲取函數指針
Func_test_open func_test_open = (Func_test_open) dlsym (handle,"test_open");
if(!func_test_open){
LOGE("%s",dlerror());
dlclose(handle);
return -1;
}
//四、調用函數
int ret = func_test_open(8080);
//五、關閉so
dlclose(handle);
return ret;
}
複製代碼
這樣JNI層只須要去調用open(int port)
方法就能夠調用到硬件功能so庫中的test_open(int port)
函數cdn
剛開始使用動態加載so庫的方案時,會比較擔憂性能問題,但在實測時跟直接依賴對比,對性能並無明顯的影響,功能實現的so庫與JNI層徹底解耦,有高度的獨立內聚性。同時支持動態加載卸載so庫,也必定程度上減小了Native層的常駐內存。對象