我的情况下,System.loadLibrary(...) 找不到本地库。

112

我想从另一个Android项目中使用现有的本地库,所以我只需将NDK构建库(libcalculate.so)复制到我的新Android项目中。在我的新Android项目中,我创建了一个文件夹libs / armeabi /,并将libcalculate.so放在那里。没有jni /文件夹。我的测试设备具有ARM架构。

在我的Java代码中,我通过以下方式加载库:

  static{
    System.loadLibrary("calculate");
  }

当我运行我的新的安卓项目时,出现了错误:
java.lang.UnsatisfiedLinkError:  ...
nativeLibraryDirectories=[/vendor/lib, /system/lib]]] couldn't find "libcalculate.so"

所以,正如错误提示所说,复制的本地库不在/verdor/lib或/system/lib中,我该如何解决我的问题?

(我解压了apk包,在lib/下有libcalculate.so)

====更新=====

我还尝试在项目根目录下创建一个jni/文件夹,并在jni/下添加一个Android.mk文件。Android.mk文件的内容是:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := libcalculate
LOCAL_SRC_FILES := libcalculate.so
include $(PREBUILT_SHARED_LIBRARY)

接着,在项目根目录下,我执行了 ndk-build 命令。之后,ndk-build 生成了 armeabi/ 和 armeabi-v7a/ 目录(并将 libcalculate.so 放入了这两个目录中)。

然后,我成功地运行了 maven 构建项目。在最终的 apk 包中,包含以下文件:

lib/armeabi/libcalculate.so
lib/armeabi-v7a/libcalculate.so

但是当我运行我的应用程序时,仍然会抛出相同的错误:

java.lang.UnsatisfiedLinkError:  ...
nativeLibraryDirectories=[/vendor/lib, /system/lib]]] couldn't find "libcalculate.so"

3
你是将库文件直接放在“libs/”目录下吗?你可能需要为每个目标ABI创建一个子目录来支持(armeabi、armeabi-v7a、x86、mips等),并在每个子目录中放置相应的.so文件(即,构建用于armeabi的.so文件放在“libs/armeabi/”中,以此类推)。 - Michael
@Michael,抱歉我在我的帖子中漏掉了,实际上我将它放在了libs/armeabi/下。 - user842225
检查libcalculate.so是否确实被打包进程选中-例如尝试unzip-l package.apk,或将apk重命名为.zip并使用某些应用程序打开它。如果没有出现,请检查打包过程(您的IDE是否注意到该文件夹是否存在,您是否需要刷新项目?)。 - mstorsjo
1
你不需要Android.mk或任何编译相关的文件。只需将so文件放入其相应的子目录中,如jniLibs中所示:https://github.com/Ph1b/MaterialAudiobookPlayer/tree/master/audiobook/src/main - Paul Woitaschek
即使不是最佳实践,您仍然可以使用库来测试本地库的最快方法 - 只需确保使用ABI目录,然后使用sourceSets {main {jniLibs.srcDirs = ['libs']}}。请参见https://medium.com/@pokkbaby/android-studio-import-native-library-9dda32e58253。 - Droid Teahouse
显示剩余3条评论
14个回答

195

为了找到根本原因(并可能同时解决您的问题),您可以执行以下操作:

  1. 删除jni文件夹和所有mk文件。如果您没有编译任何内容,则不需要这些文件或NDK。

  2. 将libcalculate.so文件复制到<project>/libs/(armeabi|armeabi-v7a|x86|...)。在使用Android Studio时,它是<project>/app/src/main/jniLibs/(armeabi|armeabi-v7a|x86|...),但我看到您正在使用eclipse。

  3. 构建您的APK并将其打开为zip文件,以检查您的libcalculate.so文件是否位于lib/(armeabi|armeabi-v7a|x86|...)中。

  4. 卸载并重新安装您的应用程序

  5. 运行dumpsys package packages | grep yourpackagename获取应用程序的nativeLibraryPathlegacyNativeLibraryDir

  6. 在您拥有的nativeLibraryPath上运行ls或者在legacyNativeLibraryDir/armeabi上运行ls,以检查您的libcalculate.so是否确实存在。

  7. 如果它在那里,请检查它是否未被更改过,以与您原始的libcalculate.so文件相同:是否编译为正确的架构,是否包含所需符号,是否存在任何丢失的依赖项。您可以使用readelf分析libcalculate.so。

为了检查步骤5-7,您可以使用我的应用程序而不是命令行和readelf:本地库监视器

附:很容易混淆.so文件应该放在哪里或默认生成的位置,在此是一个摘要:

  • libs/CPU_ABI 在Eclipse项目中使用

  • jniLibs/CPU_ABI 在Android Studio项目中使用

  • jni/CPU_ABI 在AAR(Android Archive)中使用

  • lib/CPU_ABI 在最终的APK文件中使用

  • 在<5.0设备中,位于应用程序的nativeLibraryPath内,在>=5.0设备上,位于应用程序的legacyNativeLibraryDir/CPU_ARCH内。

其中,CPU_ABI可以是以下任意一种: armeabi,armeabi-v7a,arm64-v8a,x86,x86_64,mips,mips64。具体取决于你的目标架构以及你的库已经为哪些架构编译。

另外请注意,不同的CPU_ABI目录中的库不会混合使用:如果在APK中的armeabi-v7a文件夹中有任何库,则位于armeabi文件夹内的库将不会安装到armeabi-v7a设备上。


3
太棒了,谢谢!我正在使用Android Studio,但我的JNI构建被复制到libs而不是jniLibs中。 - Luis
4
关于需要完整套装的最后一条注释对我非常关键。那就是我的问题所在,谢谢! - Ben Trengrove
对于第 7 部分:您的意思是在 APK 安装到设备后,.so 文件可能会被更改吗?如果是这样,系统会破坏 .so 文件的可能性有多大? - jayatubi
5
太好了!在我的非常奇怪的情况下,我正在使用一组第三方库(OpenCV-位于armeabi文件夹中),当我通过Gradle添加不同的第三方库时,这些库停止加载。原来第二个库不支持ARMv5或6,通过包含它,我的OpenCV库变得不可见(尽管它们实际上存在)。您提到的完整集合给了我线索——将armeabi文件夹重命名为armeabi-v7a解决了问题(因为我现在不支持ARM 5或6……)。这是一个麻烦的问题! - Mete
3
另一种找到放置lib文件(*.so)的位置的方法是运行应用程序并使用以下代码打印nativeLibraryDir:System.out.println(getApplicationContext().getApplicationInfo().nativeLibraryDir),目录的名称还将为您提供ABI。 - David Rauca
显示剩余6条评论

26

在 Gradle 中,将所有文件和文件夹复制到 libs/ 目录后

jniLibs.srcDirs = ['libs']
将上述行添加到build.gradle文件中的sourceSets中有效。没有其他任何方法有效。

3
在build.gradle文件中,"sourceSets"位于哪里? - Ashana.Jackol
@Ashana.Jackol 在你的 build.gradle 文件中 - olajide

18

出现此错误的原因是您的应用程序和链接的本地库之间ABI不匹配。换句话说,您的应用程序和.so针对不同的ABI。

如果您使用最新的Android Studio模板创建应用程序,则其可能针对arm64-v8a进行定位,但您的.so可能针对armeabi-v7a进行定位,例如。

有两种方法可以解决此问题:

  1. 为应用程序支持的每个ABI构建本地库。
  2. 将您的应用程序更改为针对旧版ABI,与您构建.so的ABI相匹配。

选择2可能不太好,但我认为您可能更感兴趣的是:

更改应用程序的build.gradle

android {
    defaultConfig {
        ...
        ndk {
            abiFilters 'armeabi-v7a'
        }
   }
}

如果我的应用程序在Eclipse中怎么办?当我将同一定制的应用程序从6迁移到9时,就会出现这个问题。 - Shadow
这是真正的方法,在我的情况下起作用了!:-)。 - Robbie

17

在我的情况下,我必须排除使用 Gradle 编译源代码并设置库路径。

android {

    ...
    sourceSets {
        ...
        main.jni.srcDirs = []
        main.jniLibs.srcDirs = ['libs']
    }
....

这对我也解决了问题,我将armeabi文件添加到armeabi-v7a和x86文件夹中,但我不确定是否必要。 - dokam_scotland

14
你正在使用gradle吗?如果是,请将.so文件放在<project>/src/main/jniLibs/armeabi/目录中。 希望这能帮到你。

不,我没有使用Gradle,我是使用Eclipse+Maven。 - user842225

11

仅供参考,我曾遇到此错误信息,解决方法是在指定库文件时,前面要省略'lib',结尾要省略'.so'。

也就是说,如果你有一个名为libmyfablib.so的文件,则需要调用:

   System.loadLibrary("myfablib"); // this loads the file 'libmyfablib.so' 

我查看了apk文件,进行了安装/卸载操作并尝试了各种复杂的解决方案,但我却没有发现就在我眼前的简单问题!


1
就是这样。由于某种未知原因,即使包中存在,Android也不会安装文件名不以“lib”开头的库。真是让人费解... - George Y.
在项目中我该在哪里检查?我的意思是在代码中哪里可以找到这行 System.loadLibrary - aleksandrbel
谢谢。这很有帮助! - Riskhan
哦。我的。天!我花了一整天的时间通过检查路径来解决它,但其实都很简单,非常感谢你! - Acuna

9

这是一个Android 8的更新。

在早期版本的Android中,为了加载本地共享库(例如通过JNI访问),我把我的本地代码硬编码成遍历一系列潜在目录路径以获取lib文件夹,基于各种apk安装/升级算法:

/data/data/<PackageName>/lib
/data/app-lib/<PackageName>-1/lib
/data/app-lib/<PackageName>-2/lib
/data/app/<PackageName>-1/lib
/data/app/<PackageName>-2/lib

这种方法不可靠,并且在Android 8上无法使用。从https://developer.android.com/about/versions/oreo/android-8.0-changes.html可以看出,在他们的“安全”改变的一部分中,现在需要使用sourceDir:

"您不能再假设APK位于名称以-1或-2结尾的目录中。应用程序应使用sourceDir获取目录,而不是直接依赖目录格式。"

更正,sourceDir不是找到本地共享库的方法;请使用类似的方法。已测试适用于Android 4.4.4-8.0。

// Return Full path to the directory where native JNI libraries are stored.
private static String getNativeLibraryDir(Context context) {
    ApplicationInfo appInfo = context.getApplicationInfo();
    return appInfo.nativeLibraryDir;
}

5
尝试在包含PREBUILT_SHARED_LIBRARY部分后调用您的库:
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := libcalculate
LOCAL_SRC_FILES := <PATH>/libcalculate.so
include $(PREBUILT_SHARED_LIBRARY)

#...

LOCAL_SHARED_LIBRARIES += libcalculate

更新:

如果你想在Java中使用这个库,你需要将其编译为共享库。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := libcalculate
LOCAL_SRC_FILES := <PATH>/libcalculate.so
include $(BUILD_SHARED_LIBRARY)

您需要将库部署在/vendor/lib目录中。


仅在本节末尾。 - Alex

3

您可以通过更改ABI来使用旧版本构建:

defaultConfig {
    ...

    ndk {
        abiFilters 'armeabi-v7a'
    }
    ...
}

您还应该通过将以下行添加到gradle.properties中来使用已弃用的NDK:

android.useDeprecatedNdk=true

1

实际上,你不能只是把一个 .so 文件放在 /libs/armeabi/ 目录下,并使用 System.loadLibrary 加载它。你需要创建一个 Android.mk 文件并声明一个预构建模块,在其中指定你的 .so 文件作为源文件。

为此,请将你的 .so 文件和 Android.mk 文件放在 jni 文件夹中。 你的 Android.mk 应该类似于以下内容:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := libcalculate
LOCAL_SRC_FILES := libcalculate.so
include $(PREBUILT_SHARED_LIBRARY)

来源:关于预构建的Android NDK文档


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接