android 调用第三方so库

本文介绍如何在Android应用和框架层调用第三方so库的方法,包括使用dlopen和dlsym进行函数调用的过程,并解决权限问题。

首先要知道这个第三方的so库是不是按jni标准写的,如果是那就简单了,直接写个native调用就行了。如果不是那就比较麻烦了,必须要把这个so库里面的函数封装一下在调用,下面进入正题。

假设这个库放在/system/vendor/lib下面,名字为libtest.so,我们要调用的方法是get_status,我分别列出在第三方的app和framewok怎样调用。

首先是在第三方app的调用,这里的核心就是使用dlopen函数,然后使用dlsym找到这个函数,最后在调用这个函数。这还是公司的一个前辈教我的,真心不懂c.....

//定义LOGE打印
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
//定义一个指针函数,我这里没有传参数,如果需要的话可以在这里加参数
typedef int (*CAC_FUNC)();
//定义一个so库的路径
#define LIB_CACULATE_PATH "/system/vendor/lib/libtest.so"
Java_com_flycom_jnitest_MainActivity_intFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    LOGD("cac_func: %s","Java_com_flycom_jnitest_MainActivity_intFromJNI");
    void *handle;
    char *error;
    int r;
    CAC_FUNC cac_func = NULL;
    handle=dlopen(LIB_CACULATE_PATH,RTLD_LAZY);

    LOGD("cac_func: %s","handle");
    if (!handle) {
        LOGD("cac_func2: %s","EXIT_FAILURE");
        exit(EXIT_FAILURE);
    }
    //清除之前存在的错误
    dlerror();
    //获取一个函数
    LOGD("cac_func2: %s","get_status");
    *(void **) (&cac_func) = dlsym(handle, "get_status");
   
    if ((error = dlerror()) != NULL)  {
        LOGD("cac_func: %s","error");
        exit(EXIT_FAILURE);
    }
   //调用这个函数
    r =  (*cac_func)();

    LOGD("cac_func: %d\n", r);
    //关闭动态链接库
    dlclose(handle);
    return r;

}

然后用System.loadLibrary加载自己的这个库,写个native调用就行了。

 static {
        System.loadLibrary("native-lib");
 }
 public native int intFromJNI();

下面来说说遇到的坑

在调用时出现了这个错误,网上有大神解决了,主要是因为没有权限。

E/linker: library "/system/vendor/lib/libkphproxy.so" ("/vendor/lib/libkphproxy.so") needed or dlopened by "/data/app/com.flycom.jnitest-mKVoy6YOwxW_DNa_RgRhMQ==/lib/arm/libnative-lib.so" is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="", default_library_paths="/data/app/com.flycom.jnitest-mKVoy6YOwxW_DNa_RgRhMQ==/lib/arm:/data/app/com.flycom.jnitest-mKVoy6YOwxW_DNa_RgRhMQ==/base.apk!/lib/armeabi-v7a", permitted_paths="/data:/mnt/expand:/data/data/com.flycom.jnitest"]

在framework/vendor/etc目录下的public.libraries.txt,从这个文件的名字就可以大概知道其中的意义了如果没有的话可以新建一个,把你调用的so库名字加进去,注意必须是全名,例如我的libtest.so。在system/etc文件夹下面也有这个文件,当然是针对system/lib的so库,如果你想访问system/lib的so库请在这里添加。

接下来说下在framework层的添加,例如我想加在com_android_server_SystemServer.cpp

jni的调用是一样的,

static void android_server_SystemServer_startHidlServices(JNIEnv* env, jobject /* clazz */) {
    using ::android::frameworks::schedulerservice::V1_0::ISchedulingPolicyService;
    using ::android::frameworks::schedulerservice::V1_0::implementation::SchedulingPolicyService;
    using ::android::frameworks::sensorservice::V1_0::ISensorManager;
    using ::android::frameworks::sensorservice::V1_0::implementation::SensorManager;
    using ::android::hardware::configureRpcThreadpool;

    status_t err;

    configureRpcThreadpool(5, false /* callerWillJoin */);

    JavaVM *vm;
    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Cannot get Java VM");

    sp<ISensorManager> sensorService = new SensorManager(vm);
    err = sensorService->registerAsService();
    ALOGE_IF(err != OK, "Cannot register %s: %d", ISensorManager::descriptor, err);

    sp<ISchedulingPolicyService> schedulingService = new SchedulingPolicyService();
    err = schedulingService->registerAsService();
    ALOGE_IF(err != OK, "Cannot register %s: %d", ISchedulingPolicyService::descriptor, err);
}
//这里的名字随便取
static jint android_server_SystemServer_verifyStatus(JNIEnv*, jobject /* clazz */) {
	LOGD("cac_func2: %s","Java_com_flycom_jnitest_MainActivity_intFromJNI");
    
  LOGD("cac_func: %s","Java_com_flycom_jnitest_MainActivity_intFromJNI");
    void *handle;
    char *error;
    int r;
    CAC_FUNC cac_func = NULL;
    handle=dlopen(LIB_CACULATE_PATH,RTLD_LAZY);

    LOGD("cac_func: %s","handle");
    if (!handle) {
        LOGD("cac_func2: %s","EXIT_FAILURE");
        exit(EXIT_FAILURE);
    }
    //清除之前存在的错误
    dlerror();
    //获取一个函数
    LOGD("cac_func2: %s","get_status");
    *(void **) (&cac_func) = dlsym(handle, "get_status");
   
    if ((error = dlerror()) != NULL)  {
        LOGD("cac_func: %s","error");
        exit(EXIT_FAILURE);
    }
   //调用这个函数
    r =  (*cac_func)();

    LOGD("cac_func: %d\n", r);
    //关闭动态链接库
    dlclose(handle);
    return r;
}

这里遇到的坑就比较多了,注意函数一定要注册,否则找不到方法,第一个参数跟第三个参数好理解,关键的第二个参数"()I",这个括号里面是传入的参数,我的方法没有返回值,所以是空的,后面的“I”代表返回类型是int,这个可以在网上找到。

/*
 * JNI registration.
 */
static const JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "startSensorService", "()V", (void*) android_server_SystemServer_startSensorService },
    { "startHidlServices", "()V", (void*) android_server_SystemServer_startHidlServices },
	{ "verifyStatus", "()I", (void*) android_server_SystemServer_verifyStatus },
};

在这里调用是不需要在public.libraries.txt给权限的。然后有一个更奇葩的问题,就是我在framewoke层调用同样的函数,返回结果竟然跟app的结果不一样,这个问题困扰了我一天。最后问了提供so库的商家,才发现是权限的问题发火

在app中调用已经加了权限,而在system_server中没加权限,在device/mediatek/sepolicy/bsp/non_plat/system_server.te文件中加入访问的权限

allow system_server tkcore_admin_device:chr_file  { ioctl read write open };
最后终于调用成功。。。。


### Android JNI 调用第三方 SO #### 准备工作 为了成功调用第三方 `.so` 动态链接,项目结构需做适当调整。在 `build.gradle` 文件中指定源码路径以及 `.so` 的位置: ```groovy sourceSets { main { jni.srcDirs = [] // 不使用Gradle自动编译C/C++代码 jniLibs.srcDirs = ['libs', 'src/main/libs'] // 加载.so所在目录 } } ``` 此配置告知 Gradle 构建系统忽略默认的 C/C++ 编译流程并手动管理 `.so` 文件[^3]。 #### Java 层加载 .so Java 类中应包含用于加载共享的方法声明: ```java public class NativeHelper { static { System.loadLibrary("native-lib"); // 假设目标名为libnative-lib.so } public native String callNativeFunction(); } ``` 这里假设要加载的是位于 `/libs/armeabi-v7a/libnative-lib.so` 的,并定义了一个原生方法供后续实现[^4]。 #### 实现 JNI 接口 创建对应的 C 或者 C++ 文件去具体实现这些接口逻辑。对于每一个被标记为 `native` 关键字修饰的方法都需要有一个相应的 C/C++ 函数作为其实现体。例如针对上面提到的例子可以在 cpp 中这样写: ```cpp #include <jni.h> #include "com_example_NativeHelper.h" extern "C" JNIEXPORT jstring JNICALL Java_com_example_NativeHelper_callNativeFunction(JNIEnv *env, jobject /* this */) { return env->NewStringUTF("Hello from C++!"); } ``` 注意这里的命名空间匹配规则:包名替换为下划线 `_` ,类名紧跟其后,最后是方法签名[^2]。 #### 验证符号表 如果遇到未解析引用错误,则可能是由于缺少必要的导出符号造成的。此时可借助工具如 `nm` 来查看实际存在的全局符号列表,确保所需函数确实存在于最终打包进去的目标文件里[^5]: ```bash $ aarch64-linux-android-nm -D path/to/sharedlibrary.so | grep functionName ``` 以上步骤涵盖了从环境搭建到基本功能验证整个过程中的要点。通过这种方式能够有效地集成外部预构建好的 `.so` 文件至应用程序当中,从而充分利用现有资源完成特定任务需求。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值