在Android中使用dlopen加载so

使用cmake解决Android中对第三方库的依赖一文中,我们通过cmake的配置来将依赖的so给链接了起来。但是有时我们可能需要在程序运行地时候来动态地加载依赖库。linux 提供了动态加载依赖库的系统调用,我们在 Android 中也可以使用。

  1. 将我们要依赖的 so 和工程中其他的 so 都放入到 jniLibs 目录下,这样所有的 so 文件都会打包到 App 中。当然被依赖的 so 也可以放入其他指定的目录。
  2. 获取到被依赖 so 的路径,如果被依赖 so 放在自定义的路径里,那这一步可以忽略。我们可以根据 App 里其他 so 的路径来得到被依赖 so 的路径。如被依赖的 so 为 liblayer1.so 而我们准备在 liblayer2.so 里面来动态地加载 liblayer1.so 就可以通过下面的程序来获取到 liblayer1.so 所在的目录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    std::string get_self_dir() {
    static const char *SELF_NAME = "/liblayer2.so";
    static const size_t SELF_NAME_LEN = strlen(SELF_NAME);
    FILE *fmap = fopen("/proc/self/maps", "r");
    if (!fmap) {
    LOG_E("failed to open maps");
    return {};
    }
    std::unique_ptr<FILE, int (*)(FILE *)> fmap_close{fmap, ::fclose};
    char linebuf[512];
    while (fgets(linebuf, sizeof(linebuf), fmap)) {
    uintptr_t begin, end;
    char perm[10], offset[20], dev[10], inode[20], path_mem[256], *path;
    int nr = sscanf(linebuf, "%zx-%zx %s %s %s %s %s", &begin, &end, perm,
    offset, dev, inode, path_mem);
    if (nr == 6) {
    path = nullptr;
    } else {
    if (nr != 7) {
    LOG_E("failed to parse map line: %s", linebuf);
    return {};
    }
    path = path_mem;
    }
    if (path) {
    auto len = strlen(path);
    auto last_dir_end = path + len - SELF_NAME_LEN;
    if (!strcmp(last_dir_end, SELF_NAME)) {
    last_dir_end[1] = 0;
    return path;
    }
    }
    }
    LOG_E("can not find path of %s", SELF_NAME + 1);
    return {};
    }
  3. 将 dlfcn.h 头文件给引入进来

    1
    #include <dlfcn.h>

    通过 dlopen 来加载 liblayer1.so 然后同过 dlsym 来获取到 liblayer1.so 对外暴露的符号

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    NET_API_FUNCTIONS_TYPE_LAYER1 *client_layer1;
    static std::string self_dir = get_self_dir();
    if (self_dir.empty())
    return;
    std::string path{self_dir};
    path.append("liblayer1.so");
    LOG_E("dlopen %s:", path.c_str());
    void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
    if (!handle) {
    return;
    }
    auto sym = dlsym(handle, "net_client_layer1");

    if (!sym) {
    return;
    }
    client_layer1 = reinterpret_cast<NET_API_FUNCTIONS_TYPE_LAYER1 *>(sym);
  4. 通过 client_layer1 就可以像原来一样调用 liblayer1.so 里的方法了。

完整代码请见Github