android4.4 Jni函数查找失败问题处理(java.lang.UnsatisfiedLinkError: Native method not found)

本文分析了JNI函数查找失败的问题,探讨了系统加载SO库时的机制及冲突原因,并提出了相应的解决方案。

Jni函数查找失败问题分析

1、 问题点描述:

系统jni函数查找报错java.lang.UnsatisfiedLinkError: Native method not found。通常的原因是函数书写错误,但是在肯定函数没有问题的情况下,偶尔还会出现报错的问题。

2、 问题点原因分析:

1 经过log分析问题点发生时,软件在调用mcu的指令发送函数McuDaemon.write0,系统查找不到此函数在Jni层内的具体实现,故抛出函数查找不到的错误。

E/AndroidRuntime( 2471): java.lang.UnsatisfiedLinkError: Native method not found: com.soling.uart.McuDaemon.write0:([B)V

2 此函数在Jni层有正常实现,除了概率性报错外,其他场景下都能够正常运行。故不是软件代码编写的问题,而是系统函数查找检索出现问题。

3 继续分析log,在问题出现时,在另外一个线程有其他的库方法被调用到。推测为两个库的方法同时调用有冲突。

I/sysdata jni( 2471): sysdata_opera --->func:int private_store_write(const char*, const char*) write 340=128
I/sysdatajni(2471):sysdata_opera--->func:jint com_soling_emc_test_FileSystemDataSave_native_setdata(JNIEnv*, jobject, jstring, jstring) [private_data_write] :Write private data fail


4 分析源码,抛出函数查找不到的错误在android/dalvik/vm/Native.cpp第129行:

    /* now scan any DLLs we have loaded for JNI signatures */
    void* func = lookupSharedLibMethod(method);
    if (func != NULL) {
        /* found it, point it at the JNI bridge and then call it */
        dvmUseJNIBridge((Method*) method, func);
        (*method->nativeFunc)(args, pResult, method, self);
        return;
    }

    IF_ALOGW() {
        char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
        ALOGW("No implementation found for native %s.%s:%s",
            clazz->descriptor, method->name, desc);
        free(desc);
    }

    dvmThrowUnsatisfiedLinkError("Native method not found", method);


由上述代码可以看到,lookupSharedLibMethod时获取到的函数为空。


5 系统查找检索的方法分析

函数如下:

/*
 * See if the requested method lives in any of the currently-loaded
 * shared libraries.  We do this by checking each of them for the expected
 * method signature.
 */
static void* lookupSharedLibMethod(const Method* method)
{
    if (gDvm.nativeLibs == NULL) {
        ALOGE("Unexpected init state: nativeLibs not ready");
        dvmAbort();
    }

    return (void*) dvmHashForeach(gDvm.nativeLibs, findMethodInLib,
        (void*) method);
}

6 dvmHashForeach 是HashTable中轮询函数的方法,findMethodInLib为从一个so库中查找函数的方法。可以看出系统加载每个程序的so库时,会保存在对应的一个HashTable中,java层调用native方法时,会遍历HashTable中保存的so库信息,来查找对应的jni函数。分析此流程可以看出,当问题出现时,无法从HashTable的so库中查找到对应的函数,但是此时可以看出对应的so库已经加载,但是检索函数没有查找对应的so库。故继续分析dvmHashForeach函数。

7 dvmHashForeach函数为:

/*
 * Scan every entry in the hash table and evaluate it with the specified
 * indirect function call. If the function returns 1, remove the entry from
 * the table.
 *
 * Does NOT invoke the "free" function on the item.
 *
 * Returning values other than 0 or 1 will abort the routine.
 */
int dvmHashForeachRemove(HashTable* pHashTable, HashForeachRemoveFunc func)
{
    int i, val, tableSize;

    tableSize = pHashTable->tableSize;

    for (i = 0; i < tableSize; i++) {
        HashEntry* pEnt = &pHashTable->pEntries[i];

        if (pEnt->data != NULL && pEnt->data != HASH_TOMBSTONE) {
            val = (*func)(pEnt->data);
            if (val == 1) {
                pEnt->data = HASH_TOMBSTONE;
                pHashTable->numEntries--;
                pHashTable->numDeadEntries++;
            }
            else if (val != 0) {
                return val;
            }
        }
    }
    return 0;
}

结合日志可以看到HashTable在轮询过程中,HashTable被外部线程改变,长度发生变化,导致so库没有查找完全。

3、 问题原因总结:

1、系统加载每个程序的so库时,会保存在对应的一个HashTable中。每次程序调用loadLibrary 时,hashTable会插入so库的信息,长度会根据情况发生变化.

2、java层调用native方法时,会遍历HashTable中保存的so库信息,来查找对应的jni函数。

3、当查找jni函数时,如果HashTable发生长度变化,so库在table中的信息可能发生位置偏移,但系统中dalvik使用的遍历HashTable的方法并不会更新遍历的长度,可能导致无法查找到对应库的jni函数。

4、 问题对策:

为了避免查找jni函数和加载so库两种动作冲突导致的jni函数查找失败,故程序开始运行时统一加载所有的so库。

5、结论:

软件设计过程中要多思考程序执行存在的不确定性,虽然有些时序发生的概率非常之小,也要从细节上提升软件的可靠性、准确性、稳定性。之所以会出现此次报错,归根结底是android系统本身的问题,但是在不能完全更换系统的情况下,必须针对不同场景,使用特定的方法来适配系统。


另:通过搜索此问题,找到google的dalvik虚拟机针对此查询特别将遍历hashtable的方法由动态长度改为静态长度,可能是针对某问题而改动,但由此引发了报错的隐患,故不进行源码修改,而从策略上来做规避。

 

出现 `java.lang.UnsatisfiedLinkError: dlopen failed: library "libmediapipe_tasks_vision_jni.so" not found` 错误通常表示系统在运行时无法找到或加载指定的本地库(`.so` 文件)。以下是可能的原因及解决方法: ### 1. 检查 `.so` 文件是否正确放置 确保 `libmediapipe_tasks_vision_jni.so` 文件已经正确放置在应用的 `jniLibs` 目录中。Android 项目的目录结构应如下所示: ``` app/ └── src/ └── main/ └── jniLibs/ └── armeabi-v7a/ 或 arm64-v8a/ 或 x86_64/ └── libmediapipe_tasks_vision_jni.so ``` 如果使用的是 CMake 或 ndk-build,确保 `.so` 文件已正确构建并放置在对应架构的目录中。 如果使用的是 Gradle 构建系统,可以在 `build.gradle` 文件中配置 `jniLibs` 的路径: ```groovy android { ... sourceSets { main { jniLibs.srcDirs = ['src/main/jniLibs'] } } } ``` 此配置确保 `.so` 文件被正确打包进 APK 中 [^3]。 ### 2. 检查 `.so` 文件是否被正确打包进 APK 使用 `APK Analyzer` 工具检查 `.so` 文件是否被包含在最终的 APK 包中。可以通过 Android Studio 的菜单 **Build > Analyze APK** 来打开 APK 文件,并查看 `lib/<architecture>/` 目录下是否存在 `libmediapipe_tasks_vision_jni.so` 文件 [^3]。 ### 3. 检查 `.so` 文件的依赖关系 如果 `libmediapipe_tasks_vision_jni.so` 依赖于其他 `.so` 文件(例如 `libmediapipe_jni.so` 或其他第三方库),请确保这些依赖库也被正确打包进 APK 中,并且它们的路径和架构匹配。例如: ``` app/ └── src/ └── main/ └── jniLibs/ └── arm64-v8a/ ├── libmediapipe_tasks_vision_jni.so └── libmediapipe_jni.so ``` 如果目标设备是 64 位架构,确保同时提供 32 位和 64 位的 `.so` 文件,或根据设备架构选择性打包 [^2]。 ### 4. 使用 `System.loadLibrary` 加载库 确保在 Java 代码中使用 `System.loadLibrary` 正确加载了本地库。例如: ```java static { System.loadLibrary("mediapipe_tasks_vision_jni"); } ``` 注意:`loadLibrary` 方法不带 `.so` 扩展名,仅使用库名。 如果需要从特定路径加载 `.so` 文件(例如从 `assets` 或外部存储加载),可以使用 `System.load` 方法并提供完整路径: ```java System.load("/data/data/your.package.name/lib/libmediapipe_tasks_vision_jni.so"); ``` ### 5. 检查 Android 9.0(API 28)及以上的限制 从 Android 9.0 开始,系统对非公开 API 的访问进行了限制,特别是对 `/system/lib/` 和 `/vendor/lib/` 路径下的 `.so` 文件。如果应用作为系统应用部署在 `/system/app` 目录下,并且 `.so` 文件未正确签名或权限配置不正确,可能会导致 `UnsatisfiedLinkError` [^4]。 解决方法包括: - 确保 `.so` 文件的权限设置正确(例如 `chmod 644`)。 - 如果 `.so` 文件依赖于系统库(如 `libcrypto.so`),请确保这些库在系统中存在且版本兼容 [^2]。 ### 6. 检查 Gradle 插件配置 如果在 Gradle 插件中禁用了 NDK 构建流程,可能会导致 `.so` 文件未被识别或打包。例如,以下代码可能会导致 `.so` 文件未被正确处理: ```groovy tasks.whenTaskAdded { task -> if (task.name.contains("Ndk") || task.name.contains("Jni")) { task.enabled = false } } ``` 请确保该段代码被注释或删除,以允许 Gradle 正确处理 `.so` 文件 [^5]。 ### 7. 使用 `readelf` 或 `objdump` 检查 `.so` 文件 使用 `readelf -d libmediapipe_tasks_vision_jni.so` 或 `objdump -p libmediapipe_tasks_vision_jni.so` 检查 `.so` 文件的依赖项。确保所有依赖的 `.so` 文件都存在于 APK 或系统路径中,并且版本兼容。 例如,检查依赖项: ```bash readelf -d libmediapipe_tasks_vision_jni.so | grep NEEDED ``` ### 8. 使用 `MediaPipe` 官方依赖 如果使用的是 MediaPipe 框架,建议直接使用官方提供的 AAR 依赖,而不是手动管理 `.so` 文件。例如,在 `build.gradle` 中添加: ```groovy dependencies { implementation 'com.google.mediapipe:tasks-vision:latest_version' } ``` 这将自动处理 `.so` 文件的打包和加载 [^1]。 ---
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值