随着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层的常驻内存。对象